What is FactoryGirl?
If you are new in Ruby testing, this part is for you.
FactoryGirl is nice replacement for fixtures. Some reasons to use it:
- FactoryGirl simplifies maintenance (when you add new field to database, you have to add it only to one place - appropriate factory).
- FactoryGirl allows to generate different values to the same object.
- You can think about objects and relations between them, not about IDs.
Official documentation (with good examples) here.
Fill tables with many-to-many relations
Tables structure
Documentations doesn't say it, but FactoryGirl allows to write custom code in before
and after
blocks. It's very convenient for filling many-to-many relations.
Imagine, there are 2 tables - teachers and subjects and relation many-to-many between them. Migration for creating these tables:
create_table :teachers do |t|
t.string :name
end
create_table :subjects do |t|
t.string :name
end
create_table :appointments do |t|
t.references :teacher
t.references :subject
end
Main idea
1. Declare in factory ignored field teachers, which not exists in class Subject, but can be passed from test:
factory :subject_with_teachers do
ignore { teachers [] }
end
2. This field will be passed to evaluator.teachers to after
action. Access it:
factory :subject_with_teachers do
ignore { teachers [] }
after(:create) do |subject, evaluator|
evaluator.teachers.each do
...
end
end
end
Note: in after
action we can write custom code, e.g. each cycle.
describe '...' do
let(:teachers) { FactoryGirl.create_list(:teacher, 3) }
let(:subjects) { FactoryGirl.create_list(:subject_with_teachers, 3, teachers: teachers) }
...
end
Full code
Factories
FactoryGirl.define do
factory :teacher do
sequence(:name) {|n| "name #{n}"}
end
factory :appointment, class: Appointment do
teacher # should be passed by creator
subject # should be passed by creator
end
factory :subject do
sequence(:name) {|n| "name #{n}"}
# inherits all subject fields
factory :subject_with_teachers do
# creates `field` 'teachers' with default value '[]'
# caller can set this parameter when calls FactoryGirl.build or FactoryGirl.create
ignore { teachers [] }
after(:create) do |subject, evaluator|
# subject contains only fields from factory
# evaluator contains all fields, including ignored fields
# here we can write any code, for example - each cycle
puts "#{subject} has been created"
evaluator.teachers.each do |teacher|
FactoryGirl.create(:appointment, { teacher: teacher, subject: subject })
puts "Appointment for #{teacher} and #{subject} has been created"
end
end
end
end
end
Rspec test
describe 'subjects with teachers' do
let(:teachers) { FactoryGirl.create_list(:teacher, 3) }
let(:subjects) { FactoryGirl.create_list(:subject_with_teachers, 3, teachers: teachers) }
it 'should create 3 subjects, 3 teachers and 9 appointments between them' do
subjects.length.should == 3
subjects[0].teachers.should == teachers
subjects[1].teachers.should == teachers
subjects[2].teachers.should == teachers
end
end
No comments:
Post a Comment