I need to test a model with HABTM association. My factory looks like:
FactoryGirl.define do
factory :article do |f|
body "Some awesome text"
after(:build) {|article| article.users = [create(:user)]}
end
end
How to test article creation without users association later in such way:
it "is not a valid article without users" do
article = build(:article, users: []) #doesn't work
expect(article.valid?).to eq(false)
end
You may be better off creating a sub-factory:
FactoryGirl.define do
factory :article do |f|
body "Some awesome text"
factory :article_with_users do
after(:build) {|article| article.users = [create(:user)]}
end
end
end
Then test by:
it "don't create article without users associations" do
article = build(:article) #doesn't work
expect(article.valid?).to eq(false)
end
it "do create article with users associations" do
article = build(:article_with_users)
expect(article.valid?).to eq(true)
end
This, however, depends on your validation setup in your models as well.
it don't, it do :)
Related
When I am trying to run test case for model using Rspec and Factory girl it is showing uninitialized constant Wing
code in factories/wing.rb
FactoryGirl.define do
factory :wing do
wing_name "Example Title"
is_deleted "0"
mg_school_id "1"
created_by "2013-06-02 02:28:12"
updated_by "2013-06-02 02:28:12"
end
end
code in model/mg_wing_spec.rb
require 'rails_helper'
RSpec.describe MgWing, type: :model do
it "has a valid factory" do
#hai=FactoryGirl.create(:wing)
end
end
Change filename factories/wing.rb to factories/mg_wing.rb
Your factory name should be the same in your model and rspec filename.
Here are a couple of tutorials if you want it:
https://medium.com/#JonoYeong/setting-up-rspec-and-factory-girl-8cf287801099
https://semaphoreci.com/community/tutorials/working-effectively-with-data-factories-using-factorygirl
You either need the factory name same as the model name or pass the class param and factorygirl will take care of it
Solution 1
FactoryGirl.define do
factory :mg_wing do
...
end
end
Solution 2
FactoryGirl.define do
factory :wing, class: MgWing do
...
end
end
I'm trying to test simple POST controller#create method.
it "saves the new object in the DB" do
object = build(:object_with_association)
expect { post :create, object: object.attributes }.to change(SomeObject, :count).by(1)
end
Is this object.attributes is the best way to do this? I tried to use attributes_for(:object_with_association), but it returns a hash without associations at all. May be there is some useful method to do this inside expect?
My factory:
factory :obj do
name "A"
association :first, factory: :first
association :second, factory: :second
factory :obj_with_association do
transient do
nested_count 5
end
after(:build) do |obj, evaluator|
create_list(:nested, evaluator.nested_count, obj: obj)
end
end
end
Association for the :first and :second is belongs_to, for the :nested is has_many
You can create attributes separately and merge them, or you can try traits with FactoryGirl.
Please take a look at these two SO questions for details:
factory girl nested factory
Using FactoryGirl to create a parameter string for controller testing with related models
My User model has the following action:
def release(idea)
if idea.status == "claimed"
idea.status = "available"
self.ideas.delete(idea)
end
end
Each user has_many ideas, and this is the way to release an idea and say "I don't want to be responsible for this idea anymore."
However, the current implementation lets one user release an idea owned by another user. I could easily solve this in the function itself by checking for idea.user_id but I'm trying to learn how to use CanCan and rolify... which is where the problem starts.
ability.rb says:
if user.has_role? :user
can :release, Idea, user_id: user.id
end
This fails in rspec:
Failures:
1) User manipulates ideas: can't change an idea he doesn't own
Failure/Error: let(:james) {create(:user)}
NoMethodError:
undefined method `find_or_create_by' for #<Rolify::Adapter::ResourceAdapter:0xabc7d98>
tl;dr: Should I be specifying abilities for User or for Idea? If User, how should I word the condition?
Thank you!
Edit: User factory:
FactoryGirl.define do
factory :user do
name Faker::Name.name
email Faker::Internet.email
provider "MyString"
uid {"user_#{rand(1000).to_s}" }
trait :admin do
after(:create) {|user| user.add_role(:admin)}
end
trait :guest do
after(:create) {|user| user.add_role(:guest)}
end
trait :authorized_user do
after(:create) do |user|
user.add_role(:user)
user.remove_role(:guest)
end
end
end
end
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
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 }