Class: Postburner::TimeTravelTestQueue

Inherits:
InlineTestQueue show all
Extended by:
TimeHelpers
Defined in:
lib/postburner/strategies/time_travel_test_queue.rb

Overview

Note:

Requires ActiveSupport::Testing::TimeHelpers (Rails testing framework)

Note:

Time is stubbed globally during execution - side effects may occur

Note:

Multiple scheduled jobs execute in queue order, not scheduled order

Test queue strategy with automatic time travel for scheduled jobs.

This strategy executes jobs inline/synchronously like InlineTestQueue, but automatically uses time travel for scheduled jobs instead of raising exceptions. When a job has a future run_at, it travels to that time, executes the job, then returns to the present.

## When to Use TimeTravelTestQueue

Choose this strategy for test environments where you want:

  • **Automatic time management:** No need to manually call ‘travel_to`

  • **Simple scheduled job testing:** Test delayed/scheduled jobs without boilerplate

  • **Fast tests:** Jobs execute immediately regardless of schedule

  • **No Beanstalkd:** Tests run without external dependencies

  • **Convenience over control:** Less explicit but easier to use than InlineTestQueue

This is ideal for integration/feature tests where you want to verify that scheduled jobs execute correctly without managing time travel yourself. The tradeoff is less explicit control over timing compared to InlineTestQueue.

## Strategy Behavior

  • Execution: Synchronous/inline (no Beanstalkd required)

  • **Testing mode:** Returns true

  • **Premature execution:** Automatically travels to run_at and executes

  • Beanstalkd: Not used (bkid remains nil)

  • **Time travel:** Automatic using ActiveSupport::Testing::TimeHelpers

  • **Execution order:** Jobs may execute out of intended chronological order

## How Time Travel Works

When a job with future run_at is queued:

  1. Detects job.run_at > Time.zone.now

  2. Calls ‘travel_to(job.run_at)` to stub global time

  3. Executes job at the scheduled time (Time.zone.now == job.run_at)

  4. Returns to present time after execution

  5. Job timestamps reflect the scheduled time, not actual time

## Usage

Examples:

Explicitly activate TimeTravelTestQueue

Postburner.time_travel_test_strategy!
job = MyJob.create!(args: { user_id: 123 })
job.queue!(delay: 1.hour)
# Job executes immediately at scheduled time
assert job.reload.processed_at

Scheduled jobs execute automatically

Postburner.time_travel_test_strategy!
job = SendReminderEmail.create!(args: { user_id: 123 })

# Job executes immediately despite 2-day delay
job.queue!(delay: 2.days)

# Timestamps reflect the scheduled time
assert_equal 2.days.from_now.to_i, job.reload.run_at.to_i
assert_not_nil job.processed_at

Multiple jobs execute in queue order, not schedule order

Postburner.time_travel_test_strategy!

job1 = MyJob.create!(args: { id: 1 })
job2 = MyJob.create!(args: { id: 2 })

job1.queue!(delay: 2.days)  # Executes first (queued first)
job2.queue!(delay: 1.hour)  # Executes second (queued second)

# Both are processed, but job1 executed before job2
# despite job2 having an earlier scheduled time

Testing feature with scheduled job

test "sends reminder email after 24 hours" do
  Postburner.time_travel_test_strategy!

  user = users(:john)
  reminder = ReminderJob.create!(args: { user_id: user.id })
  reminder.queue!(delay: 24.hours)

  # Job executes immediately at scheduled time
  assert reminder.reload.processed_at
  assert_emails 1
end

See Also:

Class Method Summary collapse

Methods included from TimeHelpers

travel_to

Methods inherited from InlineTestQueue

handle_premature_perform, testing

Methods inherited from StrictQueue

handle_perform!, handle_premature_perform, testing

Class Method Details

.insert(job, options = {}) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Executes job inline with automatic time travel for scheduled jobs.

Called automatically via after_save_commit hook when Job#queue! is invoked. If the job has a future run_at, travels to that time before execution. Otherwise executes immediately.

Uses recursion detection to prevent infinite loops when jobs create and enqueue other jobs (e.g., schedule callbacks). Jobs enqueued while already inside a perform! call are queued but not executed immediately.

Parameters:

  • job (Postburner::Job)

    The job to execute

  • options (Hash) (defaults to: {})

    Unused in test mode

Returns:

  • (Hash)

    Status hash with :status => ‘INLINE’, :id => nil

Raises:

  • (RuntimeError)

    if ActiveSupport::Testing::TimeHelpers not available



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/postburner/strategies/time_travel_test_queue.rb', line 121

def insert(job, options = {})
  # Detect recursion: if we're already performing a job, don't execute
  # the newly enqueued job immediately. This prevents infinite loops
  # when job callbacks (like schedule_next_execution) enqueue new jobs.
  if Thread.current[:postburner_performing]
    return { status: 'INLINE_DEFERRED', id: nil }
  end

  begin
    Thread.current[:postburner_performing] = true

    # If job has a future run_at, travel to that time for execution
    if job.run_at && job.run_at > Time.current
      travel_to(job.run_at) do
        job.perform!(job.args)
      end
    else
      # No future run_at, execute normally
      job.perform!(job.args)
    end
  ensure
    Thread.current[:postburner_performing] = false
  end

  # Return format matching Beanstalkd response (symbol keys)
  { status: 'INLINE', id: nil }
end