views:

624

answers:

3

I haven't been able to find anything for a situation like this. I have a model which has a named scope defined thusly:

class Customer < ActiveRecord::Base
  # ...
  named_scope :active_customers, :conditions => { :active => true }
end

and I'm trying to stub it out in my Controller spec:

# spec/customers_controller_spec.rb
describe CustomersController do
  before(:each) do
    Customer.stub_chain(:active_customers).and_return(@customers = mock([Customer]))
  end

  it "should retrieve a list of all customers" do
    get :index
    response.should be_success
    Customer.should_receive(:active_customers).and_return(@customers)
  end
end

This is not working and is failing, saying that Customer expects active_customers but received it 0 times. In my actual controller for the Index action I have @customers = Customer.active_customers. What am I missing to get this to work? Sadly, I'm finding that it's easier to just write the code than it is to think of a test/spec and write that since I know what the spec is describing, just not how to tell RSpec what I want to do.

+1  A: 

I think there's some confusion when it comes to stubs and message expectations. Message expectations are basically stubs, where you can set the desired canned response, but they also test for the call to be made by the code being tested. In contrast stubs are just canned responses to the method calls. But don't mix a stub with a message expectation on the same method and test or bad things will happen...

Back to your question, there are two things (or more?) that require spec'ing here:

  1. That the CustomersController calls Customer#active_customers when you do a get on index. Doesn't really matter what Customer#active_customers returns in this spec.
  2. That the active_customers named_scope does in fact return customers where the active field is true.

I think that you are trying to do number 1. If so, remove the whole stub and simply set the message expectation in your test:

describe CustomersController do
  it "should be successful and call Customer#active_customers" do
    Customer.should_receive(:active_customers)
    get :index
    response.should be_success
  end
end

In the above spec you are not testing what it returns. That's OK since that is the intent of the spec (although your spec is too close to implementation as opposed to behavior, but that's a different topic). If you want the call to active_customers to return something in particular, go ahead and add .and_returns(@whatever) to that message expectation. The other part of the story is to test that active_customers works as expected (ie: a model spec that makes the actual call to the DB).

hgimenez
Thank you for your explanation (I was following an RSpec tutorial and that's how it showed to do the actions, with the mock/stub). However, when I try the code you post I get the same error: <Customer(id: integer, first_name: string, last_name: string, address: text, city: string, state: string, zip_code: string, active: boolean, created_at: datetime, updated_at: datetime) (class)> expected :active_customers with (any args) once, but received it 0 times. This is despite having the call to Customer.active_customers in CustomersController#index.
Wayne M
Sorry, my bad. The call to `get :index` should come after the message expectation. I am correcting the answer so that the expectation is the first line in the spec.
hgimenez
Okay that is working now. I'm used to the normal test process where get :index comes BEFORE what you're testing, so that seems to be the issue.. it's reversed with RSpec. I guess I need more practice with RSPec. Thanks!
Wayne M
It *does* feel reversed, I agree. What you are after is called a "Test spy", where you test expectations in past tense (Customer.should have_received(:active_customers) at the bottom of your spec. RSpec's mocking framework won't support it, but RR, not-a-mock, and jferry's fork of mocka do support them and are RSpec compatible. Read up on it here: http://xunitpatterns.com/Test%20Spy.html, http://robots.thoughtbot.com/post/159805295/spy-vs-spy and http://rspec.info/documentation/mocks/other_frameworks.html or how to use in RSpec...
hgimenez
+1  A: 

You should have the array around the mock if you want to test that you receive back an array of Customer records like so:

Customer.stub_chain(:active_customers).and_return(@customers = [mock(Customer)])
railsninja
A: 

stub_chain has worked the best for me.

I have a controller calling

ExerciseLog.this_user(current_user).past.all

And I'm able to stub that like this

ExerciseLog.stub_chain(:this_user,:past).and_return(@exercise_logs = [mock(ExerciseLog),mock(ExerciseLog)])
jspooner
http://apidock.com/rspec/Spec/Mocks/Methods/stub_chain
jspooner