Class: RuboCop::Cop::DevDoc::Test::RequireGlibTravel

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/dev_doc/test/require_glib_travel.rb

Overview

Controller tests must run inside a glib_travel block.

Rationale

Per the testing best practice ("Time Travel"), tests should always run inside a glib_travel block — even when they do not appear time-sensitive. Controller tests integrate HTTP, the database, and the clock (timestamps, expiry windows, token validity, rate limits), so they are the highest-leverage place to enforce it. A controller test that does not freeze time passes today and flakes or regresses the moment an ordering, expiry, or TTL boundary shifts under it.

The block form auto-returns to real time at the end of the test, so no teardown is needed:

test 'shows the row' do
glib_travel(FROZEN_TIME) do
  get items_url(format: :json)
  assert_response :success
end
end

This cop is a presence check — it only asks "did you freeze time?". Whether you used the block form (vs. the leak-prone bare anchor) is enforced separately by DevDoc/Test/RequireGlibTravelBlock, and glib_travel_freeze vs. glib_travel by AvoidGlibTravelFreeze.

What is NOT flagged

  • Tests that already call glib_travel or glib_travel_freeze.
  • Non-controller test files — the cop is scoped to test/controllers/** via Include, where the time-sensitivity risk is highest. (The best practice recommends the block form for every test; this cop enforces the subset with the strongest evidence.)

Inline disable

For the rare controller test that is genuinely time-independent and the block adds noise, add a rubocop:disable DevDoc/Test/RequireGlibTravel comment to the test's opening line (test '...' do or def test_*) with a written reason.

Examples:

# bad
test 'shows the row' do
  get items_url(format: :json)
  assert_response :success
end

# good
test 'shows the row' do
  glib_travel(FROZEN_TIME) do
    get items_url(format: :json)
    assert_response :success
  end
end

# good (method-style test — also covered)
def test_shows_the_row
  glib_travel(FROZEN_TIME) do
    get items_url(format: :json)
  end
end

Constant Summary collapse

MSG =
'Wrap this test in a `glib_travel` block — controller tests integrate ' \
'HTTP, DB, and time, so the frozen time must be deterministic. See the ' \
'testing best practice ("Time Travel"). Disable with a written reason ' \
'only if the test is genuinely time-independent.'.freeze

Instance Method Summary collapse

Instance Method Details

#calls_glib_time_helper?(node) ⇒ Object

Any glib time helper satisfies the presence check. glib_travel_freeze counts as "froze time" here — its form is policed by AvoidGlibTravelFreeze, not by this cop.



80
81
82
# File 'lib/rubocop/cop/dev_doc/test/require_glib_travel.rb', line 80

def_node_search :calls_glib_time_helper?, <<~PATTERN
  (send nil? {:glib_travel :glib_travel_freeze} ...)
PATTERN

#on_block(node) ⇒ Object



84
85
86
87
88
# File 'lib/rubocop/cop/dev_doc/test/require_glib_travel.rb', line 84

def on_block(node)
  return unless test_block?(node)

  check_test(node, node.send_node.loc.selector)
end

#on_def(node) ⇒ Object

Minitest also allows method-style tests (def test_shows_the_row); those are the same kind of controller test and must freeze time too.



92
93
94
95
96
# File 'lib/rubocop/cop/dev_doc/test/require_glib_travel.rb', line 92

def on_def(node)
  return unless test_method?(node)

  check_test(node, node.loc.name)
end

#test_block?(node) ⇒ Object



72
73
74
# File 'lib/rubocop/cop/dev_doc/test/require_glib_travel.rb', line 72

def_node_matcher :test_block?, <<~PATTERN
  (block (send nil? :test ...) _args _body)
PATTERN