Testing a create action with rspec and factory is failing - ruby-on-rails

I'm trying to test a post/create controller action in my rails 4 app, but it's failing.
Here is the scaffold generated test:
it "assigns a newly created project as #project" do
post :create, {:project => valid_attributes}, valid_session
assigns(:project).should be_a(Project)
assigns(:project).should
end
Here is the code after I refactored to use it with FactoryGirl
it "assigns a newly created project as #project" do
project = FactoryGirl.create(:post)
assigns(project).should be_a(Project)
assigns(project).should be_persisted
end
So it's failing:
Failure/Error: assigns(project).should be_a(Project)
expected nil to be a kind of Project(id: integer, title: string, description: text, created_at: datetime, updated_at: datetime)
I don't know why projectis returning nil in assigns method. I already inspected it to make sure it's returning a proper Project.
Btw, here is my project factory:
factory :project do
title "MyString"
description "MyText"
users {[FactoryGirl.create(:user)]}
end
Thanks in advance!

The assigns(project) method call returns the value of the #project instance variable in Rails. Invoking the FactoryGirl method has no effect on this variable, so it evaluates to nil.

Related

Set the value of attr_accessor from inside Rspec test and FactoryGirl

I am using this Rails gem to add captcha question to my comment form. https://github.com/kiskolabs/humanizer
I am trying to bypass the captcha inside my Rspec test. According to documentation, this can be done by adding these code into my model
attr_accessor :bypass_humanizer
require_human_on :create, :unless => :bypass_humanizer
Now, I just need to know how to set the 'bypass_humanizer' attribute inside my test. This is what I have inside my test at the moment :
it 'saves the new comment in the database' do
comment = FactoryGirl.attributes_for(:comment, :bypass_humanizer => true)
expect {
post :create, blog_id: #blog, comment: comment
}.to change(Comment, :count).by(1)
end
Any help is appreciated.
-------- UPDATE --------
Funny enough that I can do this in my another Rspec test and test will pass
hello = create(:comment, body: 'Hello World', bypass_humanizer: true)
So I just need to find something similar for FactoryGirl attributes_for
You could try adding bypass_humanizer true in your FactoryGirl factory, so that every time you generate an instance of that class bypass_humanizer is set to true.
In alternative, you could write the test in this way:
it 'saves the new comment in the database' do
comment = FactoryGirl.attributes_for(:comment)
comment.merge!(:bypass_humanizer => true)
expect {
post :create, blog_id: #blog, comment: comment
}.to change(Comment, :count).by(1)
end
You need to make sure that 'bypass_humanizer' is allowed in your controller and model.
If you're using strong_parameters, make sure that it's in the whitelist in the controller.
If you're using attr_accessible, make sure that it's listed appropriately.

Meaning for attributes_for in FactoryGirl and Rspec Testing

Looking through a tutorial on controller testing the author gives an example of an rspec test testing a controller action. My question is, why did they use the method attributes_for over build? There is no clear explanation why attributes_for is used besides that it returns a hash of values.
it "redirects to the home page upon save" do
post :create, contact: Factory.attributes_for(:contact)
response.should redirect_to root_url
end
The tutorial link is found here: http://everydayrails.com/2012/04/07/testing-series-rspec-controllers.html The example is found at the beginning topic section Controller testing basics
attributes_for will return a hash, whereas build will return a non persisted object.
Given the following factory:
FactoryGirl.define do
factory :user do
name 'John Doe'
end
end
Here is the result of build:
FactoryGirl.build :user
=> #<User id: nil, name: "John Doe", created_at: nil, updated_at: nil>
and the result of attributes_for
FactoryGirl.attributes_for :user
=> {:name=>"John Doe"}
I find attributes_for very helpful for my functional test, as I can do things like the following to create a user:
post :create, user: FactoryGirl.attributes_for(:user)
When using build, we would have to manually create a hash of attributes from the user instance and pass it to the post method, such as:
u = FactoryGirl.build :user
post :create, user: u.attributes # This is actually different as it includes all the attributes, in that case updated_at & created_at
I usually use build & create when I directly want objects and not an attributes hash
Let me know if you need more details

error in rspec but works fine in development (can't mass-assign protected attributes)

I recently started using rspec and factory_girl and I'm working on a basic control spec for my create action in my projects controller.
So I have this as a before filter:
before :each do
#project = FactoryGirl.create(:project)
#user = FactoryGirl.create(:user, first_name: "Jim", last_name: "Smith", username: "jsmith")
session[:user_id] = #user.id # this maintains the session for the user created in the previous linew
end
My project expects to have a user associated with it.
So in the create spec, I have this:
describe 'POST #create' do
attribute_merge = FactoryGirl.attributes_for(:project).merge(FactoryGirl.attributes_for(:user))
context "with valid attributes" do
it "creates a new project" do
expect{
post :create, project: attribute_merge
}.to change(Project,:count).by(1)
end
end
end
So what I'm trying to do is pass the project attributes hash along with the user attributes hash because a project requires at least one user in order to be created. Now, the error I get is:
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: first_name, last_name....
I should add that my create action works perfectly in development and I do have attr_accessible :first_name, :last_name, :username,... in my user.rb file
It's failing because your passing the actual attributes for the user to the project, instead of just a reference to the user.
Try
post :create, project: FactoryGirl.build(:project, user: user).attributes

Testing "Post create" with Rspec

I am trying to test a "Post create" action with Rspec. The code is as follows:
def valid_attributes
{
:zone => Flymgr::Zone.new(:countries => Flymgr::ZoneCountry.first,
:name => 'USA',
:description => 'USA Flight',
:zipcodes => ''),
:price => '100.00',
:class => 'first',
}
end
def valid_session
{}
end
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:admin]
admin = FactoryGirl.create(:admin)
sign_in admin
end
describe "POST create" do
describe "with valid params" do
it "creates a new Flymgr::Rule" do
expect {
post :create, {:Flymgr_rule => valid_attributes}
}.to change(Flymgr::Rule, :count).by(1)
end
One of the required attributes for the form is a 'zone', this is a dropdown box and the options for the dropdown are created with a different form. I do not know how to create an form entry using Rspec. As you can see, I have tried to call a method from a different controller Flymgr::Zone.new. I don't think this is working and it is breaking my test.
Can anyone advise on the best way to do this? Perhaps I should be using FactoryGirl to create a zone and rule entry?
your request parameter hash has an object as value of :zone, when you post it will just be 'to_s'-ed, which is unlikely what you want.
In general the best practice is to use factory girl to build your objects and use the attributes_for strategy to parameterize its attributes for the post request:
What is the proper way to test 'create' controller actions?
Your question is suggesting that the association is a belong_to so you just need to post an id. Be aware that at present, FactoryGirl does not create any attributes for the associations. If your factory definition for rule takes care of the zone association, you can use this workaround:
FactoryGirl.build(:flymgr_rule).attributes
to also include a zone_id but, then you need to exclude the unwanted params.
("id", "created_at", "updated_at", etc).
So you may be better off explicitly insert the params hash info for zone the way you see it in a valid post request.
Read this thread on factorygirl attributes and associations:
https://github.com/thoughtbot/factory_girl/issues/359
As the guide points out:
# Returns a hash of attributes that can be used to build a User instance
attrs = FactoryGirl.attributes_for(:user)

Stupefied by RSpec

I'm sorry, but this is beginning to feel like kicking myself in the head. I'm completely baffled by RSpec. Have watched video after video, read tutorial after tutorial, and still I'm just stuck on square one.
=== here is what I'm working with
http://github.com/fudgestudios/bort/tree/master
=== Errors
F
1)
NoMethodError in 'bidding on an item should work'
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.new_record?
spec/controllers/auction_controller_spec.rb:16:
spec/controllers/auction_controller_spec.rb:6:
Finished in 0.067139 seconds
1 example, 1 failure
=== here is my controller action
def bid
#bid = Bid.new(params[:bid])
#bid.save
end
=== here is my test
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "bidding on an item" do
controller_name :items
before(:each) do
#user = mock_user
stub!(:current_user).and_return(#user)
end
it "should work" do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
assigns[:bid].should be_new_record
end
end
=== spec_helper
http://github.com/fudgestudios/bort/tree/master/spec/spec_helper.rb
It's very disheartening to wake for work at 3 a.m. and accomplish nothing for the day. Please understand.
You've got a couple of things backwards in before(:each). Seeing as the example is specifying that the post should increase the count by 1, you're dealing with real records and there is no reason for stubbing anything at all. Also, at this point, since there is only one example, there is no reason to have a before block. I'd do it this way:
describe ItemsController, "bidding on an item" do
fixtures :users
it "should create a new Bid" do
login_as :quentin
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
end
One thing I'd recommend is creating these things VERY granularly for now until you understand them better. Start with the expectation (post should change bid count), run the spec and let the failure message guide you to add whatever else you need in the spec or in the code.
Jesse,
It'll still pass if you comment out the 2nd two lines of before(:each), which are having no impact on the "should create a new Bid" example.
The lambda keyword creates an arbitrary block of code that is not executed when you define it, but is actually an object you can assign to a variable and execute later:
the_post = lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end
At this point that code is not executed, but we can refer to it with the 'the_post' variable. Now we can send it 'should', followed by 'change ...', like this:
the_post.should change(Bid, :count).by(1)
When this line is executed, a few things happen. The material to the right of 'should' is evaluated first, initializing an rspec matcher object with some instructions. That matcher is the argument to 'should' - the equivalent of this:
matcher = change(Bid, :count).by(1)
the_post.should(matcher)
The 'should' method is called on the_post, which is the code block (that still hasn't been executed). Under the hood, the 'should' method passes self (the_post) to the matcher, so the matcher now has everything it needs to evaluate the example.
The matcher calls Bid.count and records the value. Then it executes the block (the_post), and then calls Bid.count a second time and compares it to the value it recorded earlier. In this case, since we're looking for Bid.count to change by 1 (positive is implicit here - increase by 1), if that's what happens the matcher stays silent and the example passes.
If the values are the same, or differ by some value other than 1, the example will fail. You can see that work if you change the expectation to by(2) instead of by(1).
HTH,
David
EDIT: you shouldn't expect Bid.count to increment when using a mock object. Mantra I forgot: caffeine before code.
Just commenting out the lines, for now, so the original is still there.
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "POST to bid_controller" do
controller_name :items
before(:each) do
##bid = mock_model(Bid) # create a new mock model so we can verify the appropriate things
#Bid.stub!(:new).and_return(#bid) # stub the new class method on Bid to return our mock rather than a new ActiveRecord object.
# this separates our controller spec entirely from the database.
end
it "should create a new Bid" do
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
# ... more specs
end
Try to write as small specs as possible, write your setences in such a way as to make it obvious what you should be verifying in that spec. For example, how I changed yours from it "should work" to it "should create a new Bid". If there's more to that controller, write a new spec
for each small piece of functionality.
If you do end up needing mock users, there are some helpers for restful_authentication that make it easier. First create a user fixture in
RAILS_ROOT/spec/fixtures/users.yml, like this:
quentin:
login: quentin
email: quentin#example.com
salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
created_at: <%= 5.days.ago.to_s :db %>
activation_code: 8f24789ae988411ccf33ab0c30fe9106fab32e9b
activated_at: <%= 5.days.ago.to_s :db %>
name: "Quentin"
Then in your spec you will be able to write the following and have your current_user method and all the other parts of restul_authentication
behave as you would expect them to at runtime.
login_as :quentin
# .... the rest of your spec
As an example of a few more specs I might add as a couple more examples:
def do_post
# extracting the method under test, so I don't repeat myself
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end
it "should create a new Bid" do
lambda do
do_post
end.should change(Bid, :count).by(1)
end
it "should assign the Bid to the proper auction" do
#bid.should_receive(:auction_id=).with(1) # test this, I believe doing Bid.new(params[:bid]) sets the id directly not sets the model
do_post
end
it "should assign the Bid the proper points" do
#bid.should_receive(:point=).with(1)
do_post
end
While I don't quite understand what's going on. (with stubs and the lambda)....
for
def bid
#bid = Bid.new params[:bid]
#bid.save
end
The following passes !!
require File.dirname(__FILE__) + '/../spec_helper'
include ApplicationHelper
include UsersHelper
include AuthenticatedTestHelper
describe "bidding on an item" do
controller_name :items
fixtures :users
before(:each) do
#user = login_as :quentin
#bid = mock_model(Bid) # create a new mock model so we can verify the appropriate things
#bid.stub!(:new).and_return(#bid) # stub the new class method on Bid to return our mock rather than a new ActiveRecord object.
#Bid.stub!(:save).and_return(true)# this separates our controller spec entirely from the database.
end
it "should create a new Bid" do
lambda do
post 'bid', :bid => { :auction_id => 1, :user_id => #user.id, :point => 1 }
end.should change(Bid, :count).by(1)
end
end

Resources