I am testing a typical Rails model with a typical factory:
# My model uses a 3-letter uppercase airport code,
# such as "ATL" for Atlanta, "BOS" for Boston, etc.
class Airport < ActiveRecord::Base
validates :code, uniqueness: true
Factory.define :airport do |f|
f.code { random_airport_code } # Get a 3-letter uppercase code
I am adding more tests and starting to see collisions in the airport code: for example the factory creates an airport with code "XYZ" then a subsequent call to the factory tries to create an airport with the same code.
A sequence is one way to tackle this. For example use a Factory Girl sequence, or an ordered list, or pre-calculated enumeration,some similar way of maintaining state of the next available code.
My question is: what are non-sequence ways to tackle this? I want to use random data, and not a sequence.
A few ideas I'm trying because they're pragmatic -- any insight on these is much appreciated.
while
airport = Factory.build :airport
airport.save && return airport
end
Pros: fast in practice because collisions are rare; local state.
Cons: awkward syntax; non-local to the factory; the save might fail for reasons other than the collision.
Airport.transaction
while
x = random_airport_code
if Airport.exists?(code: x)
next
else
Factory :airport, code: x
break
end
end
end
Pros: this is the closest to what I want; local state; ensures there's no collision.
Cons: long awkward syntax.
Does Factory Girl or Minifacture have any kind of syntax that is more amenable to random data, and not a sequence?
Or perhaps some kind of pattern to automatically re-roll of the dice if there's a save collision?
Some overhead is fine with me. In practice a collision is happening once per day or so, on a continuous integration setup with thousands of tests. If the test suite must re-roll the dice a few times, or probe the database for existing values, etc. that's fine.
The comments ask why random data instead of a sequence. I prefer random data because my experience is that random data leads to better tests, better long-term maintainability, and better semantics with the test goal. Also, I use Faker and Forgery instead of fixtures, in case that's helpful to know.
To earn the bounty, the answer must be random on the fly-- not a sequence. (For example, the solution I'm seeking may likely use #sample and/or an unordered set, and may likely not use #shuffle and/or an ordered set)
Factory Bot, originally known as Factory Girl, is a software library for the Ruby programming language that provides factory methods to create test fixtures for automated software testing.
build_stubbed is the younger, more hip sibling to build ; it instantiates and assigns attributes just like build , but that's where the similarities end.
Factory Bot is a helper for writing factories for Ruby tests. It was previously known as Factory Girl.
You could use a callback. Something like:
factory :airport do
after(:build) do |airport|
airport.code = loop do
code = ('AAA'..'ZZZ').to_a.sample
break code unless Airport.exists?(code: code)
end
end
end
You may want to change after(:build)
to before(:create)
, it depends on how you want to use the factory.
this should work, but it only allows for 17576 models to be created
CODES = ("AAA".."ZZZ").to_a.shuffle
Factory.define :airport do |f|
f.code { CODES.pop }
end
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