My controller spec fails because Factory Girl seems to be creating non-unique Users even though I sequence the User attributes that need to be unique.
The Errors
1) TopicsController POST #create when topic is invalid should render new
Failure/Error: let(:invalid_topic) {Factory.build :invalid_topic}
ActiveRecord::RecordInvalid:Validation failed: Email has already been taken, Username has already been taken
2) TopicsController POST #create when topic is valid should redirect to show
Failure/Error: let(:valid_topic) {Factory.build :topic}
ActiveRecord::RecordInvalid:
Validation failed: Email has already been taken, Username has already been taken
The Controller Spec (RSpec)
describe "POST #create" do
let(:valid_topic) {Factory.build :topic}
let(:invalid_topic) {Factory.build :invalid_topic}
context "when topic is invalid" do
it "should render new" do
post :create, :topic => invalid_topic
response.should render_template(:new)
end
end
context "when topic is valid" do
it "should redirect to show" do
post :create, :topic => valid_topic
response.should redirect_to(topic_path(assigns(:topic)))
end
end
end
The Factories
Factory.define :user do |f|
f.sequence(:username) { |n| "foo#{n}"}
f.password "password"
f.password_confirmation { |u| u.password}
f.sequence(:email) { |n| "foo#{n}#example.com"}
end
Factory.define :topic do |f|
f.name "test topic"
f.association :creator, :factory => :user
f.forum_id 1
end
Why isn't Factory Girl sequencing the User attributes when I use Factory.create :topic?
rake db:test:prepare seemed to fix the problem.
Not sure why, though. The schema hadn't been changed.
Please, consider using database_cleaner gem. One was designed specifically to fulfill the purpose of cleaning up database between test runs.
This post explains pretty much everything.
You should consider deleting all topics by hand in the end of the test. Of course, it is not number one solution but worked out for me real great.
after(:all) { Topic.delete_all }
Related
I am learning how to test on rails from this tutorial.
On one part of the tutorial, it shows how to write invalid_attribute test:
require 'rails_helper'
RSpec.describe ContactsController, type: :controller do
describe "POST #create" do
context "with valid attributes" do
it "create new contact" do
post :create, contact: attributes_for(:contact)
expect(Contact.count).to eq(1)
end
end
context "with invalid attributes" do
it "does not create new contact" do
post :create, contact: attributes_for(:invalid_contact)
expect(Contact.count).to eq(0)
end
end
end
end
I don't understand where :contact and :invalid_contact point to.
Does :contact points to Contact class? It seems like it from FactoryGirl's gh. If so, then how can I create :invalid_contact since there is no :invalid_contact class?
I have tried post :create, contact: attributes_for(:contact, :full_name => nil) but it still fails.
spec/factories/contacts.rb:
FactoryGirl.define do
factory :contact do
full_name { Faker::Name.name }
email { Faker::Internet.email }
phone_number { Faker::PhoneNumber.phone_number }
address { Faker::Address.street_address }
end
end
First test, with valid attributes pass. On model, there is presence validation validates_presence_of :full_name, :email, :phone_number, :address. What do I add in order to pass "with invalid attributes" test?
The factory will use the class with the same name. So your :contact factory will use the Contact class. You can create a new factory for the invalid contact by specifying the class to use.
factory :invalid_contact, class: Contact do
full_name nil
end
It's also possible to use traits to avoid having two different factories.
FactoryGirl.define do
factory :contact do
full_name { Faker::Name.name }
email { Faker::Internet.email }
phone_number { Faker::PhoneNumber.phone_number }
address { Faker::Address.street_address }
trait :invalid do
full_name nil
end
end
end
Then use it with attributes_for(:contact, :invalid)
The tutorial you link to says:
Following the spec above, write a spec that uses invalid attributes to
create a new contact. This spec should check that the contact is not
created.
So you need to figure out how to test for :invalid_contact using the example for :contact.
You can just add a let in your spec:
Use let to define a memoized helper method. The value will be cached
across multiple calls in the same example but not across examples.
Source: https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/helper-methods/let-and-let
Then your controller spec would look like this:
...
let(:invalid_contact) { create(:contact, name: nil) }
context "with invalid attributes" do
it "does not create new contact" do
post :create, contact: attributes_for(invalid_contact)
expect(Contact.count).to eq(0)
end
end
...
this way #post action params are picked up from invalid_contact
or as #fanta suggested in comments, you can add a trait to your factory. I prefer my method because other people looking at your code will know why invalid_contact should be invalid without looking at the :contacts factory
I run test, display error.
Failures:
1) ContractsController POST #create with valid attributes redirects to payment page
Failure/Error: #proposal = Proposal.find(params[:proposal_id])
ActiveRecord::RecordNotFound:
Couldn't find Proposal with 'id'=
require 'rails_helper'
describe ContractsController do
login_client
describe 'POST #create' do
let(:proposal) { create(:proposal) }
let(:contract) { create(:contract) }
context 'with valid attributes' do
it 'redirects to payment page' do
post :create, contract: attributes_for(:contract)
expect(response).to redirect_to payment_new_path
end
end
end
end
factory girls:
FactoryGirl.define do
factory :contract do
sequence(:title) { |n| "translation#{n}" }
amount 150
additional_information 'X' * 500
due_date { 21.days.from_now }
proposal
client
contractor
end
end
FactoryGirl.define do
factory :proposal do
description text
amount 150
project
user
end
end
I'm sure you're getting this error because of the use of FactoryGirl#attributes_for. Why? When you use the attributes_for method, it returns a non-persisted hash attribute for the resource. The thing about attributes_for however is that it doesn't honor association, which makes sense(in order to keep FactoryGirl ORM agnostic). A suggested way around this is to use or define a custom strategy:
build(:contract).attributes
Find more useful references here
I'm new to RSpec, and trying to get my head around using Factory Girl with associations in controller specs. The difficulty is:
it's necessary to use "attributes_for" in functional tests
attributes_for "elides any associations"
So if I have models like this:
class Brand < ActiveRecord::Base
belongs_to :org
validates :org, :presence => true
end
class Org < ActiveRecord::Base
has_many :brands
end
And a factory like this:
FactoryGirl.define do
factory :brand do
association :org
end
end
This controller spec fails:
describe BrandsController do
describe "POST create with valid params" do
it "creates a new brand" do
expect {
post :create, brand: attributes_for(:brand)
}.to change(Brand, :count).by(1)
end
end
end
(And if I comment out "validates :org, :presence => true" it passes)
There are a number of solutions suggested and I think I have been making simple errors which have meant that I have not been able to get any of them to work.
1) Changing the factory to org_id per a suggestion on this page failed a number of tests with "Validation failed: Org can't be blank"
FactoryGirl.define do
factory :brand do
org_id 1002
end
end
2) Using "symbolize_keys" looks promising. Here and here it is suggested to use code like this:
(FactoryGirl.build :position).attributes.symbolize_keys
I'm not sure how to apply this in my case. Below is a guess that doesn't work (giving the error No route matches {:controller=>"brands", :action=>"{:id=>nil, :name=>\"MyString\", :org_id=>1052, :include_in_menu=>false, :created_at=>nil, :updated_at=>nil}"}):
describe BrandsController do
describe "POST create with valid params" do
it "creates a new brand" do
expect {
post build(:brand).attributes.symbolize_keys
}.to change(Brand, :count).by(1)
end
end
end
Update
I almost got this working with Shioyama's answer below but got the error message:
Failure/Error: post :create, brand: build(:brand).attributes.symbolize_keys
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: id, created_at, updated_at
So following this question I changed it to:
post :create, brand: build(:brand).attributes.symbolize_keys.reject { |key, value| !Brand.attr_accessible[:default].collect { |attribute| attribute.to_sym }.include?(key) }
Which worked!
In your solution 2), you have not passed an action to post which is why it is throwing an error.
Try replacing the code in that expect block to:
post :create, brand: build(:brand).attributes.symbolize_keys
I have been writing some rspec tests, and right now I want to verify a redirect after user creation. Many other tests work, including creating users.
However, the last of the following tests fail:
describe "success" do
before(:each) do
#attr = { :username => "woweee123",
:email => "email#mail.com",
:password => "password123",
:password_confirmation => "password123"}
end
it "should create the user" do
lambda do
post :create, :user => #attr
end.should change(User, :count)
end
it "should redirect to a user show page after creation" do
lambda do
post :create, :user => #attr
end.should redirect_to(user_path(assigns(:user)))
end
end
The failed test is: No route matches {:action=>"show", :controller=>"users"
But, when I create a user manually (on localhost) the redirect obviously works. Why is this failing?
I would check if
assigns(#user).new_record?
is not true anymore after the post. It seems like it doesn't have an id in your test. Could some validation have failed?
For Those interested the answer is that I had to change the test so that it looks like:
it "should redirect to a user show page after creation" do
post :create, :user => #attr
response.should redirect_to(user_path(assigns(:user)))
end
This works.
I'm using factorygirl. I'm still new, in particular to the testing aspect of rails.
When I descibe POST create I can create any number of Person objects without any issue. I don't actually need to create one here as I normally just stub the valid? method of Person. But for the purpose of resolving this issue I threw it in there. Regardless of this Factory.create(:person) being in the POST create context (or how many times I use it there) it throws a non-descriptive error message when I use it in the PUT update context.
Additionally I have removed all validation from the model with no change in the result.
I'm at a loss.
Lets just get the basics working:
describe PeopleController do
describe "POST create" do
describe "with valid params" do
it "creates a person from params and renders persons/_form partial" do
f = Factory.create(:person)
# f = Factory.create(:person)
# f = Factory.create(:person)
end
end
end
describe "PUT update" do
describe "with valid params" do
it "updates the requested person" do
f = Factory.create(:person) #error when running rake spec
end
end
end
end
Error:
Failure/Error: f = Factory.create(:person)
ActiveRecord::RecordInvalid:
Validation failed:
Model
class Person < ActiveRecord::Base
end
I'm pretty sure that your factory does not meet the validation requirements. In your factory definition, your factory should be able to pass all the validations. Otherwise, you have to explicitly fulfil them like :
f = Factory(:person, :attribute => value_that_passes_validation, ...)
EDIT (sequences):
Factory.define :user do |f|
f.sequence(:username) { |n| "test#{n}" }
f.password "1234567890"
f.sequence(:email) { |n| "test#{n}#test.com" }
end