2
\$\begingroup\$

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.

\$\endgroup\$
7
\$\begingroup\$

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:

  1. non-deterministic, so it may never exit the while loop if it just happens to keep picking the same couple names again and again,

  2. 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.

\$\endgroup\$
  • \$\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 Nov 27 '14 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 to build/create whether you're calling it directly or not. \$\endgroup\$ – Flambino Nov 27 '14 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 Nov 28 '14 at 16:42
2
\$\begingroup\$

This can be accomplished now with:

name { Faker::Company.unique.name }
\$\endgroup\$
0
\$\begingroup\$

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
\$\endgroup\$
  • \$\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 May 9 '18 at 16:51
-2
\$\begingroup\$

A different approach would be to utilize the following code:

Faker::Lorem.unique.word
\$\endgroup\$
  • \$\begingroup\$ what is Lorem? Did you mean Company? If so, this approach was already presented in dft's answer \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ May 9 '18 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 May 9 '18 at 16:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.