`Assigns` isn't working in my integration test - ruby-on-rails

I'm trying to write a test following the suggestion on https://stackoverflow.com/a/17002140/4499505.
My simplified test:
test "test" do
log_in_as(#user)
get users_path
assert_template 'users/index'
assigns[:users].each do
assert_select 'a[href=?]', users_path(user)
end
end
The error result:
NoMethodError: undefined method `each' for nil:NilClass
The controller method:
def index
#users_grid = initialize_grid(User.where(verified: true),
per_page: 15,
order: 'users.username',
order_direction: 'desc')
end
Apparantly assigns[:users] is empty even though there are users in the fixtures file. What am I doing wrong? I understand the assigns[:users] should assign the exact same users as shown on users/index, which is exactly what I want.

Well, your correct code is :
test "test" do
log_in_as(#user)
get users_path
assert_template 'users/index'
# in your controller you have the instance var as
# #users_gird, not #users.
assigns[:users_grid].each do
assert_select 'a[href=?]', users_path(#user)
end
end
assigns is a hash, accessible within Rails tests, containing all the instance variables that would be available to a view at this point. It’s also an accessor that allows you to look up an attribute with a symbol (since, historically, the assigns hash’s keys are all strings). In other words, assigns(:contact) is the same as assigns["contact"].

Notice in the answer you've linked to that the symbol in assigns[] matches that of the instance variable from the controller action.
In your case you're assigning the instance variable #users_grid but attempting to iterate through assigns[:users]. You probably just want to change the latter to assigns[:users_grid]

Related

Passing a named route to a controller macro in RSpec

I'm trying to DRY up my RSpec examples by adding a few controller macros for frequently used tests. In this somewhat simplified example, I created a macro that simply tests whether getting the page results in a direct to another page:
def it_should_redirect(method, path)
it "#{method} should redirect to #{path}" do
get method
response.should redirect_to(path)
end
end
I'm trying to call it like so:
context "new user" do
it_should_redirect 'cancel', account_path
end
When I run the test I get an error saying that it doesn't recognize account_path:
undefined local variable or method `account_path' for ... (NameError)
I tried including Rails.application.routes.url_helpers per the guidance given in this SO thread on named routes in RSpec but still receive the same error.
How can I pass a named route as a parameter to a controller macro?
The url helpers included with config.include Rails.application.routes.url_helpers are valid only within examples (blocks set with it or specify). Within example group (context or describe) you cannot use it. Try to use symbols and send instead, something like
# macro should be defined as class method, use def self.method instead of def method
def self.it_should_redirect(method, path)
it "#{method} should redirect to #{path}" do
get method
response.should redirect_to(send(path))
end
end
context "new user" do
it_should_redirect 'cancel', :account_path
end
Don't forget to include url_helpers to config.
Or call the macro inside example:
def should_redirect(method, path)
get method
response.should redirect_to(path)
end
it { should_redirect 'cancel', account_path }

Rspec testing instance variables with user creation

I'm testing to make sure that a created user is assigned to my instance variable #user. I understand what get means, but I'm not sure what to write for the test. I'm returning with an argument error for a bad URI or URL. What's wrong with my test and how do I fix it?
it "checks #user variable assignment for creation" do
p = FactoryGirl.create(:user)
get :users
# I'm confused on what this line above means/does. What does the hash :users refer
#to
assigns[:user].should == [p]
end
The expected URI object or string error refers to get :users and the error is as follows
Failure/Error get :users
ArgumentError:
bad argument: (expected URI object or URI string)
I guess that what you want is
it "checks #user variable assignment for creation" do
p = FactoryGirl.create(:user)
get :show, id: p.id
assigns(:user).should == p
end
The line you were not sure about checks that content of the assigned variable (#user) in the show view of the user p, is equal to the p user you just created more information there
what action are you trying to test? usually, for creation, you need to test that the controller's "create" action creates a user and assigns an #user variable
I would test it this way:
describe 'POST create' do
it 'creates a user' do
params = {:user => {:name => 'xxx', :lastname => 'yyy'}}
User.should_receive(:create).with(params)
post :create
end
it 'assigns the user to an #user instance variable' do
user = mock(:user)
User.stub!(:create => user)
post :create
assigns(:user).should == user
end
end
notice that I stub/mock all user methods, since you are testing a controller you don't have to really create the user, you only test that the controller calls the desired method, the user creation is tested inside the User model spec
also, I made 2 tests (you should test only 1 thing on each it block if possible, first it test that the controller creates a user, then I test that the controller assigns the variable
I'm assuming your controller is something like this:
controller...
def create
#user = User.create(params[:user])
end
which is TOO simple, I guess you have more code and you should test that code too (validations, redirects, flash messages, etc)

Set current_user in test

I have a test that looks like this:
test "should get create" do
current_user = FactoryGirl.build(:user, email: 'not_saved_email#example.com')
assert_difference('Inquiry.count') do
post :create, FactoryGirl.build(:inquiry)
end
assert_not_nil assigns(:inquiry)
assert_response :redirect
end
That's testing this part of the controller:
def create
#inquiry = Inquiry.new(params[:inquiry])
#inquiry.user_id = current_user.id
if #inquiry.save
flash[:success] = "Inquiry Saved"
redirect_to root_path
else
render 'new'
end
end
and the factory:
FactoryGirl.define do
factory :inquiry do
product_id 2
description 'I have a question about....'
end
end
but I keep getting errors in my tests:
1) Error:
test_should_get_create(InquiriesControllerTest):
RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
What am I doing wrong? I need to set the current_user, and I believe I am in the test, but obviously, that's not working.
You didn't create current_user. It was initialized only in test block.
There are two differents ways to do it:
First, use devise test helpers. Something like that
let(:curr_user) { FactoryGirl.create(:user, ...attrs...) }
sign_in curr_user
devise doc
Second, you can stub current_user method in your controllers for test env
controller.stub(current_user: FactroryGirl.create(:user, ...attrs...))
And you should use FactoryGirld.create(...) instead of FactoryGirl.build(...), because you factory objects have to be persisted.(be saved in db and has id attribute not nil)
There are several things which come to mind:
FactoryGirl.build(:user, ...) returns unsaved instance of a user. I'd suggest to use Factory.create instead of it, because with unsaved instance there's no id and there's no way for (usually session based) current_user getter to load it from database. If you're using Devise, you should "sign in" user after creating it. This includes saving record in DB and putting reference to it into session. See devise wiki
Also, passing ActiveRecord object to create action like this looks weird to me:
post :create, FactoryGirl.build(:inquiry)
Maybe there's some rails magic in play which recognizes your intent, but I'd suggest doing it explicitly:
post :create, :inquiry => FactoryGirl.build(:inquiry).attributes
or better yet, decouple it from factory (DRY and aesthetic principles in test code differ from application code):
post :create, :inquiry => {product_id: '2', description: 'I have a question about....'}
This references product with id = 2, unless your DB doesn't have FK reference constraints, product instance may need to be present in DB before action fires.

assigns() method vs Binding - Rails

I'm new to Ruby on Rails world.
I noticed there is at least one way to access controller instance variables from a test case.
Indeed, suppose this test method:
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:products)
end
products is an instance variable contained within concerned controller. And for sure, test case has reference to this controller. So assigns() method uses it to inspect a hash of controller's instance variables and thus allows to access any precised object from any other files that previously called an action to this controller.
So I wonder two questions:
Why do not create a 'Binding' to controller instead of using assigns() method?
I imagine a version where it is possible to do:
test "should get index" do
get :index
assert_response :success
assert_not_nil #products
end
Wouldn't it be shorter and cleaner?
Binding is the mechanism that allows ERB file to access controller instance variables, as shows this links:
http://rrn.dk/rubys-erb-templating-system
What isn't this mechanism applicable to Test case ? Is assigns() method essential ?
If you brought over the binding, though, this might pass and should not
test "should get index" do
#fake_products = [1,2,3]
get :index
assert_response :success
assert_not_nil #fake_products
end
You wouldn't necessarily want all instance variables in your test to combine with instance variables in your controller. Assigns lets you 'scope' your assertions to just the controller instance variables.

Despite a controller error my tests still pass, while visiting the edit url correctly shows an error. Why is this so?

I have a Rails controller where I accidentally defined the 'edit' method inside the 'create' method.
My Controller with the error:
class UsersController < ApplicationController
...
def create
#user = User.new(params[:user])
...
def edit
#user = User.find(params[:id])
#title = "Edit user"
#check = "BORK" # something I added for testing the rendered output
end
end
end
An example test;
it "should have the right title" do
get :edit, :id => #user
response.should have_selector('title', :content => 'Edit user')
end
So when I run the tests (I use rspec) and output the response.body, the User edit.html.erb template is rendered correctly; all the instance variables are visible. So all the tests pass.
Visiting the 'edit' URL correctly shows an error; the template uses #user instance variable, and it's not set correctly. Of course correcting the controller fixes the error.
I don't understand why the tests pass at all and why, in the test, all the instance variable values are visible?
My instinct suggests this is a scope problem? Something about #user being an instance variable, and that in the tests it's set within the scope of the test, but in my controller it's within the scope of the inner 'edit' method? But how does the test even find the 'edit' method? In what scope does that inner 'edit' method exist?
You should realise that the def construct is as much executable code as an if statement. It's not invalid to put it inside another method, but it won't be run until the outer method is called:
>> class Foo
>> def foo
>> def bar
>> end
>> end
>> end
=> nil
>> Foo.instance_methods(false)
=> ["foo"]
>> Foo.new.foo
=> nil
>> Foo.instance_methods(false)
=> ["foo", "bar"]
The reason this was erroring in your browser was because Rails reloads all (most) of your classes each request. So, even if you had visited the create action - which would cause the edit method to be defined - the following request would have unloaded it again.
However in the test environment, if an earlier test had called the create action then that would have defined the edit action for future tests. You would see a different result if your tests were run in a different order (which in itself makes it a bad idea to rely on this).
Generally of course this isn't what you want at all, so just clear it up and move along :)

Resources