rspec-sidekiq 3.0.3 → 5.1.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 +5 -5
- data/CHANGES.md +61 -0
- data/LICENSE +12 -0
- data/README.md +270 -94
- data/lib/rspec/sidekiq/batch.rb +34 -4
- data/lib/rspec/sidekiq/configuration.rb +29 -2
- data/lib/rspec/sidekiq/helpers/within_sidekiq_retries_exhausted_block.rb +5 -3
- data/lib/rspec/sidekiq/helpers.rb +2 -0
- data/lib/rspec/sidekiq/matchers/base.rb +368 -0
- data/lib/rspec/sidekiq/matchers/be_delayed.rb +19 -3
- data/lib/rspec/sidekiq/matchers/be_expired_in.rb +2 -0
- data/lib/rspec/sidekiq/matchers/be_processed_in.rb +2 -0
- data/lib/rspec/sidekiq/matchers/be_retryable.rb +2 -0
- data/lib/rspec/sidekiq/matchers/be_unique.rb +23 -1
- data/lib/rspec/sidekiq/matchers/enqueue_sidekiq_job.rb +91 -0
- data/lib/rspec/sidekiq/matchers/have_enqueued_sidekiq_job.rb +35 -0
- data/lib/rspec/sidekiq/matchers/save_backtrace.rb +2 -0
- data/lib/rspec/sidekiq/matchers.rb +16 -8
- data/lib/rspec/sidekiq/sidekiq.rb +3 -1
- data/lib/rspec/sidekiq/version.rb +3 -1
- data/lib/rspec-sidekiq.rb +4 -0
- metadata +146 -84
- data/.gitattributes +0 -22
- data/.gitignore +0 -2
- data/.rspec +0 -4
- data/.simplecov +0 -5
- data/Gemfile +0 -9
- data/lib/rspec/sidekiq/matchers/have_enqueued_job.rb +0 -183
- data/rspec-sidekiq.gemspec +0 -37
- data/spec/rspec/sidekiq/batch_spec.rb +0 -77
- data/spec/rspec/sidekiq/helpers/retries_exhausted_spec.rb +0 -40
- data/spec/rspec/sidekiq/matchers/be_delayed_spec.rb +0 -238
- data/spec/rspec/sidekiq/matchers/be_expired_in_spec.rb +0 -57
- data/spec/rspec/sidekiq/matchers/be_processed_in_spec.rb +0 -114
- data/spec/rspec/sidekiq/matchers/be_retryable_spec.rb +0 -129
- data/spec/rspec/sidekiq/matchers/be_unique_spec.rb +0 -115
- data/spec/rspec/sidekiq/matchers/have_enqueued_job_spec.rb +0 -228
- data/spec/rspec/sidekiq/matchers/save_backtrace_spec.rb +0 -136
- data/spec/rspec/sidekiq/sidekiq_spec.rb +0 -15
- data/spec/spec_helper.rb +0 -29
- data/spec/support/factories.rb +0 -33
- data/spec/support/init.rb +0 -6
- data/spec/support/test_action_mailer.rb +0 -6
- data/spec/support/test_job.rb +0 -6
- data/spec/support/test_resource.rb +0 -16
- data/spec/support/test_worker.rb +0 -8
- data/spec/support/test_worker_alternative.rb +0 -8
@@ -0,0 +1,368 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Sidekiq
|
5
|
+
module Matchers
|
6
|
+
# @api private
|
7
|
+
class JobOptionParser
|
8
|
+
attr_reader :job
|
9
|
+
|
10
|
+
def initialize(job)
|
11
|
+
@job = job
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches?(options)
|
15
|
+
with_context(**options)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def at_evaluator(value)
|
21
|
+
return value.nil? if job["at"].to_s.empty?
|
22
|
+
value == Time.at(job["at"]).to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def with_context(**expected_context)
|
26
|
+
expected_context.all? do |key, value|
|
27
|
+
if key == "at"
|
28
|
+
# send to custom evaluator
|
29
|
+
at_evaluator(value)
|
30
|
+
else
|
31
|
+
job.context.has_key?(key) && RSpec::Support::FuzzyMatcher.values_match?(value, job.context[key])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
class JobArguments
|
39
|
+
include RSpec::Mocks::ArgumentMatchers
|
40
|
+
|
41
|
+
def initialize(job)
|
42
|
+
self.job = job
|
43
|
+
end
|
44
|
+
attr_accessor :job
|
45
|
+
|
46
|
+
def matches?(expected_args)
|
47
|
+
matcher = RSpec::Mocks::ArgumentListMatcher.new(*expected_args)
|
48
|
+
|
49
|
+
matcher.args_match?(*unwrapped_arguments)
|
50
|
+
end
|
51
|
+
|
52
|
+
def unwrapped_arguments
|
53
|
+
args = job["args"]
|
54
|
+
|
55
|
+
return deserialized_active_job_args if active_job?
|
56
|
+
|
57
|
+
args
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def active_job?
|
63
|
+
if RSpec::Sidekiq.configuration.sidekiq_gte_8?
|
64
|
+
job["class"] == "Sidekiq::ActiveJob::Wrapper"
|
65
|
+
else
|
66
|
+
job["class"] == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def deserialized_active_job_args
|
71
|
+
active_job_args = ActiveJob::Arguments.deserialize(active_job_original_args)
|
72
|
+
|
73
|
+
# ActiveJob 7 (aj7) changed deserialization structure, adding passed arguments
|
74
|
+
# in an aj-specific hash under the :args key
|
75
|
+
aj7_args_hash = active_job_args.detect { |arg| arg.respond_to?(:key) && arg.key?(:args) }
|
76
|
+
|
77
|
+
return active_job_args if aj7_args_hash.nil?
|
78
|
+
|
79
|
+
active_job_args.delete(aj7_args_hash)
|
80
|
+
active_job_args.concat(aj7_args_hash[:args])
|
81
|
+
end
|
82
|
+
|
83
|
+
def active_job_original_args
|
84
|
+
active_job_args = job["args"].detect { |arg| arg.is_a?(Hash) && arg.key?("arguments") }
|
85
|
+
active_job_args ||= {}
|
86
|
+
active_job_args["arguments"] || []
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class EnqueuedJob
|
91
|
+
extend Forwardable
|
92
|
+
attr_reader :job
|
93
|
+
delegate :[] => :@job
|
94
|
+
|
95
|
+
def initialize(job)
|
96
|
+
@job = job
|
97
|
+
end
|
98
|
+
|
99
|
+
def jid
|
100
|
+
job["jid"]
|
101
|
+
end
|
102
|
+
|
103
|
+
def args
|
104
|
+
@args ||= JobArguments.new(job).unwrapped_arguments
|
105
|
+
end
|
106
|
+
|
107
|
+
def context
|
108
|
+
@context ||= job.except("args")
|
109
|
+
end
|
110
|
+
|
111
|
+
def ==(other)
|
112
|
+
super(other) unless other.is_a?(EnqueuedJob)
|
113
|
+
|
114
|
+
jid == other.jid
|
115
|
+
end
|
116
|
+
alias_method :eql?, :==
|
117
|
+
end
|
118
|
+
|
119
|
+
class EnqueuedJobs
|
120
|
+
include Enumerable
|
121
|
+
attr_reader :jobs
|
122
|
+
|
123
|
+
def initialize(klass)
|
124
|
+
@jobs = unwrap_jobs(klass.jobs).map { |job| EnqueuedJob.new(job) }
|
125
|
+
end
|
126
|
+
|
127
|
+
def includes?(arguments, options, count)
|
128
|
+
matching = jobs.filter { |job| matches?(job, arguments, options) }
|
129
|
+
|
130
|
+
case count[0]
|
131
|
+
when :exactly
|
132
|
+
matching.size == count[1]
|
133
|
+
when :at_least
|
134
|
+
matching.size >= count[1]
|
135
|
+
when :at_most
|
136
|
+
matching.size <= count[1]
|
137
|
+
else
|
138
|
+
matching.size > 0
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def each(&block)
|
143
|
+
jobs.each(&block)
|
144
|
+
end
|
145
|
+
|
146
|
+
def minus!(other)
|
147
|
+
self unless other.is_a?(EnqueuedJobs)
|
148
|
+
|
149
|
+
@jobs -= other.jobs
|
150
|
+
|
151
|
+
self
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
def matches?(job, arguments, options)
|
157
|
+
arguments_matches?(job, arguments) &&
|
158
|
+
options_matches?(job, options)
|
159
|
+
end
|
160
|
+
|
161
|
+
def arguments_matches?(job, arguments)
|
162
|
+
job_arguments = JobArguments.new(job)
|
163
|
+
|
164
|
+
job_arguments.matches?(arguments)
|
165
|
+
end
|
166
|
+
|
167
|
+
def options_matches?(job, options)
|
168
|
+
parser = JobOptionParser.new(job)
|
169
|
+
|
170
|
+
parser.matches?(options)
|
171
|
+
end
|
172
|
+
|
173
|
+
def unwrap_jobs(jobs)
|
174
|
+
return jobs if jobs.is_a?(Array)
|
175
|
+
jobs.values.flatten
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# @api private
|
180
|
+
class Base
|
181
|
+
include RSpec::Mocks::ArgumentMatchers
|
182
|
+
include RSpec::Matchers::Composable
|
183
|
+
|
184
|
+
attr_reader :expected_arguments, :expected_options, :klass, :actual_jobs, :expected_count
|
185
|
+
|
186
|
+
def initialize
|
187
|
+
@expected_arguments = [any_args]
|
188
|
+
@expected_options = {}
|
189
|
+
set_expected_count :positive, 1
|
190
|
+
end
|
191
|
+
|
192
|
+
def with(*expected_arguments)
|
193
|
+
@expected_arguments = normalize_arguments(expected_arguments)
|
194
|
+
self
|
195
|
+
end
|
196
|
+
|
197
|
+
def at(timestamp)
|
198
|
+
@expected_options["at"] = timestamp.to_time.to_i
|
199
|
+
self
|
200
|
+
end
|
201
|
+
|
202
|
+
def in(interval)
|
203
|
+
@expected_options["at"] = (Time.now.to_f + interval.to_f).to_i
|
204
|
+
self
|
205
|
+
end
|
206
|
+
|
207
|
+
def immediately
|
208
|
+
@expected_options["at"] = nil
|
209
|
+
self
|
210
|
+
end
|
211
|
+
|
212
|
+
def on(queue)
|
213
|
+
@expected_options["queue"] = queue
|
214
|
+
self
|
215
|
+
end
|
216
|
+
|
217
|
+
def once
|
218
|
+
set_expected_count :exactly, 1
|
219
|
+
self
|
220
|
+
end
|
221
|
+
|
222
|
+
def twice
|
223
|
+
set_expected_count :exactly, 2
|
224
|
+
self
|
225
|
+
end
|
226
|
+
|
227
|
+
def thrice
|
228
|
+
set_expected_count :exactly, 3
|
229
|
+
self
|
230
|
+
end
|
231
|
+
|
232
|
+
def exactly(n)
|
233
|
+
set_expected_count :exactly, n
|
234
|
+
self
|
235
|
+
end
|
236
|
+
|
237
|
+
def at_least(n)
|
238
|
+
set_expected_count :at_least, n
|
239
|
+
self
|
240
|
+
end
|
241
|
+
|
242
|
+
def at_most(n)
|
243
|
+
set_expected_count :at_most, n
|
244
|
+
self
|
245
|
+
end
|
246
|
+
|
247
|
+
def times
|
248
|
+
self
|
249
|
+
end
|
250
|
+
alias :time :times
|
251
|
+
|
252
|
+
def with_context(**kwargs)
|
253
|
+
raise ArgumentError, "Must specify keyword arguments to with_context" if kwargs.empty?
|
254
|
+
|
255
|
+
# gather keys and compare against currently set expected_options
|
256
|
+
# Someone could have accidentally used with_context and other
|
257
|
+
# chainables with different expectations. Better to explicitly
|
258
|
+
# inform loudly of clashes than let them overwrite silently
|
259
|
+
normalized = normalize_arguments(kwargs)
|
260
|
+
already_set = normalized.keys & @expected_options.keys
|
261
|
+
if already_set.any?
|
262
|
+
prettied = already_set.map { |key| "'#{key}'" }
|
263
|
+
raise ArgumentError, "There are already expectations against #{prettied.join(",")}. Did you already call other context chainables like `on` or `at`?"
|
264
|
+
end
|
265
|
+
|
266
|
+
# We're good, no accidental overwrites of expectations
|
267
|
+
@expected_options.merge!(normalized)
|
268
|
+
|
269
|
+
self
|
270
|
+
end
|
271
|
+
|
272
|
+
def set_expected_count(relativity, n)
|
273
|
+
n =
|
274
|
+
case n
|
275
|
+
when Integer then n
|
276
|
+
when :once then 1
|
277
|
+
when :twice then 2
|
278
|
+
when :thrice then 3
|
279
|
+
else raise ArgumentError, "Unsupported #{n} in '#{relativity} #{n}'. Use either an Integer, :once, :twice, or :thrice."
|
280
|
+
end
|
281
|
+
@expected_count = [relativity, n]
|
282
|
+
end
|
283
|
+
|
284
|
+
def description
|
285
|
+
"#{common_message} with arguments #{expected_arguments}"
|
286
|
+
end
|
287
|
+
|
288
|
+
def failure_message
|
289
|
+
message = ["expected to #{common_message}"]
|
290
|
+
if expected_arguments
|
291
|
+
message << " with arguments:"
|
292
|
+
message << " -#{formatted(expected_arguments)}"
|
293
|
+
end
|
294
|
+
|
295
|
+
if expected_options.any?
|
296
|
+
message << " with context:"
|
297
|
+
message << " -#{formatted(expected_options)}"
|
298
|
+
end
|
299
|
+
|
300
|
+
if actual_jobs.any?
|
301
|
+
message << "but enqueued only jobs"
|
302
|
+
if expected_arguments
|
303
|
+
job_messages = actual_jobs.map do |job|
|
304
|
+
base = [" -JID:#{job.jid} with arguments:"]
|
305
|
+
base << " -#{formatted(job.args)}"
|
306
|
+
if expected_options.any?
|
307
|
+
base << " with context: #{formatted(job.context)}"
|
308
|
+
end
|
309
|
+
|
310
|
+
base.join("\n")
|
311
|
+
end
|
312
|
+
|
313
|
+
message << job_messages.join("\n")
|
314
|
+
end
|
315
|
+
else
|
316
|
+
message << "but enqueued 0 jobs"
|
317
|
+
end
|
318
|
+
|
319
|
+
message.join("\n")
|
320
|
+
end
|
321
|
+
|
322
|
+
def common_message
|
323
|
+
"#{prefix_message} #{count_message} #{klass} #{expected_count.last == 1 ? "job" : "jobs"}"
|
324
|
+
end
|
325
|
+
|
326
|
+
def prefix_message
|
327
|
+
raise NotImplementedError
|
328
|
+
end
|
329
|
+
|
330
|
+
def count_message
|
331
|
+
case expected_count[0]
|
332
|
+
when :positive
|
333
|
+
"a"
|
334
|
+
when :exactly
|
335
|
+
expected_count[1]
|
336
|
+
else
|
337
|
+
"#{expected_count[0].to_s.gsub('_', ' ')} #{expected_count[1]}"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def failure_message_when_negated
|
342
|
+
message = ["expected not to #{common_message} but enqueued #{actual_jobs.count}"]
|
343
|
+
message << " arguments: #{expected_arguments}" if expected_arguments.any?
|
344
|
+
message << " options: #{expected_options}" if expected_options.any?
|
345
|
+
message.join("\n")
|
346
|
+
end
|
347
|
+
|
348
|
+
def formatted(thing)
|
349
|
+
RSpec::Support::ObjectFormatter.format(thing)
|
350
|
+
end
|
351
|
+
|
352
|
+
def normalize_arguments(args)
|
353
|
+
if args.is_a?(Array)
|
354
|
+
args.map{ |x| normalize_arguments(x) }
|
355
|
+
elsif args.is_a?(Hash)
|
356
|
+
args.each_with_object({}) do |(key, value), hash|
|
357
|
+
hash[key.to_s] = normalize_arguments(value)
|
358
|
+
end
|
359
|
+
elsif args.is_a?(Symbol)
|
360
|
+
args.to_s
|
361
|
+
else
|
362
|
+
args
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
@@ -1,12 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RSpec
|
2
4
|
module Sidekiq
|
3
5
|
module Matchers
|
6
|
+
include RSpec::Mocks::ArgumentMatchers
|
7
|
+
|
4
8
|
def be_delayed(*expected_arguments)
|
5
9
|
BeDelayed.new(*expected_arguments)
|
6
10
|
end
|
7
11
|
|
8
12
|
class BeDelayed
|
9
13
|
def initialize(*expected_arguments)
|
14
|
+
raise <<~MSG if RSpec::Sidekiq.configuration.sidekiq_gte_7?
|
15
|
+
Use of the be_delayed matcher with Sidekiq 7+ is not possible. Try refactoring to a Sidekiq Job with `perform_at` or `perform_in` and the `have_enqueued_sidekiq_job` matcher
|
16
|
+
MSG
|
17
|
+
|
10
18
|
@expected_arguments = expected_arguments
|
11
19
|
end
|
12
20
|
|
@@ -34,7 +42,7 @@ module RSpec
|
|
34
42
|
find_job @expected_method, @expected_arguments do |job|
|
35
43
|
if @expected_interval
|
36
44
|
created_enqueued_at = job['enqueued_at'] || job['created_at']
|
37
|
-
return job['at'].to_i == created_enqueued_at.
|
45
|
+
return job['at'].to_i == Time.at(created_enqueued_at.to_f + @expected_interval.to_f).to_i
|
38
46
|
elsif @expected_time
|
39
47
|
return job['at'].to_i == @expected_time.to_i
|
40
48
|
else
|
@@ -58,8 +66,16 @@ module RSpec
|
|
58
66
|
|
59
67
|
def find_job(method, arguments, &block)
|
60
68
|
job = (::Sidekiq::Extensions::DelayedClass.jobs + ::Sidekiq::Extensions::DelayedModel.jobs + ::Sidekiq::Extensions::DelayedMailer.jobs).find do |job|
|
61
|
-
|
62
|
-
|
69
|
+
arg = job['args'].first
|
70
|
+
yaml = begin
|
71
|
+
YAML.load(arg, aliases: true) # Psych 4 required syntax
|
72
|
+
rescue ArgumentError
|
73
|
+
YAML.load(arg) # Pysch < 4 syntax
|
74
|
+
end
|
75
|
+
|
76
|
+
@expected_method_receiver == yaml[0] &&
|
77
|
+
method.name == yaml[1] &&
|
78
|
+
(arguments.empty? || RSpec::Mocks::ArgumentListMatcher.new(*arguments).args_match?(*yaml[2]))
|
63
79
|
end
|
64
80
|
|
65
81
|
yield job if block && job
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RSpec
|
2
4
|
module Sidekiq
|
3
5
|
module Matchers
|
@@ -25,6 +27,9 @@ module RSpec
|
|
25
27
|
if !interval_matches? && @expected_interval
|
26
28
|
"expected #{@klass} to be unique for #{@expected_interval} seconds, "\
|
27
29
|
"but its interval was #{actual_interval} seconds"
|
30
|
+
elsif !expiration_matches?
|
31
|
+
"expected #{@klass} to be unique until #{@expected_expiration}, "\
|
32
|
+
"but its unique_until was #{actual_expiration || 'not specified'}"
|
28
33
|
else
|
29
34
|
"expected #{@klass} to be unique in the queue"
|
30
35
|
end
|
@@ -33,7 +38,7 @@ module RSpec
|
|
33
38
|
def matches?(job)
|
34
39
|
@klass = job.is_a?(Class) ? job : job.class
|
35
40
|
@actual = @klass.get_sidekiq_options[unique_key]
|
36
|
-
!!(value_matches? && interval_matches?)
|
41
|
+
!!(value_matches? && interval_matches? && expiration_matches?)
|
37
42
|
end
|
38
43
|
|
39
44
|
def for(interval)
|
@@ -41,6 +46,11 @@ module RSpec
|
|
41
46
|
self
|
42
47
|
end
|
43
48
|
|
49
|
+
def until(expiration)
|
50
|
+
@expected_expiration = expiration
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
44
54
|
def interval_specified?
|
45
55
|
@expected_interval
|
46
56
|
end
|
@@ -49,6 +59,10 @@ module RSpec
|
|
49
59
|
!interval_specified? || actual_interval == @expected_interval
|
50
60
|
end
|
51
61
|
|
62
|
+
def expiration_matches?
|
63
|
+
@expected_expiration.nil? || actual_expiration == @expected_expiration
|
64
|
+
end
|
65
|
+
|
52
66
|
def failure_message_when_negated
|
53
67
|
"expected #{@klass} to not be unique in the queue"
|
54
68
|
end
|
@@ -59,6 +73,10 @@ module RSpec
|
|
59
73
|
@klass.get_sidekiq_options['unique_job_expiration']
|
60
74
|
end
|
61
75
|
|
76
|
+
def actual_expiration
|
77
|
+
fail 'until is not supported for SidekiqUniqueJobs'
|
78
|
+
end
|
79
|
+
|
62
80
|
def value_matches?
|
63
81
|
[true, :all].include?(@actual)
|
64
82
|
end
|
@@ -73,6 +91,10 @@ module RSpec
|
|
73
91
|
@actual
|
74
92
|
end
|
75
93
|
|
94
|
+
def actual_expiration
|
95
|
+
@klass.get_sidekiq_options['unique_until']
|
96
|
+
end
|
97
|
+
|
76
98
|
def value_matches?
|
77
99
|
@actual && @actual > 0
|
78
100
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Sidekiq
|
5
|
+
module Matchers
|
6
|
+
# @api private
|
7
|
+
class EnqueueSidekiqJob < Base
|
8
|
+
attr_reader :original_jobs # Plus that from Base
|
9
|
+
|
10
|
+
def initialize(job_class)
|
11
|
+
super()
|
12
|
+
default = if RSpec::Sidekiq.configuration.sidekiq_gte_7?
|
13
|
+
::Sidekiq::Job
|
14
|
+
else
|
15
|
+
::Sidekiq::Worker
|
16
|
+
end
|
17
|
+
|
18
|
+
@klass = job_class || default
|
19
|
+
end
|
20
|
+
|
21
|
+
def matches?(proc)
|
22
|
+
raise ArgumentError, "Only block syntax supported for enqueue_sidekiq_job" unless Proc === proc
|
23
|
+
|
24
|
+
@original_jobs = EnqueuedJobs.new(@klass)
|
25
|
+
proc.call
|
26
|
+
@actual_jobs = EnqueuedJobs.new(@klass).minus!(original_jobs)
|
27
|
+
|
28
|
+
if @actual_jobs.none?
|
29
|
+
return false
|
30
|
+
end
|
31
|
+
|
32
|
+
@actual_jobs.includes?(expected_arguments, expected_options, expected_count)
|
33
|
+
end
|
34
|
+
|
35
|
+
def prefix_message
|
36
|
+
"enqueue"
|
37
|
+
end
|
38
|
+
|
39
|
+
def supports_block_expectations?
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @api public
|
45
|
+
#
|
46
|
+
# Passes if a Job is enqueued as the result of a block. Chainable `with`
|
47
|
+
# for arguments, `on` for queue, `at` for queued for a specific time, and
|
48
|
+
# `in` for a specific interval delay to being queued, `immediately` for
|
49
|
+
# queued without delay.
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
#
|
53
|
+
# expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job
|
54
|
+
#
|
55
|
+
# # A specific job class
|
56
|
+
# expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job(AwesomeJob)
|
57
|
+
#
|
58
|
+
# # with specific arguments
|
59
|
+
# expect { AwesomeJob.perform_async "Awesome!" }.to enqueue_sidekiq_job.with("Awesome!")
|
60
|
+
#
|
61
|
+
# # On a specific queue
|
62
|
+
# expect { AwesomeJob.set(queue: "high").perform_async }.to enqueue_sidekiq_job.on("high")
|
63
|
+
#
|
64
|
+
# # At a specific datetime
|
65
|
+
# specific_time = 1.hour.from_now
|
66
|
+
# expect { AwesomeJob.perform_at(specific_time) }.to enqueue_sidekiq_job.at(specific_time)
|
67
|
+
#
|
68
|
+
# # In a specific interval (be mindful of freezing or managing time here)
|
69
|
+
# freeze_time do
|
70
|
+
# expect { AwesomeJob.perform_in(1.hour) }.to enqueue_sidekiq_job.in(1.hour)
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# # Without any delay
|
74
|
+
# expect { AwesomeJob.perform_async }.to enqueue_sidekiq_job.immediately
|
75
|
+
# expect { AwesomeJob.perform_at(1.hour.ago) }.to enqueue_sidekiq_job.immediately
|
76
|
+
#
|
77
|
+
# # With specific context
|
78
|
+
# expect { AwesomeJob.set(trace_id: "something").perform_async }.to enqueue_sidekiq_job.with_context(trace_id: anything)
|
79
|
+
#
|
80
|
+
# ## Composable
|
81
|
+
#
|
82
|
+
# expect do
|
83
|
+
# AwesomeJob.perform_async
|
84
|
+
# OtherJob.perform_async
|
85
|
+
# end.to enqueue_sidekiq_job(AwesomeJob).and enqueue_sidekiq_job(OtherJob)
|
86
|
+
def enqueue_sidekiq_job(job_class = nil)
|
87
|
+
EnqueueSidekiqJob.new(job_class)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Sidekiq
|
5
|
+
module Matchers
|
6
|
+
def have_enqueued_sidekiq_job(*expected_arguments)
|
7
|
+
HaveEnqueuedSidekiqJob.new expected_arguments
|
8
|
+
end
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
class HaveEnqueuedSidekiqJob < Base
|
12
|
+
def initialize(expected_arguments)
|
13
|
+
super()
|
14
|
+
@expected_arguments = normalize_arguments(expected_arguments)
|
15
|
+
end
|
16
|
+
|
17
|
+
def matches?(job_class)
|
18
|
+
@klass = job_class
|
19
|
+
|
20
|
+
@actual_jobs = EnqueuedJobs.new(klass)
|
21
|
+
|
22
|
+
actual_jobs.includes?(
|
23
|
+
expected_arguments == [] ? any_args : expected_arguments,
|
24
|
+
expected_options,
|
25
|
+
expected_count
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def prefix_message
|
30
|
+
"have enqueued"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,11 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
|
8
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rspec/core"
|
4
|
+
require "rspec/matchers"
|
5
|
+
require "rspec/mocks/argument_list_matcher"
|
6
|
+
require "rspec/mocks/argument_matchers"
|
7
|
+
|
8
|
+
require "rspec/sidekiq/matchers/base"
|
9
|
+
require "rspec/sidekiq/matchers/be_delayed"
|
10
|
+
require "rspec/sidekiq/matchers/be_expired_in"
|
11
|
+
require "rspec/sidekiq/matchers/be_processed_in"
|
12
|
+
require "rspec/sidekiq/matchers/be_retryable"
|
13
|
+
require "rspec/sidekiq/matchers/be_unique"
|
14
|
+
require "rspec/sidekiq/matchers/have_enqueued_sidekiq_job"
|
15
|
+
require "rspec/sidekiq/matchers/save_backtrace"
|
16
|
+
require "rspec/sidekiq/matchers/enqueue_sidekiq_job"
|
9
17
|
|
10
18
|
RSpec.configure do |config|
|
11
19
|
config.include RSpec::Sidekiq::Matchers
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RSpec
|
2
4
|
module Sidekiq
|
3
5
|
class << self
|
@@ -14,7 +16,7 @@ end
|
|
14
16
|
|
15
17
|
RSpec.configure do |config|
|
16
18
|
config.before(:suite) do
|
17
|
-
message = '[rspec-sidekiq] WARNING! Sidekiq will *NOT* process jobs in this environment. See https://github.com/
|
19
|
+
message = '[rspec-sidekiq] WARNING! Sidekiq will *NOT* process jobs in this environment. See https://github.com/wspurgin/rspec-sidekiq/wiki/FAQ-&-Troubleshooting'
|
18
20
|
message = "\e[33m#{message}\e[0m" if RSpec::Sidekiq.configuration.enable_terminal_colours
|
19
21
|
puts message if RSpec::Sidekiq.configuration.warn_when_jobs_not_processed_by_sidekiq
|
20
22
|
end
|