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.
Example idea to use optimistic locking
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.
Example idea to use a transaction
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.
Bounty
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)
1.upto(3).inject("") { |m, e| m << (rand(26)+65).chr}
– Ezar