I'm using FactoryBot (formerly FactoryGirl) to create some factory data for my tests. I have a model that looks like this via the schema (stripped down to just the relevant stuff):
create_table "items", force: :cascade do |t|
t.text "team"
t.text "feature_id"
t.text "feature"
t.timestamps
end
However, feature_id
and feature
are NOT references to a feature object... they are just strings.
I've defined my factory like this:
FactoryBot.define do
factory :item do
team "TheTeam"
sequence(:feature_id) {|n| "Feature#{n}" }
feature { Faker::Lorem.sentence }
end
end
And the simple case works:
> FactoryBot.create(:item)
=> #<Item:0x007fad8cbfc048
id: 1,
team: "TheTeam",
feature_id: "Feature1",
feature: "Qui voluptatem animi et rerum et.",
created_at: Wed, 10 Jan 2018 02:40:01 UTC +00:00,
updated_at: Wed, 10 Jan 2018 02:40:01 UTC +00:00>
But when I want to specify my own feature_id
this is what happens:
> FactoryBot.create(:item, feature_id: "123")
=> #<Item:0x007fad8d30a880
id: 2,
team: "TheTeam",
feature_id: "123",
feature: nil,
created_at: Wed, 10 Jan 2018 02:40:59 UTC +00:00,
updated_at: Wed, 10 Jan 2018 02:40:59 UTC +00:00>
You can see that feature
is now nil
. I'm assuming this is because it's trying to infer that feature_id
and feature
are somehow related. But in this case, I don't want them to be.
Is there a better way to define the factory so that it just treats them as unrelated fields?
BTW, if I try to set both the feature_id
and feature
it looks like this:
> FactoryBot.create(:item, feature_id: "123", feature: "hi")
=> #<Item:0x007fad8d262810
id: 3,
team: "TheTeam",
feature_id: nil,
feature: nil,
created_at: Wed, 10 Jan 2018 02:45:01 UTC +00:00,
updated_at: Wed, 10 Jan 2018 02:45:01 UTC +00:00>
So it just sets both fields to nil
. I suspect FactoryBot is trying to be "smart" about these fields based on their names. I'd change them but they are already set that way in the Db.
It does appear that FactoryBot is making assumptions, and I haven't found a way to change those assumptions. It might be worth opening an issue to see what the maintainers have to offer.
In the mean time, here's a workaround:
FactoryBot.define do
FEATURE_IDS ||= (1..1000).cycle
factory :item do
team "TheTeam"
transient { without_feature_id false }
transient { without_feature false }
after(:build, :stub) do |item, evaluator|
item.feature_id = "Feature#{FEATURE_IDS.next}" unless evaluator.without_feature_id
item.feature = Faker::Lorem.sentence unless evaluator.without_feature
end
end
end
This will function properly in the cases you described above.
The incrementing is tricky. I was not able to find a way to use FactoryBot sequences outside of the resource-construction context, so I use an Enumerator and call #next
to create the sequence. This works similar to a FactoryBot sequence, except that there is no way to reset to 1 in the middle of a test run.
RSpec tests prove it works as expected, whether we are creating items in the database or building them in memory:
context 'when more than one item is created' do
let(:item_1) { create(:item) }
let(:item_2) { create(:item) }
it 'increments feature_id by 1' do
expect(item_1.feature_id).to be_present
expect(item_2.feature_id).to eq(item_1.feature_id.next)
end
end
context 'when using build instead of create' do
let(:item_1) { build(:item) }
let(:item_2) { build(:item) }
it 'increments feature_id by 1' do
expect(item_1.feature_id).to be_present
expect(item_2.feature_id).to eq(item_1.feature_id.next)
end
end
Note that you cannot create an item without a feature_id or a feature using the typical construct; for example:
>> item = create(:item, feature_id: nil)
Will result in
>> item.feature_id
#> "Feature1"
If you wish to create an object without the feature and feature_id fields, you can do:
create(:item, without_feature_id: true, without_feature: true)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With