I have a Factory Girl factory that needs to generate a unique name using Faker. Unfortunately, Factory Girl generates duplicates relatively frequently, which causes intermittent test errors. I previously added a random integer after the Faker name, but I am now checking the database to make sure that a potential name from Faker doesn't already exist.
FactoryGirl.define do
factory :company do
name do
name = Faker::Company.name
name = Faker::Company.name while Company.exists?(name: name)
name
end
end
end
The above code works but seems inelegant. I'm assigning name in the first row of the block so that only one DB lookup (instead of two) will be necessary in the most common case where a Fake name is unique.
I end the block with name
as otherwise the while
statement returns nil when the loop ends.
4 Answers 4
The simplest thing to do is probably to use FactoryGirl's sequence
:
FactoryGirl.define do
factory :company do
sequence(:name) { |n| "#{Faker::Company.name} #{n}" }
end
end
sequence
basically gives you an auto-incrementing integer, so you can avoid uniqueness issues. Sure, it'll generate some slightly odd company names, but for testing that shouldn't matter much.
In general though, I don't feel too great a need for something like Faker. Something like this should work just as well:
FactoryGirl.define do
factory :company do
sequence(:name) { |n| "Acme Inc. #{n}" }
end
end
If it's only your specs looking at the data, it doesn't really matter if the company name's "Acme", "foobar 42", or "ACC8B1B7-7EC0-4F05-AB2A-B487C134F6BF".
Besides, your current solution is:
non-deterministic, so it may never exit the
while
loop if it just happens to keep picking the same couple names again and again,but even if we ignore that, the act of adding more tests that use
company
records, will mean more name collisions and longer time spent in the loop. I don't know how Faker works, but (unlikely but technically possible) you might even end up exhausting every name Faker can come up with, and end up looping forever.
-
\$\begingroup\$ Sequences are good, but our challenge is that the :company factory is called as an association from several other factories at different points in our tests, so the sequences won't only be called once. Also, we can't use database transactions because Capybara is running in a different process. \$\endgroup\$Dan Kohn– Dan Kohn2014年11月27日 15:45:25 +00:00Commented Nov 27, 2014 at 15:45
-
\$\begingroup\$ @dankohn I'm not sure I understand your first point; even if a record gets built as an association when something else gets built,
sequence
works just fine. It's "global", so it'll be unique for each call tobuild
/create
whether you're calling it directly or not. \$\endgroup\$Flambino– Flambino2014年11月27日 18:19:50 +00:00Commented Nov 27, 2014 at 18:19 -
\$\begingroup\$ +1 for
sequences
, Faker produces random unpredictable data and can result in flaky tests that you won't be able to easily reproduce or debug. \$\endgroup\$rui– rui2014年11月28日 16:42:56 +00:00Commented Nov 28, 2014 at 16:42
This can be accomplished now with:
name { Faker::Company.unique.name }
I found a solution I like better, which replaces the while
loop with a loop/break
. Sequences work but look funny as fake data.
FactoryGirl.define do
factory :company do
name do
loop do
possible_name = Faker::Company.name
break possible_name unless Company.exists?(name: possible_name)
end
end
end
end
-
\$\begingroup\$ You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and why it is better than the original) so that the author and other readers can learn from your thought process. \$\endgroup\$Malachi– Malachi2018年05月09日 16:51:59 +00:00Commented May 9, 2018 at 16:51
A different approach would be to utilize the following code:
Faker::Lorem.unique.word
-
\$\begingroup\$ what is
Lorem
? Did you meanCompany
? If so, this approach was already presented in dft's answer \$\endgroup\$2018年05月09日 15:09:44 +00:00Commented May 9, 2018 at 15:09 -
\$\begingroup\$ Welcome to Code Review! You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and why it is better than the original) so that the author and other readers can learn from your thought process. \$\endgroup\$Malachi– Malachi2018年05月09日 16:50:41 +00:00Commented May 9, 2018 at 16:50