Why is Rspec using the wrong object for a controller test? - ruby-on-rails

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.

Related

Assigns has been extracted to a gem. Use gem 'rails-controller-testing'. Is there an alternative for the gem?

I am writing some tests for a controller tasks, the index action, which has an instance variable #tasks with all the tasks (Task.all).
If I follow the official documentation:
RSpec.describe TeamsController do
describe "GET index" do
it "assigns #teams" do
team = Team.create
get :index
expect(assigns(:teams)).to eq([team])
end
it "renders the index template" do
get :index
expect(response).to render_template("index")
end
end
end
The assigns method is moved to the gem file 'rails-controller-testing'.
I have two questions:
1 - How can I achieve the same as expect(assigns(:teams)).to eq([team]). I guess I am asking, how can I check if I have an instance variable in the index action with values [team]
2 - If this method was moved to the gem, I read in the Github issues, that the reason is: You shouldn't test it there, controller should just test response, cookies etc. But I am confuse, since in relish you can test the instance variable. Should I test it there or not? If not, where? In my views/index_spec.rb, testing if I have all the teams?
3 - Alternative: Since TeamsController is a normal class, should I create a spec in the spec/models/folder spec/models/tasks_controller.rb and there test if the method index has the instance variable #teams with the content that I want?
Thanks
The whole idea is that instead of poking inside your controller and testing its internal variables is flawed you should instead test your controllers by testing the output.
In RSpec you can do this with request and feature specs.
# config/specs/features/teams_spec.html
RSpec.feature 'Teams' do
scenario 'when a user views the teams' do
Team.create(name: 'Team Rocket')
visit '/teams'
expect(page).to have_content 'Team Rocket'
end
end
# config/specs/requests/teams_spec.html
RSpec.describe 'Teams', type: :request do
describe 'GET /teams.json' do
it "includes the team" do
team = Team.create(name: 'Team Rocket')
get teams_path(format: :json)
expect(parsed_response.first['name']).to eq 'Team Rocket'
end
end
describe 'GET /teams' do
it "includes the team" do
team = Team.create(name: 'Team Rocket')
get teams_path
expect(page).to have_content 'Team Rocket'
end
end
end
The key difference is that feature specs test the app from a user story POV by driving a browser simulator while request specs are lighter weight and you just test against the raw response.
1 - How can I achieve the same as expect(assigns(:teams)).to
eq([team]). I guess I am asking, how can I check if I have an instance
variable in the index action with values [team]
Either use the assigns gem for legacy compatiblity or test the rendered output.
2 - If this method was moved to the gem, I read in the Github issues,
that the reason is: You shouldn't test it there, controller should
just test response, cookies etc. But I am confuse, since in relish you
can test the instance variable. Should I test it there or not? If not,
where? In my views/index_spec.rb, testing if I have all the teams?
If by Relish you mean RSpec, then its been taking a while for RSpec-rails to catch up to the state-of-art in Rails testing. But the same still applies. The offical recommendation of the RSpec team is to not use assigns and faze out controller specs in favor of request specs. View specs are not really relevant here - they are used if you want to test complex views in isolation.
3 - Alternative: Since TeamsController is a normal class, should I
create a spec in the spec/models/folder
spec/models/tasks_controller.rb and there test if the method index has
the instance variable #teams with the content that I want?
Just no. Controllers are not just normal classes. You can't just instantiate a controller with MyController.new, thats why controller tests have all that stubbing in place.

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! :)
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.

undefined local variable or method `request'

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

rspec testing for content

I'm using Rspec to test the contents of a view in my Controller spec. I'm trying to test that all Product entries have their descriptions displayed on the page.
describe StoreController do
render_views
describe "GET 'index'" do
before(:each) do
get :index
end
it "should display the product list" do
Product.all.each do |product|
response.should have_selector("p", :content => product.description)
end
end
end
end
This doesn't seem to work, however, as the test passes regardless of what's in the view. Still very new to Rails, so it's probable that I'm doing something completely wrong here. How can I make the code test for the presence of each product description in the StoreController index view?
Personally I wouldn't test the contents of the view in the controller. I'd just test the outgoing output of the controller actions and any support methods. I'd put view related tests into the view specs.
If you look in the ones that are generated by rails you should see some examples of how to assert content there.
I figured it out. The problem was that my test database was not populated with any Products, so the each block was never executing. Fixed it by using Factory Girl to make sure there was data in the test database beforehand.

Stubbing named_scope in an RSpec Controller

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)])

Resources