Module: Zizq::Test

Defined in:
lib/zizq/test.rb,
lib/zizq/test/client.rb

Overview

Test-mode helpers. Activated by setting ‘c.test_mode = true` in a `Zizq.configure` block; `Zizq.client` then lazily resolves to a `Zizq::Test::Client` that buffers enqueues instead of dispatching.

Typical use in a test helper:

Zizq.configure do |c|
  c.test_mode = true
end

class MyTestCase
  setup { Zizq::Test.reset! }
end

In a test:

def 
  # Default buffered mode — assert what was enqueued.
  SignupService.new.run
  assert_equal 2, Zizq::Test.client.pending_jobs.size

  # Drain whatever's pending (handler re-enqueues fall through
  # naturally — drain loops until pending is empty).
  Zizq::Test.dispatch_enqueued_jobs

  # Block form: run the work, then drain. Matches ActiveJob's
  # `perform_enqueued_jobs do ... end`.
  Zizq::Test.dispatch_enqueued_jobs { SignupService.new.run }

  # Filter by queue and/or type when only a subset should fire.
  Zizq::Test.dispatch_enqueued_jobs(queue: "emails")
  Zizq::Test.dispatch_enqueued_jobs(type: SendEmailJob)
end

Defined Under Namespace

Classes: Client

Class Method Summary collapse

Class Method Details

.clientObject

The active test client. Raises if test mode is not enabled —better to fail loudly than return a stale or wrong client.



60
61
62
63
64
65
66
# File 'lib/zizq/test.rb', line 60

def self.client #: () -> Client
  unless Zizq.configuration.test_mode
    raise Client::NotSupported,
      "Zizq.configuration.test_mode is not enabled; Zizq::Test.client has nothing to manage."
  end
  Zizq.client #: Client
end

.disable!Object

Switch back to the real client. The buffered state is dropped along with the test client (the next ‘Zizq.client` access builds a fresh `Zizq::Client`).



54
55
56
# File 'lib/zizq/test.rb', line 54

def self.disable! #: () -> void
  Zizq.configure { |c| c.test_mode = false }
end

.dispatch_enqueued_jobs(**filters, &block) ⇒ Object

Dispatch pending jobs through the configured dequeue middleware chain (‘Zizq.configuration.dequeue_middleware` — same path the real worker uses, so any registered middlewares run in tests too), looping until no more pending entries match the filters.

  • No block: drain whatever’s pending now.

  • With block: yield first (test code enqueues), then drain.

  • ‘only_queues:` / `except_queues:` — String or Array of Strings.

  • ‘only_types:` / `except_types:` — String, Class, or Array of those (Class names match the serialized-format `type` via `.to_s`, so passing an ActiveJob class works directly).

  • ‘filter:` — a lambda `->(job)` returning truthy to keep an entry. Defaults to “pass all”. Combines with the named filters via AND.

Returns the number of jobs dispatched. A block exception propagates without draining; a handler exception during drain transitions that entry to ‘dead` and re-raises.



92
93
94
95
# File 'lib/zizq/test.rb', line 92

def self.dispatch_enqueued_jobs(**filters, &block) #: (**untyped) ?{ () -> void } -> Integer
  yield if block
  client.drain(**filters)
end

.enable!Object

Switch Zizq into test mode. After this, ‘Zizq.client` resolves to a `Zizq::Test::Client` that buffers enqueues in memory. Typically called once in a test helper.



47
48
49
# File 'lib/zizq/test.rb', line 47

def self.enable! #: () -> void
  Zizq.configure { |c| c.test_mode = true }
end

.enqueued?(job_class, *args, **kwargs) ⇒ Boolean

Was a job of ‘job_class` enqueued (optionally with matching args)?

Zizq::Test.enqueued?(SendEmailJob)                            # any args
Zizq::Test.enqueued?(SendEmailJob, 42, template: "welcome")   # exact args

With no args, matches by class/type only. With args/kwargs, uses the class’s own ‘zizq_serialize` to compute the expected payload — so it works for both `Zizq::Job` and AJ classes (AJ’s volatile fields like ‘job_id` are ignored, only the `arguments` subset is compared).

For anything fuzzier (matchers, subset matching, custom predicates), drop down to ‘client.enqueued_jobs(only_types: …, filter: ->(job) { … })`.

Returns:

  • (Boolean)


132
133
134
# File 'lib/zizq/test.rb', line 132

def self.enqueued?(job_class, *args, **kwargs) #: (Class, *untyped, **untyped) -> bool
  enqueued_count(job_class, *args, **kwargs) > 0
end

.enqueued_count(job_class, *args, **kwargs) ⇒ Object

How many times was a job of ‘job_class` enqueued (optionally with matching args)? Same argument semantics as `#enqueued?`.



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/zizq/test.rb', line 138

def self.enqueued_count(job_class, *args, **kwargs) #: (Class, *untyped, **untyped) -> Integer
  type = job_class.to_s
  return enqueued_raw_count(type: type) if args.empty? && kwargs.empty?

  unless job_class.respond_to?(:zizq_serialize)
    raise ArgumentError,
      "#{job_class} doesn't implement zizq_serialize — " \
      "include Zizq::Job or extend Zizq::ActiveJobConfig, " \
      "or use Zizq::Test.enqueued_raw? for raw enqueues."
  end

  expected = job_class.zizq_serialize(*args, **kwargs)
  client.enqueued_jobs(only_types: type).count do |job|
    payloads_equivalent?(expected, job.payload)
  end
end

.enqueued_raw?(queue: nil, type: nil, payload: nil) ⇒ Boolean

Was a raw job (queue + type + payload) enqueued? Each kwarg is optional; unspecified means “don’t filter on this axis.”

Zizq::Test.enqueued_raw?(type: "send_email")
Zizq::Test.enqueued_raw?(type: "send_email", payload: { user_id: 42 })
Zizq::Test.enqueued_raw?(queue: "emails", type: "send_email")

Returns:

  • (Boolean)


161
162
163
# File 'lib/zizq/test.rb', line 161

def self.enqueued_raw?(queue: nil, type: nil, payload: nil) #: (?queue: String?, ?type: String?, ?payload: untyped) -> bool
  enqueued_raw_count(queue: queue, type: type, payload: payload) > 0
end

.enqueued_raw_count(queue: nil, type: nil, payload: nil) ⇒ Object

How many raw jobs match (queue + type + payload)? Same argument semantics as ‘#enqueued_raw?`.



167
168
169
170
171
172
173
# File 'lib/zizq/test.rb', line 167

def self.enqueued_raw_count(queue: nil, type: nil, payload: nil) #: (?queue: String?, ?type: String?, ?payload: untyped) -> Integer
  filters = {}
  filters[:only_queues] = queue if queue
  filters[:only_types]  = type  if type
  filters[:filter]      = ->(job) { job.payload == payload } unless payload.nil?
  client.enqueued_jobs(**filters).size
end

.payloads_equivalent?(expected, actual) ⇒ Boolean

Heuristic payload comparison that handles both ‘Zizq::Job`’s serialized format (‘=> […], “kwargs” => {…}`) and ActiveJob’s (‘=> …, “arguments” => […], “job_id” => …, “enqueued_at” => …, …`). The AJ shape always has an `“arguments”` key while `Zizq::Job`’s doesn’t, so we use that to pick whether to compare the full hash or only the ‘arguments` subset (dropping AJ’s volatile per-enqueue fields).

Returns:

  • (Boolean)


182
183
184
185
186
187
188
# File 'lib/zizq/test.rb', line 182

def self.payloads_equivalent?(expected, actual) #: (untyped, untyped) -> bool
  if expected.is_a?(Hash) && expected.key?("arguments")
    actual.is_a?(Hash) && actual["arguments"] == expected["arguments"]
  else
    actual == expected
  end
end

.reset!Object

Reset buffered state between tests. Keeps the configured ‘test_mode` flag.



70
71
72
# File 'lib/zizq/test.rb', line 70

def self.reset! #: () -> void
  client.clear!
end