The problem
At some point our specs started to randomly fall with the following exception:
Faker::UniqueGenerator::RetryLimitExceeded:
Retry limit exceeded for country_code
Configuration
The problematic field was defined in the Country
factory, which was used only in a few specs:
# spec/factories/country.rb
FactoryBot.define do
factory :country do
code { Faker::Address.unique.country_code }
name { Faker::Address.unique.country }
end
end
Also to avoid Faker::UniqueGenerator::RetryLimitExceeded
exception we clear the record of unique values
between tests:
# spec/rails_helper.rb
RSpec.configure do |config|
# ...
config.before(:all) do
Faker::UniqueGenerator.clear
end
# ...
end
Together with 250 country codes defined in Faker it should guarantee that Faker::UniqueGenerator::RetryLimitExceeded
won’t be raised. It should :)
The solution
After debugging it turned out that the problem was in a completly different place. One of the remaining specs was changing I18n.locale
settings to :pl
.
context 'when changing locale' do
before { I18n.locale = :pl }
it 'sets correct formatting' do
expect(Money.from_amount(123_456.78, 'USD').format).to eq('$123 456,78')
end
end
And that was the mistake because I18n.locale
changes the global state. As a result, all the remaining specs executed after that one were using Polish locale.
But how that can generate Faker::UniqueGenerator::RetryLimitExceeded
exception?
As the locale has changed to :pl
, Faker started to use Polish locale, and in the Polish translation, country_code
contains only one code: PL
. When two countries were created in one context, using Country
factory, Faker was raising an exception because there was only one country code to use. Together with RSpec configured to run specs in random order, it was generating random false negatives.
The specs were updated not to change the locale, but execute the code in selected locale scope.
context 'when changing locale' do
let(:locale) { :pl }
it 'sets correct formatting' do
I18n.with_locale(locale) do
expect(Money.from_amount(123_456.78, 'USD').format).to eq('$123 456,78')
end
end
end
Summary
I18n.locale
chagnes the global state which is not reset between the specs. The correct way to write the test and avoid the side effects is to use I18n.with_locale
to execute the code in selected locale scope.