I am new to FactoryGirl. I come from the fixtures world.
I have the following two models:
class LevelOneSubject < ActiveRecord::Base
has_many :level_two_subjects, :inverse_of => :level_one_subject
validates :name, :presence => true
end
class LevelTwoSubject < ActiveRecord::Base
belongs_to :level_one_subject, :inverse_of => :level_two_subjects
validates :name, :presence => true
end
And I would like to do something like the following in factories:
FactoryGirl.define do
factory :level_one_subject, class: LevelOneSubject do
factory :social_sciences do
name "Social Sciences"
end
end
factory :level_two_subject do
factory :anthropology, class: LevelTwoSubject do
name "Anthropology"
association :level_one_subject, factory: social_sciences
end
factory :archaelogy, class: LevelTwoSubject do
name "Archaelogy"
association :level_one_subject, factory: social_sciences
end
end
end
Then when I use the factory in a spec like this:
it 'some factory test' do
anthropology = create(:anthropology)
end
I get the error:
NoMethodError: undefined method `name' for :anthropology:Symbol
Can anybody help here?
If I do not set the association in factory, then I do not get this error, but I get the error that level_one_subject_id has to be present and only the following test code works:
it 'some factory test' do
social_sciences = create(:social_sciences)
anthropology = create(:anthropology, :level_one_subject_id => social_sciences.id)
end
But I really want to know why the factory with the association does not work. With Fixtures I had all this for nothing.
I think you are trying to group factories by a 'class factory', which is not how FactoryGirl works. It will deduce the ActiveRecord class from the factory name itself, if named appropriately. In case, your factory name is not the same as the class name, we need to explicitly specify the class name using class named parameter. This should work:
FactoryGirl.define do
factory :level_one_subject do # automatically deduces the class-name to be LevelOneSubject
name "Social Sciences"
end
factory :anthropology, class: LevelTwoSubject do
name "Anthropology"
level_one_subject # associates object created by factory level_one_subject
end
factory :archaelogy, class: LevelTwoSubject do
name "Archaelogy"
level_one_subject # associates object created by factory level_one_subject
end
end
Related
I can have multiple profiles. All of the have to be associated with the same "api license".
Here is my profile factory:
FactoryGirl.define do
factory :profile do
name "MyString"
after(:build) do |profile|
api_license = ApiLicense.find_by_name('API test name')
api_license ||= FactoryGirl.create(:api_license)
profile.api_license = api_license
end
end
end
I can define multiple profiles with FactoryGirl.build(:profile) or FactoryGirl.create(:profile) and all of them are attached to the same api license, which is good.
The problem comes when I want to test the "no api license case".
For example FactoryGirl.build(:profile, :api_license=>nil) doesn´t work because it seems that after(:build) is executed after assigning nil value to api license.
Any ideas about this?
Create another factory without assigning api license
FactoryGirl.define do
factory :profile_without_api_license, :class => Profile do
name "MyString"
end
factory :profile do
name "MyString"
after(:build) do |profile|
api_license = ApiLicense.find_by_name('API test name')
api_license ||= FactoryGirl.create(:api_license)
profile.api_license = api_license
end
end
end
Call the factory profile_without_api_license whenever you want one without license
I'm trying to defined a has_many relationship in FactoryGirl using the after_create callback, like so in /spec/factories/emails.rb:
FactoryGirl.define do
factory :email do
after_create do |email|
email.attachments << FactoryGirl.build(:attachment)
end
end
end
The attachment is defined in a seperate factory /spec/factories/attachment.rb:
FactoryGirl.define do
factory :attachment do
# Attach the file to paperclip
file { fixture_file_upload(Rails.root.join('spec', 'support', 'myimage.png'), 'image/png') }
end
end
Using the :attachment in my specs works absolutely fine, so I'm confident that the factory for that is not the problem, however when I try and create an :email from the factory I get the following exception thrown:
Failure/Error: email = FactoryGirl.create(:email)
NoMethodError:
undefined method `after_create=' for #<Email:0x007ff0943eb8e0>
I'm at a bit of a loss as to what to do, can't seem to find any one else getting the same error.
FactoryGirl recently changed the syntax for callbacks. I think the following will work:
FactoryGirl.define do
factory :email do
after(:create) do |email|
email.attachments << FactoryGirl.build(:attachment)
end
end
end
I have an account model that belongs_to a role model.
factory :role do
name "student"
end
factory :account do
user
role
end
The first factory creates a role named "student". The second factory creates an account that is associated to the student role that was created in the previous factory. It also is associated with a user...which is not important for this question.
I have many roles to be tested (admin, student, assistant)... I dont want to specify 'student' in the role factory...thats too static. How do I specify what role to create at the time the account factory is created? Like:
factory :account do
user
role_id { factory :role { name: "admin"} }
end
What is the best way to accomplish this?
If you want a purely FG solution, you could use Traits:
factory :account do
user
trait :student do
association :role, :name => "student"
end
trait :admin do
association :role, :name => "admin"
end
end
FactoryGirl.create :account, :student
FactoryGirl.create :account, :admin
However, you can override the properties of the factory when you create the factory object. This allows for more flexibility:
FactoryGirl.create(:account,
:role => FactoryGirl.create(:role, :name => "student")
)
Since this is obviously verbose, I'd create a little helper method:
def account_as(role, options = {})
FactoryGirl.create(:account,
options.merge(:role => FactoryGirl.create(:role, :name => "student"))
)
end
Then in your tests:
let(:account) { account_as "student" }
Alternately, you could just shorten up your role generator so you could use it like:
def role(role, options = {})
FactoryGirl.create :role, options.merge(:name => role)
end
account = FactoryGirl.create :account, :role => role("student")
UPDATE
I went back to using Fixtures. IMOP, fixtures are FAR better than factories; easier to use, easier to write, easier to understand (no magic). My suggestion: limit your testing library to the very basics (listen to DHH)...use minitest with fixtures.
original post
In my app a district has many schools, a school has many uses, a user has many accounts, an account has one role. In order to create complete factories for testing I need to create a user and school that persists across factories. Im getting a "stack level too deep" error in my recent attempts.
My user_test.rb
FactoryGirl.define do
factory :district do
name "Seattle"
end
factory :school do
association :primarycontact, factory: :user # expecting this to attach the user_id from factory :user as :primary contact_id in the school model
association :district, factory: :district # expecting this to attach the :district_id from the :district factory as :district_id in the school model
name "Test School"
end
factory :user do, aliases: [:primarycontact]
email "adam#example.com"
name "Who What"
username "wwhat"
password "123456"
password_confirmation { |u| u.password }
association :school, factory: :school # expecting this to create :school_id in the users model, using the :school factory
end
factory :role do
name "student"
end
factory :account do
association :user, factory: :user
association :role, factory: :role
end
end
So, I am attempting to do FactoryGirl.create(:account)... which I am expecting to create an account, with the user and role from the factories above, with the user associated with the school that is associated with the district. This is not working for me. Among failing tests I get a "stack level too deep" error. And, I believe my before each DatabaseCleaner.clean is clearing the test db before each new factory.
The test that calls these factories is:
describe "User integration" do
def log_em_in
visit login_path
fill_in('Username', :with => "wwhat")
fill_in('Password', :with => "123456")
click_button('Log In')
end
it "tests log in" do
user = FactoryGirl.create(:account)
log_em_in
current_path.should == new_user_path
end
end
.
current_path.should == new_user_path returns unknown method error 'should'
How can I improve this code to nest the factories correctly and get a current_user in order to continue testing?
MODELS
school.rb
belongs_to :district
belongs_to :primarycontact, :class_name => "User"
has_many :users, :dependent => :destroy
user.rb
belongs_to :school
has_many :accounts, :dependent => :destroy
district.rb
has_many :schools
account.rb
belongs_to :role
belongs_to :user
role.rb
has_many :accounts
has_many :users, :through => :accounts
Your basic problem is that you have a circular dependency between your user factory and your school factory, caused by the fact that you create a primarycontact (a user) when you create a school, then that user creates a school, and so on.
You can get around this by changing how you define your school association inside the user factory. Before doing that though, I'd suggest as a general rule using the shorthand notation for associations. So replace this:
factory :account do
association :user, factory: :user
association :role, factory: :role
end
with this:
factory :account do
user
role
end
Using this simplification, the following factories will do what you want without generating any circular dependency:
FactoryGirl.define do
factory :district do
name "Seattle"
end
factory :school do |school|
district
primarycontact
name "Test School"
after_build do |s|
s.primarycontact.school = s
end
end
factory :user do
email "adam#example.com"
name "Who What"
username "wwhat"
password "123456"
password_confirmation { |u| u.password }
school
end
factory :primarycontact, class: "User" do
# add any attributes you want the primarycontact user to have here
end
factory :role do
name "student"
end
factory :account do
user
role
end
end
Notice that what I have done is to create a factory for primarycontact with the class: "User" option. Unlike the user factory, this factory does not create the school by default, avoiding the circular dependency.
Then in the school factory, I use an after_build callback to assign the school itself to the school association on primarycontact, rather than creating a new school (which was causing the problem in your factories).
Hope that makes sense. Note that the callback syntax has changed in the more recent version of factory_girl, see the documentation for details.
I am working on a web backend in Rails. My Article model is largely a wrapper that delegates most methods to the most recent ArticleVersion. When writing FactoryGirl factories, though, I was trying to create an :article_with_version factory that generates an Article and gives it a version, but I'm not sure how to forward parameters from the Article factory on to the ArticleVersion.
Here is the relevant code:
class Article < ActiveRecord::Base
has_many :versions, :class_name => "ArticleVersion"
def title
self.versions.last.title
end # method title
def contents
self.versions.last.contents
end # method contents
end # model Article
FactoryGirl.define do
factory :article_version do; end
factory :article do; end
factory :article_with_version, :parent => :article do
after_create do |article|
article.versions << Factory(:article_version, :article_id => article.id)
end # after_create
end # factory :article_with_version
end # FactoryGirl.define
What I would like to be able to do is call Factory(:article_with_version, :title => "The Grid", :contents => "<h1>Greetings, programs!</h1>") and have FactoryGirl pass those :title and :contents parameters on to the new ArticleVersion (or nil if those are omitted). Is there a way to access that hash of dynamic parameters that are passed on during Factory.create()?
You can do it using transient attributes like this:
factory :article_with_version, :parent => :article do
ignore do
title nil
contents nil
end
after_create do |article, evaluator|
article.versions = [FactoryGirl.create(:article_version, title: evaluator.title, contents: evaluator.contents)]
article.save!
end
end
Just note that the attributes being ignored will not be set on the Article itself, although it looks like that is the behaviour you want in this case.