I have not found a solution, but found this unanswered question from years ago: factory girl - know when an attribute is overridden in factory
The idea is that I would like the factory to be able to make decisions based on what was overridden when create
is called.
Here's the example:
class Contract < ApplicationRecord
# has start_date and end_date attributes
has_many :visits
end
class Visit < ApplicationRecord
# has date attribute
belongs_to :contract
validates :date, within_period: true # throws error if date < contract.start_date or date > contract.end_date
end
If the factory for Visit
looks like this:
FactoryBot.define do
factory :visit do
association :contract, start_date: Date.current, end_date: Date.current
date { Date.current }
end
end
The specs run into this:
# Case 1
create(:visit) # creates a valid visit with Date.current in everything, which is ok
# Case 2
create(:visit, date: '01/01/2001') # does not create a visit, because it throws the date error
# Case 3
create(:visit, contract: @contract) # could create a visit or throw the date error, depending on @contract's attributes
If there is a way for the factory to know what is overridden:
In Case 2, it could send dates to the contract factory that actually work with the overridden date.
In Case 3, it could set its own date to be something that passes validation based on the contract object received.
It seems this could be solved with something like create(:visit, contract: @contract, date: @contract.start_date)
or maybe the use of traits. I'm wondering, though, if these approaches wouldn't be considered to go against some principle of testing, given that they would make the test have to know and care about this rule, even when the test itself may not be about dates at all.
For now I think I'll solve and move forward with the first approach, so the specs will explicitly build the objects honring the validation rule, but I'm curious what folks out there have seen, tried, or would recommend.
You can use the @overrides
instance variable to see what was passed into the create
method.
FactoryBot.define do
factory :visit do
# if contract is passed use contract.start_date as date
# Date.current otherwise
# if date is given, the block is ignored.
date { @overrides[:contract]&.start_date || Date.current }
# If contract is not given, build a contract with dates from the date attribute
association :contract, start_date: date, end_date: date
end
end
If this doesn't work with the blockless association definition, you can change to the association definition with a block.