Flaky Specs: AdminSetting Cache Leak (rails-settings-cached)
TL;DR
Never assign AdminSetting.some_field = value in a spec. The write goes through
rails-settings-cached, which stores the value in Rails.cache — and the test
cache store is a file store (config/environments/test.rb):
config.cache_store = :file_store, Rails.root.join('tmp', 'cache', "paralleltests#{ENV['TEST_ENV_NUMBER']}")
use_transactional_fixtures = true rolls back the DB row, but not the cache
entry. Every spec that runs later in the same process (or even a later local
run, since tmp/cache persists) silently reads the leaked value.
Use a stub instead:
# ❌ BAD: poisons Rails.cache for every spec that runs afterwards
before do
AdminSetting.default_dine_in_min_booking_time_in_advance = 60
end
# ✅ GOOD: scoped to the example, no cache write
before do
allow(AdminSetting).to receive(:default_dine_in_min_booking_time_in_advance).and_return(60)
end
Case study: seat_availability_spec “flaky timezone” failure (PR #8059)
CI intermittently failed with:
SeatAvailability.last_minute_booking? when requested dining time is more than
restaurant.min_booking_time returns false
expected: falsey value
got: true
It looked timezone-related (failing shards showed random Zonebie timezones like
Canberra), but the spec already pins Asia/Bangkok with Time.use_zone and
the restaurant factory sets time_zone: 'Asia/Bangkok' — every instant in the
test is fully pinned. The timezone was a red herring.
The actual chain:
SeatAvailability.last_minute_booking?callsrestaurant.determine_min_booking_time(service_type: nil)(lib/model_ext/restaurants/instance_methods.rb). With no package and no service type, that method ignoresrestaurant.min_booking_timeand returnsAdminSetting.default_dine_in_min_booking_time_in_advance(default: 30).spec/models/restaurant_spec.rb(#determine_min_booking_timeblock) hadAdminSetting.default_dine_in_min_booking_time_in_advance = 60in abeforeblock.- When
restaurant_specran beforeseat_availability_specin the same CI shard (random RSpec ordering), the cached60leaked, so the 50-minute test case became50 <= 60 → last_minute_booking? == true→ failure.
Fixed in hh-server#8059
(commit 5d3712fd84) by stubbing the setting in both specs.
How to recognize this failure mode
-
A spec asserting time/quantity thresholds fails intermittently on CI but passes locally and in isolation (
rspec <file>alone is green). -
The threshold under test ultimately comes from
AdminSetting— check the code path: many “restaurant-level” values fall back to AdminSetting defaults. -
The failure correlates with which specs shared the process, not with the Zonebie timezone printed at the top of the log. Reproduce with:
bundle exec rspec spec/models/restaurant_spec.rb spec/my_lib/seat_availability_spec.rb --order defined -
Grep for the poison source:
grep -rEn "AdminSetting\.[a-z_]+ = " spec/ | grep -v "allow"
Related
- Timezone Testing Strategy — for failures that are timezone-related (Zonebie). Check that first, but remember this page when the spec is already timezone-pinned and still flakes.