Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. SeatAvailability.last_minute_booking? calls restaurant.determine_min_booking_time(service_type: nil) (lib/model_ext/restaurants/instance_methods.rb). With no package and no service type, that method ignores restaurant.min_booking_time and returns AdminSetting.default_dine_in_min_booking_time_in_advance (default: 30).
  2. spec/models/restaurant_spec.rb (#determine_min_booking_time block) had AdminSetting.default_dine_in_min_booking_time_in_advance = 60 in a before block.
  3. When restaurant_spec ran before seat_availability_spec in the same CI shard (random RSpec ordering), the cached 60 leaked, so the 50-minute test case became 50 <= 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"
    
  • 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.