rspec-sidekiq 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47fe73bb71c8962a243f7128bffe68ce0ec5a4c644f1e1bb597723980af806b0
4
- data.tar.gz: 0fdfbac81db581526c48fb58741e56eaee011507235538df4a48a66584477c74
3
+ metadata.gz: 829c5fcd191be48f662173034da4820f8dd5d405c85a865cc272048b1cee3b95
4
+ data.tar.gz: 07d430fa03b32c89f6d859c4fc44034ecc517d4eeec623d552854d5deaa142f4
5
5
  SHA512:
6
- metadata.gz: 01a72c5aa64da2033f4efc53000fd52241d120d8c761dd095272b6111807d99ae50746428c5a222c670a8ff573b39a911faeb1a0b3e21b93bfedaa8068f60ec6
7
- data.tar.gz: 6d052d8dd15a05a002cbc30a26e81aa61621cc9c8e8ec1789e4b6fdb7174cb74727c81baf9cdbf1727eed951f18b65397718800f90d5adfc067dc5a6d803844f
6
+ metadata.gz: c2a20b0ba6848989deea4759a0dfaec982c05f9543b60d42343d4b7d69e22e38f175596e53a00f911ad94732a9b6fca7904703f60b35bd014b00180d905b266a
7
+ data.tar.gz: bc0b993e2fe9350b2f72c8b5e7711b2c642a8098566edebfef5dd00b2f3a5aadef6a014de61831ff05a255376824e441076e29ff1ca0491f56e5fce98d52f57b
data/CHANGES.md CHANGED
@@ -1,6 +1,15 @@
1
1
  Unreleased
2
2
  ---
3
3
 
4
+ 5.3.0
5
+ ---
6
+ * Add `have_job` matcher and named queue stubs (`stub_named_queues`) (#279)
7
+ * Add `have_job_option` and `have_job_options` matchers for job option assertions (#278)
8
+ * Add `respond_to_missing?` to `NullBatch` for proper method reflection (#275)
9
+ * Support RSpec matchers with `at` and `in` evaluators (#272)
10
+ * Allow `on` to accept either a `String` or `Symbol` queue name (#273)
11
+ * Avoid `require "sidekiq/testing"` deprecation on Sidekiq 8.1.1+ (#280)
12
+
4
13
  5.2.0
5
14
  ---
6
15
  * Add `never` matcher for aliasing `exactly(0)` (#256)
data/README.md CHANGED
@@ -3,21 +3,26 @@
3
3
 
4
4
  Simple testing of Sidekiq jobs via a collection of matchers and helpers.
5
5
 
6
- [Jump to Matchers »](#matchers) | [Jump to Helpers »](#helpers)
6
+ [Jump to Matchers »](#matchers) | [Jump to Helpers »](#helpers)
7
7
 
8
8
  ## Installation
9
+
9
10
  ```ruby
10
11
  # Gemfile
11
12
  group :test do
12
13
  gem 'rspec-sidekiq'
13
14
  end
14
15
  ```
15
- rspec-sidekiq requires ```sidekiq/testing``` by default so there is no need to include the line ```require "sidekiq/testing"``` inside your ```spec_helper.rb```.
16
16
 
17
- *IMPORTANT! This has the effect of not pushing enqueued jobs to Redis but to a ```job``` array to enable testing ([see the FAQ & Troubleshooting Wiki page][rspec_sidekiq_wiki_faq_&_troubleshooting]). Thus, only include ```gem "rspec-sidekiq"``` in environments where this behaviour is required, such as the ```test``` group.*
17
+ rspec-sidekiq requires `sidekiq/testing` by default so there is no need to include the line `require "sidekiq/testing"` inside your `spec_helper.rb`.
18
+
19
+ > [!IMPORTANT]
20
+ > This has the effect of not pushing enqueued jobs to Redis but to a `job` array to enable testing ([see the FAQ & Troubleshooting Wiki page][rspec_sidekiq_wiki_faq_&_troubleshooting]). Thus, only include `gem "rspec-sidekiq"` in environments where this behaviour is required, such as the `test` group.*
18
21
 
19
22
  ## Configuration
20
- If you wish to modify the default behaviour, add the following to your ```spec_helper.rb``` file
23
+
24
+ If you wish to modify the default behaviour, add the following to your `spec_helper.rb` file
25
+
21
26
  ```ruby
22
27
  RSpec::Sidekiq.configure do |config|
23
28
  # Clears all job queues before each example
@@ -32,16 +37,20 @@ end
32
37
  ```
33
38
 
34
39
  ## Matchers
35
- * [```enqueue_sidekiq_job```](#enqueue_sidekiq_job)
36
- * [```have_enqueued_sidekiq_job```](#have_enqueued_sidekiq_job)
37
- * [```be_processed_in```](#be_processed_in)
38
- * [```be_retryable```](#be_retryable)
39
- * [```save_backtrace```](#save_backtrace)
40
- * [```be_unique```](#be_unique)
41
- * [```be_expired_in```](#be_expired_in)
42
- * [```be_delayed``` (_deprecated_)](#be_delayed)
43
40
 
44
- ### ```enqueue_sidekiq_job```
41
+ * [`enqueue_sidekiq_job`](#enqueue_sidekiq_job)
42
+ * [`have_enqueued_sidekiq_job`](#have_enqueued_sidekiq_job)
43
+ * [`have_job`](#have_job)
44
+ * [`have_job_option`](#have_job_option)
45
+ * [`have_job_options`](#have_job_options)
46
+ * [`be_processed_in`](#be_processed_in)
47
+ * [`be_retryable`](#be_retryable)
48
+ * [`save_backtrace`](#save_backtrace)
49
+ * [`be_unique`](#be_unique)
50
+ * [`be_expired_in`](#be_expired_in)
51
+ * [`be_delayed` (_deprecated_)](#be_delayed)
52
+
53
+ ### `enqueue_sidekiq_job`
45
54
 
46
55
  *Describes that the block should enqueue a job*. Optionally specify the
47
56
  specific job class, arguments, timing, and other context
@@ -108,7 +117,7 @@ expect do
108
117
  end.to enqueue_sidekiq_job(AwesomeJob).and enqueue_sidekiq_job(OtherJob)
109
118
  ```
110
119
 
111
- ### ```have_enqueued_sidekiq_job```
120
+ ### `have_enqueued_sidekiq_job`
112
121
 
113
122
  Describes that there should be an enqueued job (with the specified arguments):
114
123
 
@@ -120,6 +129,7 @@ expect(AwesomeJob).to have_enqueued_sidekiq_job('Awesome', true)
120
129
  ```
121
130
 
122
131
  You can use the built-in RSpec args matchers too:
132
+
123
133
  ```ruby
124
134
  AwesomeJob.perform_async({"something" => "Awesome", "extra" => "stuff"})
125
135
 
@@ -146,6 +156,7 @@ expect(AwesomeJob).to have_enqueued_sidekiq_job.at_most(:thrice)
146
156
  ```
147
157
 
148
158
  Likewise, specify what should be in the context:
159
+
149
160
  ```ruby
150
161
  AwesomeJob.set(trace_id: "something").perform_async
151
162
 
@@ -162,6 +173,7 @@ AwesomeJob.perform_at time, 'Awesome', true
162
173
  # test with...
163
174
  expect(AwesomeJob).to have_enqueued_sidekiq_job('Awesome', true).at(time)
164
175
  ```
176
+
165
177
  ```ruby
166
178
  AwesomeJob.perform_in 5.minutes, 'Awesome', true
167
179
  # test with...
@@ -212,8 +224,88 @@ expect(Sidekiq::Worker).to have_enqueued_sidekiq_job(
212
224
  )
213
225
  ```
214
226
 
215
- ### ```be_processed_in```
227
+ ### `have_job`
228
+
229
+ Describes that a Sidekiq set (ScheduledSet, RetrySet, DeadSet) should contain a job. Typically used together with [`stub_named_queues`](#stub_named_queues).
230
+
231
+ ```ruby
232
+ # Match any job in the set
233
+ expect(Sidekiq::ScheduledSet.new).to have_job
234
+
235
+ # Match a specific job class
236
+ expect(Sidekiq::ScheduledSet.new).to have_job(AwesomeJob)
237
+
238
+ # With specific arguments
239
+ expect(Sidekiq::ScheduledSet.new).to have_job(AwesomeJob).with('arg')
240
+
241
+ # A specific number of times
242
+ expect(Sidekiq::ScheduledSet.new).to have_job(AwesomeJob).once
243
+ expect(Sidekiq::ScheduledSet.new).to have_job(AwesomeJob).twice
244
+ expect(Sidekiq::ScheduledSet.new).to have_job(AwesomeJob).exactly(3).times
245
+ expect(Sidekiq::ScheduledSet.new).to have_job(AwesomeJob).at_least(1).time
246
+ expect(Sidekiq::ScheduledSet.new).to have_job(AwesomeJob).at_most(2).times
247
+ ```
248
+
249
+ #### Testing retry jobs
250
+
251
+ ```ruby
252
+ expect(Sidekiq::RetrySet.new)
253
+ .to have_job(AwesomeJob)
254
+ .with('arg')
255
+ .with_error('something went wrong')
256
+ .with_error_class(RuntimeError)
257
+ .with_retry_count(2)
258
+ ```
259
+
260
+ #### Testing dead jobs
261
+
262
+ ```ruby
263
+ expect(Sidekiq::DeadSet.new)
264
+ .to have_job(AwesomeJob)
265
+ .with('arg')
266
+ .died_within(1.hour)
267
+ ```
268
+
269
+ #### Scanning with a pattern
270
+
271
+ Use `.scanning(pattern)` to filter by a glob-style pattern matched against the job's JSON representation:
272
+
273
+ ```ruby
274
+ expect(Sidekiq::ScheduledSet.new).to have_job(AwesomeJob).scanning("*some_trace_id*")
275
+ ```
276
+
277
+ ### `have_job_option`
278
+
279
+ Describes a single Sidekiq option set on a job class:
280
+
281
+ ```ruby
282
+ class AwesomeJob
283
+ include Sidekiq::Job
284
+ sidekiq_options retry: 5, queue: 'critical', dead: false
285
+ end
286
+
287
+ expect(AwesomeJob).to have_job_option(:retry, 5)
288
+ expect(AwesomeJob).to have_job_option(:queue, 'critical')
289
+ expect(AwesomeJob).to have_job_option(:dead, false)
290
+ ```
291
+
292
+ ### `have_job_options`
293
+
294
+ Describes multiple Sidekiq options set on a job class:
295
+
296
+ ```ruby
297
+ class AwesomeJob
298
+ include Sidekiq::Job
299
+ sidekiq_options retry: 5, queue: 'critical', backtrace: true
300
+ end
301
+
302
+ expect(AwesomeJob).to have_job_options(retry: 5, queue: 'critical', backtrace: true)
303
+ ```
304
+
305
+ ### `be_processed_in`
306
+
216
307
  *Describes the queue that a job should be processed in*
308
+
217
309
  ```ruby
218
310
  sidekiq_options queue: :download
219
311
  # test with...
@@ -221,12 +313,14 @@ expect(AwesomeJob).to be_processed_in :download # or
221
313
  it { is_expected.to be_processed_in :download }
222
314
  ```
223
315
 
224
- ### ```be_retryable```
316
+ ### `be_retryable`
317
+
225
318
  *Describes if a job should retry when there is a failure in its execution*
226
319
 
227
320
  Note: this only tests against the `retry` option in the job's Sidekiq options.
228
321
  To test an enqueued job's retry, i.e. `AwesomeJob.set(retry: 5)`, use
229
322
  `with_context`
323
+
230
324
  ```ruby
231
325
  sidekiq_options retry: 5
232
326
  # test with...
@@ -240,8 +334,10 @@ expect(AwesomeJob).to be_retryable false # or
240
334
  it { is_expected.to be_retryable false }
241
335
  ```
242
336
 
243
- ### ```save_backtrace```
337
+ ### `save_backtrace`
338
+
244
339
  *Describes if a job should save the error backtrace when there is a failure in its execution*
340
+
245
341
  ```ruby
246
342
  sidekiq_options backtrace: 5
247
343
  # test with...
@@ -257,13 +353,15 @@ it { is_expected.to_not save_backtrace } # or
257
353
  it { is_expected.to save_backtrace false }
258
354
  ```
259
355
 
260
- ### ```be_unique```
356
+ ### `be_unique`
261
357
 
262
- :warning: This is intended to for Sidekiq Enterprise unique job implementation.
263
- There is _limited_ support for Sidekiq Unique Jobs, but compatibility is not
264
- guaranteed.
358
+ > [!CAUTION]
359
+ > This is intended to for Sidekiq Enterprise unique job implementation.
360
+ > There is _limited_ support for Sidekiq Unique Jobs, but compatibility is not
361
+ > guaranteed.
265
362
 
266
363
  *Describes when a job should be unique within its queue*
364
+
267
365
  ```ruby
268
366
  sidekiq_options unique_for: 1.hour
269
367
  # test with...
@@ -277,16 +375,18 @@ it { is_expected.to be_unique.for(1.hour) }
277
375
 
278
376
  #### `until` sub-matcher
279
377
 
280
- :warning: This sub-matcher only works for Sidekiq Enterprise
378
+ > [!CAUTION]
379
+ > This sub-matcher only works for Sidekiq Enterprise
281
380
 
282
381
  ```ruby
283
382
  sidekiq_options unique_for: 1.hour, unique_until: :start
284
383
  it { is_expected.to be_unique.until(:start) }
285
384
  ```
286
385
 
386
+ ### `be_expired_in`
287
387
 
288
- ### ```be_expired_in```
289
388
  *Describes when a job should expire*
389
+
290
390
  ```ruby
291
391
  sidekiq_options expires_in: 1.hour
292
392
  # test with...
@@ -294,13 +394,14 @@ it { is_expected.to be_expired_in 1.hour }
294
394
  it { is_expected.to_not be_expired_in 2.hours }
295
395
  ```
296
396
 
297
- ### ```be_delayed```
397
+ ### `be_delayed`
298
398
 
299
399
  **This matcher is deprecated**. Use of it with Sidekiq 7+ will raise an error.
300
400
  Sidekiq 7 [dropped Delayed
301
401
  Extensions](https://github.com/sidekiq/sidekiq/issues/5076).
302
402
 
303
403
  *Describes a method that should be invoked asynchronously (See [Sidekiq Delayed Extensions][sidekiq_wiki_delayed_extensions])*
404
+
304
405
  ```ruby
305
406
  Object.delay.is_nil? # delay
306
407
  expect(Object.method :is_nil?).to be_delayed
@@ -323,6 +424,7 @@ expect(MyMailer.instance_method :some_mail).to be_delayed
323
424
  ```
324
425
 
325
426
  ## Example matcher usage
427
+
326
428
  ```ruby
327
429
  require 'spec_helper'
328
430
 
@@ -341,9 +443,56 @@ end
341
443
  ```
342
444
 
343
445
  ## Helpers
446
+
447
+ * [`stub_named_queues`](#stub_named_queues)
344
448
  * [Batches (Sidekiq Pro) _experimental_](#batches)
345
449
  * [`within_sidekiq_retries_exhausted_block`](#within_sidekiq_retries_exhausted_block)
346
450
 
451
+ ### `stub_named_queues`
452
+
453
+ If you need to test jobs in Sidekiq's named sets (ScheduledSet, RetrySet, DeadSet) without
454
+ a Redis instance, opt-in with `stub_named_queues: true`. This replaces those sets with
455
+ in-memory implementations backed by a `JobStore`.
456
+
457
+ ```ruby
458
+ RSpec.describe "Scheduled jobs", stub_named_queues: true do
459
+ it "schedules a job" do
460
+ AwesomeJob.perform_at(1.hour.from_now, 'arg')
461
+
462
+ expect(Sidekiq::ScheduledSet.new).to have_job(AwesomeJob).with('arg')
463
+ end
464
+
465
+ it "tracks retry jobs via the job store" do
466
+ store = RSpec::Sidekiq::NamedQueues.job_store
467
+ store.add_retry(
468
+ "class" => "AwesomeJob",
469
+ "args" => ["arg"],
470
+ "error_message" => "boom",
471
+ "error_class" => "RuntimeError",
472
+ "retry_count" => 1
473
+ )
474
+
475
+ expect(Sidekiq::RetrySet.new)
476
+ .to have_job(AwesomeJob)
477
+ .with_error('boom')
478
+ .with_retry_count(1)
479
+ end
480
+
481
+ it "tracks dead jobs via the job store" do
482
+ store = RSpec::Sidekiq::NamedQueues.job_store
483
+ store.add_dead(
484
+ "class" => "AwesomeJob",
485
+ "args" => ["arg"],
486
+ "failed_at" => Time.now.to_f
487
+ )
488
+
489
+ expect(Sidekiq::DeadSet.new)
490
+ .to have_job(AwesomeJob)
491
+ .died_within(1.minute)
492
+ end
493
+ end
494
+ ```
495
+
347
496
  ### Batches
348
497
 
349
498
  If you are using Sidekiq Batches ([Sidekiq Pro feature][sidekiq_wiki_batches]),
@@ -351,10 +500,10 @@ You can *opt-in* with `stub_batches` to make `rspec-sidekiq` mock the
351
500
  implementation (using a NullObject pattern). This enables testing without a
352
501
  Redis instance. Mocha and RSpec stubbing is supported here.
353
502
 
354
- :warning: **Caution**: Opting-in to this feature, while allowing you to test without
355
- having Redis, _does not_ provide the exact API that `Sidekiq::Batch` does. As
356
- such it can cause surprises.
357
-
503
+ > [!CAUTION]
504
+ > Opting-in to this feature, while allowing you to test without
505
+ > having Redis, _does not_ provide the exact API that `Sidekiq::Batch` does. As
506
+ > such it can cause surprises.
358
507
 
359
508
  ```ruby
360
509
  RSpec.describe "Using mocked batches", stub_batches: true do
@@ -375,6 +524,7 @@ end
375
524
  ```
376
525
 
377
526
  ### within_sidekiq_retries_exhausted_block
527
+
378
528
  ```ruby
379
529
  sidekiq_retries_exhausted do |msg|
380
530
  bar('hello')
@@ -386,11 +536,13 @@ FooClass.within_sidekiq_retries_exhausted_block {
386
536
  ```
387
537
 
388
538
  ## Testing
539
+
389
540
  ```
390
541
  bundle exec rspec
391
542
  ```
392
543
 
393
544
  ## Maintainers
545
+
394
546
  * [@wspurgin]
395
547
  * [@ydah]
396
548
 
@@ -400,18 +552,17 @@ bundle exec rspec
400
552
  * [@philostler]
401
553
 
402
554
  ## Contribute
555
+
403
556
  Please do! If there's a feature missing that you'd love to see then get in on the action!
404
557
 
405
558
  Issues/Pull Requests/Comments all welcome...
406
559
 
560
+ [@packrat386]: https://github.com/packrat386
561
+ [@philostler]: https://github.com/philostler
562
+ [@wspurgin]: https://github.com/wspurgin
563
+ [@ydah]: https://github.com/ydah
407
564
  [github_actions]: https://github.com/wspurgin/rspec-sidekiq/actions
408
565
  [github_actions_badge]: https://github.com/wspurgin/rspec-sidekiq/actions/workflows/main.yml/badge.svg
409
-
410
566
  [rspec_sidekiq_wiki_faq_&_troubleshooting]: https://github.com/wspurgin/rspec-sidekiq/wiki/FAQ-&-Troubleshooting
411
567
  [sidekiq_wiki_batches]: https://github.com/sidekiq/sidekiq/wiki/Batches
412
568
  [sidekiq_wiki_delayed_extensions]: https://github.com/sidekiq/sidekiq/wiki/Delayed-Extensions
413
-
414
- [@wspurgin]: https://github.com/wspurgin
415
- [@ydah]: https://github.com/ydah
416
- [@packrat386]: https://github.com/packrat386
417
- [@philostler]: https://github.com/philostler
@@ -10,6 +10,10 @@ if defined? Sidekiq::Batch
10
10
  def method_missing(*args, &block)
11
11
  self
12
12
  end
13
+
14
+ def respond_to_missing?(*_args)
15
+ true
16
+ end
13
17
  end
14
18
 
15
19
  ##
@@ -4,6 +4,10 @@ module Sidekiq
4
4
  module Worker
5
5
  module ClassMethods
6
6
  def within_sidekiq_retries_exhausted_block(user_msg = {}, exception = default_retries_exhausted_exception, &block)
7
+ unless sidekiq_retries_exhausted_block
8
+ raise ArgumentError, 'Define `sidekiq_retries_exhausted` before calling `within_sidekiq_retries_exhausted_block`'
9
+ end
10
+
7
11
  block.call
8
12
  sidekiq_retries_exhausted_block.call(default_retries_exhausted_message.merge(user_msg), exception)
9
13
  end
@@ -3,6 +3,54 @@
3
3
  module RSpec
4
4
  module Sidekiq
5
5
  module Matchers
6
+ # @api private
7
+ module CountExpectation
8
+ private
9
+
10
+ def set_expected_count(relativity, n)
11
+ n =
12
+ case n
13
+ when Integer then n
14
+ when :once then 1
15
+ when :twice then 2
16
+ when :thrice then 3
17
+ else raise ArgumentError, "Unsupported #{n} in '#{relativity} #{n}'. Use either an Integer, :once, :twice, or :thrice."
18
+ end
19
+ @expected_count = [relativity, n]
20
+ end
21
+
22
+ def count_message
23
+ case expected_count[0]
24
+ when :positive
25
+ "a"
26
+ when :exactly
27
+ expected_count[1]
28
+ else
29
+ "#{expected_count[0].to_s.gsub('_', ' ')} #{expected_count[1]}"
30
+ end
31
+ end
32
+ end
33
+
34
+ # @api private
35
+ module ArgumentNormalization
36
+ private
37
+
38
+ def normalize_arguments(args)
39
+ case args
40
+ when Array
41
+ args.map { |x| normalize_arguments(x) }
42
+ when Hash
43
+ args.each_with_object({}) do |(key, value), hash|
44
+ hash[key.to_s] = normalize_arguments(value)
45
+ end
46
+ when Symbol
47
+ args.to_s
48
+ else
49
+ args
50
+ end
51
+ end
52
+ end
53
+
6
54
  # @api private
7
55
  class JobOptionParser
8
56
  attr_reader :job
@@ -19,6 +67,8 @@ module RSpec
19
67
 
20
68
  def at_evaluator(value)
21
69
  return value.nil? if job["at"].to_s.empty?
70
+ return value.matches?(Time.at(job["at"])) if value.respond_to?(:matches?)
71
+
22
72
  value == Time.at(job["at"]).to_i
23
73
  end
24
74
 
@@ -50,17 +100,19 @@ module RSpec
50
100
  end
51
101
 
52
102
  def unwrapped_arguments
53
- args = job["args"]
54
-
55
- return deserialized_active_job_args if active_job?
103
+ @unwrapped_arguments ||= begin
104
+ args = job["args"]
56
105
 
57
- args
106
+ active_job? ? deserialized_active_job_args : args
107
+ end
58
108
  end
59
109
 
60
110
  private
61
111
 
62
112
  def active_job?
63
- if RSpec::Sidekiq.configuration.sidekiq_gte_8?
113
+ return @active_job if defined?(@active_job)
114
+
115
+ @active_job = if RSpec::Sidekiq.configuration.sidekiq_gte_8?
64
116
  job["class"] == "Sidekiq::ActiveJob::Wrapper"
65
117
  else
66
118
  job["class"] == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
@@ -108,12 +160,30 @@ module RSpec
108
160
  @context ||= job.except("args")
109
161
  end
110
162
 
163
+ def matches_arguments?(expected_args)
164
+ job_arguments.matches?(expected_args)
165
+ end
166
+
167
+ def matches_options?(expected_options)
168
+ job_option_parser.matches?(expected_options)
169
+ end
170
+
111
171
  def ==(other)
112
172
  super(other) unless other.is_a?(EnqueuedJob)
113
173
 
114
174
  jid == other.jid
115
175
  end
116
176
  alias_method :eql?, :==
177
+
178
+ private
179
+
180
+ def job_arguments
181
+ @job_arguments ||= JobArguments.new(job)
182
+ end
183
+
184
+ def job_option_parser
185
+ @job_option_parser ||= JobOptionParser.new(self)
186
+ end
117
187
  end
118
188
 
119
189
  class EnqueuedJobs
@@ -159,15 +229,11 @@ module RSpec
159
229
  end
160
230
 
161
231
  def arguments_matches?(job, arguments)
162
- job_arguments = JobArguments.new(job)
163
-
164
- job_arguments.matches?(arguments)
232
+ job.matches_arguments?(arguments)
165
233
  end
166
234
 
167
235
  def options_matches?(job, options)
168
- parser = JobOptionParser.new(job)
169
-
170
- parser.matches?(options)
236
+ job.matches_options?(options)
171
237
  end
172
238
 
173
239
  def unwrap_jobs(jobs)
@@ -180,6 +246,8 @@ module RSpec
180
246
  class Base
181
247
  include RSpec::Mocks::ArgumentMatchers
182
248
  include RSpec::Matchers::Composable
249
+ include CountExpectation
250
+ include ArgumentNormalization
183
251
 
184
252
  attr_reader :expected_arguments, :expected_options, :klass, :actual_jobs, :expected_count
185
253
 
@@ -195,12 +263,12 @@ module RSpec
195
263
  end
196
264
 
197
265
  def at(timestamp)
198
- @expected_options["at"] = timestamp.to_time.to_i
266
+ @expected_options["at"] = matcher?(timestamp) ? timestamp : timestamp.to_time.to_i
199
267
  self
200
268
  end
201
269
 
202
270
  def in(interval)
203
- @expected_options["at"] = (Time.now.to_f + interval.to_f).to_i
271
+ @expected_options["at"] = matcher?(interval) ? interval : (Time.now.to_f + interval.to_f).to_i
204
272
  self
205
273
  end
206
274
 
@@ -210,7 +278,7 @@ module RSpec
210
278
  end
211
279
 
212
280
  def on(queue)
213
- @expected_options["queue"] = queue
281
+ @expected_options["queue"] = normalize_arguments(queue)
214
282
  self
215
283
  end
216
284
 
@@ -274,18 +342,6 @@ module RSpec
274
342
  self
275
343
  end
276
344
 
277
- def set_expected_count(relativity, n)
278
- n =
279
- case n
280
- when Integer then n
281
- when :once then 1
282
- when :twice then 2
283
- when :thrice then 3
284
- else raise ArgumentError, "Unsupported #{n} in '#{relativity} #{n}'. Use either an Integer, :once, :twice, or :thrice."
285
- end
286
- @expected_count = [relativity, n]
287
- end
288
-
289
345
  def description
290
346
  "#{common_message} with arguments #{expected_arguments}"
291
347
  end
@@ -332,17 +388,6 @@ module RSpec
332
388
  raise NotImplementedError
333
389
  end
334
390
 
335
- def count_message
336
- case expected_count[0]
337
- when :positive
338
- "a"
339
- when :exactly
340
- expected_count[1]
341
- else
342
- "#{expected_count[0].to_s.gsub('_', ' ')} #{expected_count[1]}"
343
- end
344
- end
345
-
346
391
  def failure_message_when_negated
347
392
  message = ["expected not to #{common_message} but enqueued #{actual_jobs.count}"]
348
393
  message << " arguments: #{expected_arguments}" if expected_arguments.any?
@@ -354,18 +399,10 @@ module RSpec
354
399
  RSpec::Support::ObjectFormatter.format(thing)
355
400
  end
356
401
 
357
- def normalize_arguments(args)
358
- if args.is_a?(Array)
359
- args.map{ |x| normalize_arguments(x) }
360
- elsif args.is_a?(Hash)
361
- args.each_with_object({}) do |(key, value), hash|
362
- hash[key.to_s] = normalize_arguments(value)
363
- end
364
- elsif args.is_a?(Symbol)
365
- args.to_s
366
- else
367
- args
368
- end
402
+ private
403
+
404
+ def matcher?(object)
405
+ object.respond_to?(:matches?)
369
406
  end
370
407
  end
371
408
  end