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! :)
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.
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.
Related
Currently, my Rails app only displays activated user accounts in the search results. It also only allows people to navigate to a profile page if that profile has been activated. To do these things, users_controller.rb is configured like this:
def index
#users = User.where(activated: true).paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
redirect_to root_url and return unless #user.activated?
end
I am wondering how to use an integration test to check this behavior. My current test is this:
test "only show profiles of activated users" do
log_in_as(#admin)
get users_path
assert_template 'users/index'
assert_select 'div.pagination'
first_page_of_users = User.paginate(page: 1)
first_page_of_users.each do |user|
assert_equal true, user.activated?
end
end
I have also modified /fixtures/users.yml to include a user that has not activated his profile:
non:
name: Non Activated
email: nonactivated#example.gov
password_digest: <%= User.digest('password') %>
activated: false
When I run rake test, I get this error:
FAIL["test_only_show_profiles_of_activated_users", UsersIndexTest, 1.271917]
test_only_show_profiles_of_activated_users#UsersIndexTest (1.27s)
Expected: true
Actual: false
test/integration/users_index_test.rb:40:in `block (2 levels) in <class:UsersIndexTest>'
test/integration/users_index_test.rb:39:in `block in <class:UsersIndexTest>'
Can anyone help me understand why the test is able to detect profiles of non-activated users?
Firstly, let me state that my struggle with this same exercise landed me here, so I’m obliged for your post. 6 months late, but perhaps my response might still provide some insight.
After a good deal of effort, this much is clear to me:
User.paginate is semi-redundant to the actual code in users_controller.rb, and furthermore, is actually causing your immediate problem -- namely, in your test, User.paginate doesn’t include the where filter
Instantiating User.paginate directly in the test and using it to evaluate its own members doesn’t make sense - in this case, the test is only testing itself and not the app
In order to actually test the app, one must access the #users instance variable in users_controller.rb, e.g., my_test_users = assigns(:users)
In order to be thorough and test for presence of the non-activated user pre-defined in the users.yml fixture, we should check all pages, which presents the problem of how to determine total number of pages -- will_paginate provides this: my_test_users.total_pages
The final insight that helped with this exercise was to really begin understanding what makes up a REST-ful implementation, namely, how controller-action mapping works with named routes and parameterization, e.g., get users_path, page: 1 -- this obviously calls the users controller, and if we recall that will_paginate takes a :page argument --> User.paginate(page: params[:page]) -- we see that this argument is provided by the named path parameter above
So hopefully this much is enough to put the pieces together for a complete integration test.
(The test to see if non-activated user is redirected to root_url upon attempting to navigate to user profile is much more straightforward)
For what it’s worth, so far, this exercise has proven to be the most challenging and rewarding of the tutorial.
I think for your case the better way of access you can use default scope
default_scope where(:published => true)
We have an API which we returns some structured JSON data. Sites have_many :controllers, and Controllers belong_to :site
For the test, we have to create a mock site and controller, which is achieved in all our other feature test files exactly like I have it listed below in the before(:each) do block.
Test:
describe Api::V2::SitesController, :type => :controller do
render_views
before(:each) do
basic_auth_and_skip_hmac
#site = FactoryGirl.create(:site)
#user_site = FactoryGirl.create(:user_site, user: #user, site: #site)
#controller = FactoryGirl.create(:controller, site: #site)
end
it 'List all sites' do
get :index, format: :json
puts response.body
expect(response.body).to include("Site 1")
expect(response.body).to include("Controller 1")
end
end
But the response for this controller test is unexpected:
Api::V2::SitesController
List all sites (FAILED - 1)
Failures:
1) Api::V2::SitesController List all sites
Failure/Error: get :index, format: :json
NoMethodError:
undefined method `response_body=' for #<Controller:0x0000010db0c2d8>
Why do you even care about response_body for the Controller object Rspec? It clearly states at the top that we're describing the SitesController!
Removing the creation of the controller object and the matching expectation at the bottom of the file makes the test pass as expected:
Finished in 0.60435 seconds (files took 5.38 seconds to load)
1 example, 0 failures
But I'm not really testing everything I set out to test because my JSON includes:
"controllers":[]
Which technically cannot happen in our application. The controller is the most important unit to measure for us, so returning a JSON response with valid site information but no controllers would be pointless.
As shown in the discussion above with Mike - it turns out that "#controller" is special to Ruby.
And I happen to work in probably the only industry where this becomes a naming conflict. We manage a service for irrigation controllers, so the word is always messing with my head - am I talking about MVC controller or the actual device?
It's been a burden that probably no one else will ever encounter as it's just not a variable you would ever think to use.
In summary - don't ever call #controller, pretty much anywhere.
I am new to ruby on rails. I am getting an undefined method error when I run rspec on comment_spec.rb
1) after_save calls 'Post#update_rank' after save
Failure/Error: request.env["HTTP_REFERER"] = '/'
NameError:
undefined local variable or method `request' for #<RSpec::ExampleGroups::AfterSave:0x007fa866ead8d0>
# ./spec/models/vote_spec.rb:45:in `block (2 levels) in <top (required)>'
This is my spec:
require 'rails_helper'
describe Vote do
....
describe 'after_save' do
it "calls 'Post#update_rank' after save" do
request.env["HTTP_REFERER"] = '/'
#user = create(:user)
#post = create(:post, user: #user)
sign_in #user
vote = Vote.new(value:1, post: post)
expect(post). to receive(:update_rank)
vote.save
end
end
Any help that you would have would be greatly appreciated...
I was following the apirails book tutorial chapter 3 here
http://apionrails.icalialabs.com/book/chapter_three
I was receiving the same error and DrPositron's solution worked for me, all green again. Just needed to add ":type => :controller" on my block like so:
describe Api::V1::UsersController, :type => :controller do
end
Hope this helps someone
OK here's the deal.
Vote is a model, i suppose.
You are writing a test for that model.
There's a difference between model tests ("the domain logic is doing what its supposed to") and feature/integration tests ("the application is behaving the way its supposed to").
The request variable is associated with feature or controller tests.
So what's wrong?
You are not logging in users in model tests, just check if the update_rank method is being called on save, thats it.
No user-interaction jazz in model tests.
Hope that helps!
Cheers
Jan
So Louis, just to expand on Jan's response:
You appear to be writing a model spec. The purpose of a model spec is simply to test how your model classes work, and that behavior is testable without having to pay any attention to the application logic around signing in, making "requests" to particular controllers, or visiting particular pages.
You're essentially just testing a couple related Ruby classes. For this, we don't need to think about the whole app -- just the classes we're testing.
As a consequence, RSpec doesn't make certain methods available in the spec/models directory -- you're not supposed to think about requests or authentication in these tests.
It looks like your test is simply designed to make sure that when you create a vote for a post, it updates that post's rank (or, specifically, call's that post's update_rank method). To do that, you don't need to create a user, or sign a user in, or pay any attention to the request (what request would we be referring to? We're just testing this as if in Rails console, with no HTTP request involved).
So you could basically remove the first four lines of your test -- apart from the line creating your post, and the post's user if it's necessary (if the post model validates the presence of a user). Don't sign a user in -- we're just testing a Ruby class. There's no concept of a website to sign into in this test.
Then, as a last thing to take care of to get your spec to pass, make sure to refer to the post you create by the right name. Right now, you're creating a post and assigning it to the #post variable, but then you're referring to just post later on. post doesn't exist; just #post. You'll have to pick one variable name and stick with it.
Also, if you are using RSpec 3, file type inference is now disabled by default and must be opted in as described here. If you're new to RSpec, a quick overview of the canonical directory structure is here.
For example, for a controller spec for RelationshipsController, insert , :type => :controller as such:
describe RelationshipsController, :type => :controller do
#spec
end
Having been inspired by Sandi Metz's approach to writing tests (http://www.confreaks.com/videos/2452-railsconf2013-the-magic-tricks-of-testing), I am trying to refactor a test for a Rails controller to assert that it is sending a command message properly.
Here are the relevant parts of the Application:
class DealsController < ApplicationController
def index
if params[:reset]
deal_filter.reset
...
class ApplicationController
def deal_filter
...
#deal_filter ||= DealFilter.new(args)
end
...
class DealFilter
def reset
...do work...
end
...
And here is the rspec test:
describe DealsController do
it "should send 'reset' to the deal_filter" do
df = instance_double("DealFilter")
get :index, reset: "true"
expect(df).to receive(:reset)
end
end
The test results that keep coming back are:
1) DealsController GET index for any user params contain 'reset' should send 'reset' to the deal_filter
Failure/Error: expect(df).to receive(:reset)
(Double "DealFilter (instance)").reset(any args)
expected: 1 time with any arguments
received: 0 times with any arguments
I have already confirmed that the reset param is being sent through the test and that the controller is following the appropriate path, yet the test continues to fail.
Can anyone suggest a possible reason for the failure or resources for further study? I am relatively new to object oriented thinking and using mocks with Rspec. Could it be that I have misunderstood the role of doubles?
Thanks for your time!
You need to make sure your double gets used. I think the best way to do that here is to stub the deal_filter method to return the double.
I addition I would isolate the expection, so that it's the only thing in the it block. This will make it easier to add more expections without duplication the setup logic.
describe DealsController do
let(:df) { instance_double("DealFilter") }
before do
allow(controller).to receive(:deal_filter).and_return(df)
get :index, reset: "true"
end
it "should send 'reset' to the deal_filter" do
expect(df).to have_received(:reset)
end
end
I think you're expecting your instance_double to be used automatically somewhere within the index action. That's not how doubles work. You can create a double and use it for things, but your code in the controller doesn't (and shouldn't) know anything about that double and so won't ever call anything on it.
For an example of how an instance double can actually be used see this documentation.
Another issue with your expectation is that you're not setting it early enough. When you expect an object to receive a method call there needs to be something that happens after that which would invoke that method. In your example the expectation to receive :reset is the very last line of your example.
I'd recommend reading up on how other people have tested controllers with rspec as a good starting place.
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.
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:
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.
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).
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)])
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)])