I am using RSpec and FactoryGirl to test my Ruby-on-Rails-3 application. I am using a hierarchy of factories:
FactoryGirl.define do
factory :local_airport do
... attributes generic to a local airport
factory :heathrow do
name "London Heathrow"
iata "LHR"
end
factory :stansted do
name "Stansted"
iata "STN"
end
... more local airports
end
end
In my RSpec I sometimes want to be able to create all the child factories by specifying the parent factory. Ideally, something like:
describe Flight do
before( :each ) do
# Create the standard airports
FactoryGirl.find( :local_airport ).create_child_factories
end
end
Many thanks in advance.
You can't really tell a factory to build all it's child factories, as being a child factory just means that it inherits the attributes from the parent. But what you could do is add a trait, for example :with_child_factories. Then your factory would look like the following:
FactoryGirl.define do
factory :local_airport do
... attributes generic to a local airport
factory :heathrow do
name "London Heathrow"
iata "LHR"
end
factory :stansted do
name "Stansted"
iata "STN"
end
... more local airports
trait :with_child_factories do
after(:create) do
FactoryGirl.create(:heathrow)
FactoryGirl.create(:stansted)
...
end
end
end
end
Then you could call it in your test with
FactoryGirl.create(:local_airport, :with_child_factories)
Well after a few hours of delving in the FactoryGirl code I have found a solution. Interestingly FactoryGirl only stores a reference to the parent in the factory, and not from parents to children.
In spec/factories/factory_helper.rb:
module FactoryGirl
def self.create_child_factories( parent_factory )
FactoryGirl.factories.each do |f|
parent = f.send( :parent )
if !parent.is_a?(FactoryGirl::NullFactory) && parent.name == parent_factory
child_factory = FactoryGirl.create( f.name )
end
end
end
end
In my RSpec I can now write:
require 'spec_helper'
describe Flight do
before( :each ) do
# Create the standard airports
FactoryGirl.create_child_factories( :local_airport )
end
...
One gotcha I found is that it is best to have a factory hierarchy that is simple (i.e. two levels). I started with a three layer hierarchy and found I was generating 'abstract' factories that only existed as part of the hierarchy. I have simplified the hierarchy to two levels using traits.
Related
I am building a Rails 3.2.11 application with Mongoid. I test with Cucumber and create test objects with FactoryGirl. I have embedded objects. I want to be able to use FactoryGirl traits with both my parent and embedded objects to make lots of permutations and keep things DRY.
The problem: I can't seem to get traits to apply to the embedded objects.
model.rb (actually, separate model files)
class User
include Mongoid::Document
#Fields and stuff
embeds_one :car
end
class Car
include Mongoid::Document
#Fields and stuff
embedded_in :user
end
spec/factories.rb
FactoryGirl.define do
factory :user do
status 'active' # shared attribute
trait :bob do
name 'Bob'
email 'bob#bob.com'
end
trait :carol do
name 'Carol'
email 'carol#carol.com'
end
car { [FactoryGirl.build(:car)] }
end
factory :car do
number_of_tires 4 # shared attribute
trait :red_ford do
make 'Ford'
color 'Red'
end
trait :blue_chevy do
make 'Chevy'
color 'Blue'
end
end
end
features/step_definitions/user_steps.rb (not working right)
Given /^There is a user Bob with a Blue Chevy$/ do
FactoryGirl.create(:user, :bob, :car => [:car => :blue_chevy])
end
I can factory create the user object just fine if I leave off the embedded object trait. Any way to get the embedded object to be built with the desired trait applied? Thanks!
The fix
Joshua Clayton at Thoughtbot http://joshuaclayton.me says:
There's no shorthand syntax for applying traits to associations;
basically, you'd have to do something like this:
cars = [FactoryGirl.build(:car, :blue_chevy)]
FactoryGirl.create(:user, :bob, cars: cars)
Because it is a 1 to 1 relationship, you do not need the array when building the car object in spec/factories.rb
car { FactoryGirl.build(:car) }
This applies in the cucumber step as well. Also, as I understand, traits are used as attributes, so your step code should look like
FactoryGirl.create(:user, :bob, :car, :blue_chevy)
I have the following factory:
factory :store do
room
factory :store_with_items do
ignore do
items_count 4
end
after(:create) do |store, evaluator|
FactoryGirl.create_list(:equippable_item, evaluator.items_count, store: store)
end
end
end
Next, I create an object:
#store = FactoryGirl.create :store_with_items
My problem is that when I "delete" one of the store's items, the store still shows that it has 4 items.
#store.items[0].store_id = nil
#store.save!
puts #store.items.size
The puts is 4. How do I properly delete an item? Isn't this how you would do it in rails?
I used to prefer this approach, but now I avoid it; its easier and more flexible to let factories be simple and populate has_many associations at runtime.
Try this
Factory for store (same):
factory :store do
room
end
Factory for items:
factory :item do
store # will use the store factory
end
Then in my test I would populate what is appropriate for the case at hand:
#store = FactoryGirl.create :store
#item1 = FactoryGirl.create :item, store: #store
#item2 = FactoryGirl.create :equippable_item_or_whatever_factory_i_use, store: #store
To explain
By passing in the store instance explicitly, the association will be setup for you. This is because when you pass something explicitly in FactoryGirl.create or FactoryGirl.build it overrides whatever is defined in the factory definition. It even works with nil. This way, you'll have real object instances that give you all the real functionality.
To test destroy
I think the code in your example is not good; it breaks the association between store and item, but doesn't actually remove the item record so you're leaving behind an orphan record. I would do this instead:
#store.items[0].destroy
puts #store.items.size
Bonus
You probably also want to setup your child associations to be destroyed when the parent is destroyed if its not already. This would mean when you say #store.destroy all the items belonging to it will also be destroyed (removed from the db.)
class Store < ActiveRecord::Base
has_many :items, dependent: :destroy
.....
end
There aren't currently any up to date answers for this using Factory Girl 4.1 (that I could find) - how do you setup a many to many relationship inside of a factory?
For instance I have Students and Classrooms which are in a many to many relationship using a join table, so far I had the following setup:
factory :classroom do
name "Foo Class"
...
end
factory :student do
name "John Doe"
...
end
factory :student_with_classroom, :parent => :student do
after(:build) {|student| student.classrooms << classroom}
end
However this results in:
NameError:
undefined local variable or method `classroom' for #<FactoryGirl::SyntaxRunner>
My attempt was guesswork for the most part as I had no luck finding any non-deprecated syntax to accomplish this.
Actually I managed to find the answer I was looking for buried under a slew of other answers in this SO: How to create has_and_belongs_to_many associations in Factory girl
factory :classroom do
name "Foo Class"
...
end
factory :student do
name "John Doe"
...
end
factory :student_with_classroom, :parent => :student do
classrooms {[FactoryGirl.create(:classroom)]}
end
Check out this SO post: How to set up factory in FactoryGirl with has_many association. It will point you to https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md; search for has_many.
I have a nested resource like this
resources :projects do
resources :tasks
end
The tasks have a field named number. Whenever I create a task I would like to give it a squential number within the parent project.
This is my model class
class Task < ActiveRecord :: Base
belongs_to :project
validate_presence_of :title
before_create :generate_number
private
def generate_number
if project.tasks.nil? || project.tasks.count < 1
self.number = 1
else
self.number = list.topics.count+1
end
end
end
I am not sure about certain things:
Does this logic belongs in my Task Model or in my Project model or in a seperate class/module?
What is the best before filter. (before_create, before_validation, validation)?
Because there are many ways how to create a task. With a list, in a list, alone and then attach it to a list...
And which filter would work in my tests so that I could setup some Fakes for example with factory girl... Because right now FactoryGirl does not always executes generate number...
This is my factory
FactoryGirl.define do
factory :project do
name "Hello world"
end
trait :with_tasks do
ignore do
number_of_tasks 3
end
after :create do |project,evaluator|
#project.Factory.create_list :taks, evaluator.number_of_tasks, :project => project
end
end
end
What would be the best. reliable way to generate a sequential custom taks number depending on the project which works in my specs as well as in production?
Any best practise tips would be appreciated.
I would keep the before_create callback in the Task model, which would call the generate_number function. This should work in Factory girl where it would add the number if you use Factory.create, but not when you use Factory.build.
I have some ActiveRecord superclass Product and a subclass DiscountedProduct that share the same table and I have some factories for the superclass that I want to use with the subclass.
Factory(:product).class #=> Product
What I am trying to find is a shorthand for:
DiscountedProduct.create(Factory.build(:product).attributes)
NOTICE:
I don't use Factory.attributes_for so that the needed associations get built.
FactoryGirl v2.3.2
Rails 3.0.4
Well, seems factory_girl supports quite well the inheritance.
You can define your factory either way :
As a nested definition
factory :product do
name 'Product name'
factory :discounted_product do
discounted true
end
end
or as a linked definition
factory :discounted_product :parent => :product do
approved true
end