Suppose I want to run a test and create many Student
s. Each student belongs to one school. The School names are given by the Faker gem, but there's a limited number of them, and students are associated to those schools.
Can I use FactoryGirl in a way that lets me reuse existing schools ? ie. a new FactoryGirl.create(:student)
is assigned to either
FactoryGirl.create(:school)
.
class Student
belongs_to :school, class_name: 'SchoolSociety'
end
class School
has_many :students
field :name
end
I believe it's irrelevant here, but I'm using Mongoid. My factories would look like
FactoryGirl.define do
factory :student, class: Student do
association(:school, factory: :school)
end
factory :school, class: School do
name { Faker::University.name }
end
end
One solution would be to use School.where(name: Faker::University.name)
but I'd lose all the flexibility of FactoryGirls factories...
Any better workaround ?
The context is running Cucumber tests with many students
Edit:
My actual cucumber case is testing a jackpot that increases according to a math formula involving current time and number of registered users (that registered under a School). I was doing something in the likes of
Scenario Outline: Jackpot increases with registrations and time
Given the current date is <date>
And <count> students have registered for the special event
When I am on the special event page
Then I should see "<jackpot> €"
Examples:
| date | count | jackpot |
| 2016/11/24 15:00:00 | 5 | 4 9 5 , 3 0 |
| 2016/11/30 15:00:00 | 10 | 4 9 7 , 6 0 |
| 2016/12/10 15:00:00 | 20 | 5 0 2 , 2 0 |
| 2016/12/10 15:00:00 | 150 | 6 5 2 , 2 0 |
Now, those <count> students have registered for the special event
must be students that belong to an existing school (which means, during the registration process, they must register with a school email whose domain exists/is mappable to a School
in our DB)
You should be specific and precise about your Givens when using Cucumber rather than letting factory rules decide what is created. So if you want to create several students in the same school I would have
Given there is a school with several students
If you want to have several schools with students
Given there are several schools with students
And if you want to have a student that is enrolled in more than one school
Given there is a student enrolled in two schools
Obviously you can create a create deal of variety here by doing things like
When you implement these steps, especially when you are starting, its tempting to put all the code to do the work in the step. Resist this, instead make calls in the steps and make the calls match the step description. For example
Given 'there is a school with several students' do
@school = create_school
@students = []
several.times do
@students << create_student school: school
end
end
This step uses two methods create_school
and create_student
. You would define these in a helper module
module SchoolsStepHelper
def create_school
...
def create_student(school: ...)
...
end
World SchoolsStepHelper
Now you can be precise about how you create your students, and when you come across something new e.g. a student enrolled in two schools you can add/modify your methods to get this extra functionality, e.g. you might add
def enroll_student(student: school:)
...
so that we can do
Given 'there is a student enrolled in two schools' do
@student = create_student
@school_1 = create_school
@school_2 = create_schoo
enroll_student(student: @student, school: @school_1)
enroll_student(student: @student, school: @school_1)
end
Now however we still have too much code in our step definitions, so we need to refactor. Here we have a couple of choices
Given there is a school harvard
And there is a school yale
And there is a student Fred
And Fred is enrolled in yale and harvard.
You'd do things this way to make your scenario more descriptive, particularly when developing the enroll functionality
Given 'there is a student enrolled in two schools' do
@student = create_dual_enrolled_student
end
Which ever approach you chose, you will be reusing the simple methods we created at the beginning of this answer.
Now your question about Factories is basically an implementation detail, about how you create things. It should be possible to
Implement a solution which requires little or no understanding of factories to understand whats happening. (just very simple Factory calls in your methods)
Implement a solution that doesn't even use factories (this is the approach I favour, but thats a whole other story).
Finally if you take this approach and all your step definitions are implemented as simple calls, then it doesn't matter if you have lots of similar step definitions e.g.
Given there is a student
Given Fred is a student
Given there is a student Fred
Given there is a student Sue
You can have a step definition for each one of these without creating duplication because making calls is not duplication and the cost of the extra steps is just about balanced by the simplicity of implementation and the need for no parameters or regexs.
Given 'there is a student' do
create_student
end
Given 'Fred is a student' do
create_student name: 'Fred'
end
Given there is a student Fred do
create_student name: Fred
end
Given 'there is a student Sue' do
create_student name: 'Sue'
end
Phew that was a long answer, I hope its useful.