Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

expected: 1 time with any arguments received: 0 times with any argument

I'm testing the index action for my ProjectsController.

I'm using the will_paginate gem, and am trying to write an RSpec test that ensures the paginate method is called on the current user's projects when they projects_path.

The result I'm getting, however, isn't what I expected and I can't figure out why.

result

Failure/Error: expect(user.projects).to receive(:paginate)
       (#<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Project:0x00000004719ef0>).paginate(any args)
           expected: 1 time with any arguments
           received: 0 times with any arguments
     # ./spec/requests/project_pages_spec.rb:82:in `block (3 levels) in <top (required)>'

projects#index

def index
    if params[:search]
      @projects = current_user.projects.search(params[:search]).paginate(:page => params[:page], :per_page => 13)
    else
      @projects = current_user.projects.paginate(:page => params[:page], :per_page => 13)
    end 
end 

index test

describe "index" do

  describe "pagination" do

    let(:user) { FactoryGirl.create(:user) }
    let(:client) { FactoryGirl.create(:client) }

    before do
      capybara_sign_in(user)
      @project = Project.create(name: "Blog", fee: 550, client_id: client.id)
    end 

    it "should call the paginate method" do
      expect(user.projects).to receive(:paginate)
      visit projects_path
    end
  end
end

Note that I haven't finished writing the tests, so please omit any comments re: drying up the code etc.

Thanks for the help guys/gals! :)

like image 424
bitfizzy Avatar asked Aug 25 '15 16:08

bitfizzy


2 Answers

The reason it is failing is that user in your spec file is not the same as current_user in your controller. So while current_user is receiving paginate your spec's user never is. Couple of ways you could solve it.

You could do this:

expect_any_instance_of(User).to receive(:paginate)

Drawback here is that you're testing for any instance, not specifically your current_user instance. Whether that's a problem or not is up to you :)

The other way would be to stub current_user:

controller.stub(:current_user).and_return(user)
expect(user.projects).to receive(:paginate)

Upside is you're testing exactly the same user. Downside is stubbing current_user might introduce other issues.

like image 111
Philip Hallstrom Avatar answered Sep 24 '22 03:09

Philip Hallstrom


Part of the pain you are feeling is that you are testing implementation instead of behavior. In general, the behavior of a web request can be described in a few words: it accepts input parameters and returns a response. To test the behavior, test the contents of the response.

In your case, the app will behave one way when params[:search] is present, and another way when it is missing. So write one test with params[:search] set to an appropriate value, and one with it missing, and test that the response is what is expected in each case. You don't need to check the entire response, just enough to verify that the right data is returned.

To make the tests return the right responses, use a different set of data for each test. The model will return the expected rows, and voilà!, passing tests.

Now this may seem like more work than mocking and stubbing, and it will take more time to run, but it is the right way to test a request. You want to check the whole stack, not just the method call in the controller. The resulting tests will be true integration tests, and will be less brittle since they are not coupled to the implementation.

like image 42
zetetic Avatar answered Sep 27 '22 03:09

zetetic