rspec-rails 3.0.2 → 7.1.1
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
- checksums.yaml.gz.sig +0 -0
- data/.document +1 -1
- data/.yardopts +3 -1
- data/Capybara.md +6 -55
- data/Changelog.md +805 -47
- data/{License.txt → LICENSE.md} +5 -3
- data/README.md +278 -444
- data/lib/generators/rspec/channel/channel_generator.rb +12 -0
- data/lib/generators/rspec/{observer/templates/observer_spec.rb → channel/templates/channel_spec.rb.erb} +1 -1
- data/lib/generators/rspec/controller/controller_generator.rb +24 -7
- data/lib/generators/rspec/controller/templates/controller_spec.rb +3 -3
- data/lib/generators/rspec/controller/templates/request_spec.rb +19 -0
- data/lib/generators/rspec/controller/templates/routing_spec.rb +13 -0
- data/lib/generators/rspec/controller/templates/view_spec.rb +1 -1
- data/lib/generators/rspec/feature/feature_generator.rb +15 -2
- data/lib/generators/rspec/feature/templates/feature_singular_spec.rb +5 -0
- data/lib/generators/rspec/feature/templates/feature_spec.rb +1 -1
- data/lib/generators/rspec/generator/generator_generator.rb +24 -0
- data/lib/generators/rspec/generator/templates/generator_spec.rb +5 -0
- data/lib/generators/rspec/helper/helper_generator.rb +2 -2
- data/lib/generators/rspec/helper/templates/helper_spec.rb +1 -1
- data/lib/generators/rspec/install/install_generator.rb +41 -7
- data/lib/generators/rspec/install/templates/spec/rails_helper.rb +63 -22
- data/lib/generators/rspec/job/job_generator.rb +13 -0
- data/lib/generators/rspec/job/templates/job_spec.rb.erb +7 -0
- data/lib/generators/rspec/mailbox/mailbox_generator.rb +14 -0
- data/lib/generators/rspec/mailbox/templates/mailbox_spec.rb.erb +7 -0
- data/lib/generators/rspec/mailer/mailer_generator.rb +12 -3
- data/lib/generators/rspec/mailer/templates/mailer_spec.rb +2 -2
- data/lib/generators/rspec/mailer/templates/preview.rb +13 -0
- data/lib/generators/rspec/model/model_generator.rb +20 -6
- data/lib/generators/rspec/model/templates/fixtures.yml +1 -1
- data/lib/generators/rspec/model/templates/model_spec.rb +1 -1
- data/lib/generators/rspec/request/request_generator.rb +17 -0
- data/lib/generators/rspec/request/templates/request_spec.rb +10 -0
- data/lib/generators/rspec/scaffold/scaffold_generator.rb +90 -113
- data/lib/generators/rspec/scaffold/templates/api_controller_spec.rb +129 -0
- data/lib/generators/rspec/scaffold/templates/api_request_spec.rb +131 -0
- data/lib/generators/rspec/scaffold/templates/controller_spec.rb +46 -64
- data/lib/generators/rspec/scaffold/templates/edit_spec.rb +11 -7
- data/lib/generators/rspec/scaffold/templates/index_spec.rb +4 -3
- data/lib/generators/rspec/scaffold/templates/new_spec.rb +4 -4
- data/lib/generators/rspec/scaffold/templates/request_spec.rb +138 -0
- data/lib/generators/rspec/scaffold/templates/routing_spec.rb +18 -11
- data/lib/generators/rspec/scaffold/templates/show_spec.rb +3 -3
- data/lib/generators/rspec/system/system_generator.rb +24 -0
- data/lib/generators/rspec/system/templates/system_spec.rb +9 -0
- data/lib/generators/rspec/view/templates/view_spec.rb +1 -1
- data/lib/generators/rspec/view/view_generator.rb +4 -4
- data/lib/generators/rspec.rb +30 -11
- data/lib/rspec/rails/active_record.rb +25 -0
- data/lib/rspec/rails/adapters.rb +46 -29
- data/lib/rspec/rails/configuration.rb +165 -41
- data/lib/rspec/rails/example/channel_example_group.rb +93 -0
- data/lib/rspec/rails/example/controller_example_group.rb +185 -149
- data/lib/rspec/rails/example/feature_example_group.rb +43 -23
- data/lib/rspec/rails/example/helper_example_group.rb +28 -25
- data/lib/rspec/rails/example/job_example_group.rb +23 -0
- data/lib/rspec/rails/example/mailbox_example_group.rb +80 -0
- data/lib/rspec/rails/example/mailer_example_group.rb +27 -22
- data/lib/rspec/rails/example/model_example_group.rb +9 -6
- data/lib/rspec/rails/example/rails_example_group.rb +9 -2
- data/lib/rspec/rails/example/request_example_group.rb +21 -17
- data/lib/rspec/rails/example/routing_example_group.rb +47 -39
- data/lib/rspec/rails/example/system_example_group.rb +180 -0
- data/lib/rspec/rails/example/view_example_group.rb +179 -134
- data/lib/rspec/rails/example.rb +4 -0
- data/lib/rspec/rails/extensions/active_record/proxy.rb +5 -11
- data/lib/rspec/rails/feature_check.rb +51 -0
- data/lib/rspec/rails/file_fixture_support.rb +18 -0
- data/lib/rspec/rails/fixture_file_upload_support.rb +45 -0
- data/lib/rspec/rails/fixture_support.rb +70 -14
- data/lib/rspec/rails/matchers/action_cable/have_broadcasted_to.rb +180 -0
- data/lib/rspec/rails/matchers/action_cable/have_streams.rb +58 -0
- data/lib/rspec/rails/matchers/action_cable.rb +70 -0
- data/lib/rspec/rails/matchers/action_mailbox.rb +73 -0
- data/lib/rspec/rails/matchers/active_job.rb +526 -0
- data/lib/rspec/rails/matchers/base_matcher.rb +179 -0
- data/lib/rspec/rails/matchers/be_a_new.rb +70 -64
- data/lib/rspec/rails/matchers/be_new_record.rb +25 -20
- data/lib/rspec/rails/matchers/be_valid.rb +39 -34
- data/lib/rspec/rails/matchers/have_enqueued_mail.rb +259 -0
- data/lib/rspec/rails/matchers/have_http_status.rb +359 -333
- data/lib/rspec/rails/matchers/have_rendered.rb +55 -32
- data/lib/rspec/rails/matchers/redirect_to.rb +30 -27
- data/lib/rspec/rails/matchers/relation_match_array.rb +1 -1
- data/lib/rspec/rails/matchers/routing_matchers.rb +107 -101
- data/lib/rspec/rails/matchers/send_email.rb +122 -0
- data/lib/rspec/rails/matchers.rb +21 -12
- data/lib/rspec/rails/tasks/rspec.rake +9 -17
- data/lib/rspec/rails/vendor/capybara.rb +10 -11
- data/lib/rspec/rails/version.rb +1 -1
- data/lib/rspec/rails/view_assigns.rb +1 -20
- data/lib/rspec/rails/view_path_builder.rb +29 -0
- data/lib/rspec/rails/view_rendering.rb +89 -27
- data/lib/rspec/rails/view_spec_methods.rb +56 -0
- data/lib/rspec/rails.rb +9 -1
- data/lib/rspec-rails.rb +83 -3
- data.tar.gz.sig +0 -0
- metadata +108 -78
- metadata.gz.sig +3 -2
- data/lib/generators/rspec/integration/integration_generator.rb +0 -17
- data/lib/generators/rspec/integration/templates/request_spec.rb +0 -10
- data/lib/generators/rspec/observer/observer_generator.rb +0 -13
@@ -0,0 +1,526 @@
|
|
1
|
+
require "active_job/base"
|
2
|
+
require "active_job/arguments"
|
3
|
+
|
4
|
+
module RSpec
|
5
|
+
module Rails
|
6
|
+
module Matchers
|
7
|
+
# Namespace for various implementations of ActiveJob features
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
module ActiveJob
|
11
|
+
# rubocop: disable Metrics/ClassLength
|
12
|
+
# @private
|
13
|
+
class Base < RSpec::Rails::Matchers::BaseMatcher
|
14
|
+
def initialize
|
15
|
+
@args = []
|
16
|
+
@queue = nil
|
17
|
+
@priority = nil
|
18
|
+
@at = nil
|
19
|
+
@block = proc { }
|
20
|
+
set_expected_number(:exactly, 1)
|
21
|
+
end
|
22
|
+
|
23
|
+
def with(*args, &block)
|
24
|
+
@args = args
|
25
|
+
@block = block if block.present?
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_queue(queue)
|
30
|
+
@queue = queue.to_s
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def at_priority(priority)
|
35
|
+
@priority = priority.to_i
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def at(time_or_date)
|
40
|
+
case time_or_date
|
41
|
+
when Time then @at = Time.at(time_or_date.to_f)
|
42
|
+
else
|
43
|
+
@at = time_or_date
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def exactly(count)
|
49
|
+
set_expected_number(:exactly, count)
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def at_least(count)
|
54
|
+
set_expected_number(:at_least, count)
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def at_most(count)
|
59
|
+
set_expected_number(:at_most, count)
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def times
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def once
|
68
|
+
exactly(:once)
|
69
|
+
end
|
70
|
+
|
71
|
+
def twice
|
72
|
+
exactly(:twice)
|
73
|
+
end
|
74
|
+
|
75
|
+
def thrice
|
76
|
+
exactly(:thrice)
|
77
|
+
end
|
78
|
+
|
79
|
+
def failure_message
|
80
|
+
return @failure_message if defined?(@failure_message)
|
81
|
+
|
82
|
+
"expected to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}".tap do |msg|
|
83
|
+
if @unmatching_jobs.any?
|
84
|
+
msg << "\nQueued jobs:"
|
85
|
+
@unmatching_jobs.each do |job|
|
86
|
+
msg << "\n #{base_job_message(job)}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def failure_message_when_negated
|
93
|
+
"expected not to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def message_expectation_modifier
|
97
|
+
case @expectation_type
|
98
|
+
when :exactly then "exactly"
|
99
|
+
when :at_most then "at most"
|
100
|
+
when :at_least then "at least"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def supports_block_expectations?
|
105
|
+
true
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def check(jobs)
|
111
|
+
@matching_jobs, @unmatching_jobs = jobs.partition do |job|
|
112
|
+
if matches_constraints?(job)
|
113
|
+
args = deserialize_arguments(job)
|
114
|
+
@block.call(*args)
|
115
|
+
true
|
116
|
+
else
|
117
|
+
false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if (signature_mismatch = detect_args_signature_mismatch(@matching_jobs))
|
122
|
+
@failure_message = signature_mismatch
|
123
|
+
return false
|
124
|
+
end
|
125
|
+
|
126
|
+
@matching_jobs_count = @matching_jobs.size
|
127
|
+
|
128
|
+
case @expectation_type
|
129
|
+
when :exactly then @expected_number == @matching_jobs_count
|
130
|
+
when :at_most then @expected_number >= @matching_jobs_count
|
131
|
+
when :at_least then @expected_number <= @matching_jobs_count
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def base_message
|
136
|
+
"#{message_expectation_modifier} #{@expected_number} jobs,".tap do |msg|
|
137
|
+
msg << " with #{@args}," if @args.any?
|
138
|
+
msg << " on queue #{@queue}," if @queue
|
139
|
+
msg << " at #{@at.inspect}," if @at
|
140
|
+
msg << " with priority #{@priority}," if @priority
|
141
|
+
msg << " but #{self.class::MESSAGE_EXPECTATION_ACTION} #{@matching_jobs_count}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def base_job_message(job)
|
146
|
+
msg_parts = []
|
147
|
+
msg_parts << "with #{deserialize_arguments(job)}" if job[:args].any?
|
148
|
+
msg_parts << "on queue #{job[:queue]}" if job[:queue]
|
149
|
+
msg_parts << "at #{Time.at(job[:at])}" if job[:at]
|
150
|
+
msg_parts <<
|
151
|
+
if job[:priority]
|
152
|
+
"with priority #{job[:priority]}"
|
153
|
+
else
|
154
|
+
"with no priority specified"
|
155
|
+
end
|
156
|
+
|
157
|
+
"#{job[:job].name} job".tap do |msg|
|
158
|
+
msg << " #{msg_parts.join(', ')}" if msg_parts.any?
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def matches_constraints?(job)
|
163
|
+
job_matches?(job) && arguments_match?(job) && queue_match?(job) && at_match?(job) && priority_match?(job)
|
164
|
+
end
|
165
|
+
|
166
|
+
def job_matches?(job)
|
167
|
+
@job ? @job == job[:job] : true
|
168
|
+
end
|
169
|
+
|
170
|
+
def arguments_match?(job)
|
171
|
+
if @args.any?
|
172
|
+
args = serialize_and_deserialize_arguments(@args)
|
173
|
+
deserialized_args = deserialize_arguments(job)
|
174
|
+
RSpec::Mocks::ArgumentListMatcher.new(*args).args_match?(*deserialized_args)
|
175
|
+
else
|
176
|
+
true
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def detect_args_signature_mismatch(jobs)
|
181
|
+
return if skip_signature_verification?
|
182
|
+
|
183
|
+
jobs.each do |job|
|
184
|
+
args = deserialize_arguments(job)
|
185
|
+
|
186
|
+
if (signature_mismatch = check_args_signature_mismatch(job.fetch(:job), :perform, args))
|
187
|
+
return signature_mismatch
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
nil
|
192
|
+
end
|
193
|
+
|
194
|
+
def skip_signature_verification?
|
195
|
+
return true unless defined?(::RSpec::Mocks) && (::RSpec::Mocks.respond_to?(:configuration))
|
196
|
+
|
197
|
+
!RSpec::Mocks.configuration.verify_partial_doubles? ||
|
198
|
+
RSpec::Mocks.configuration.temporarily_suppress_partial_double_verification
|
199
|
+
end
|
200
|
+
|
201
|
+
def check_args_signature_mismatch(job_class, job_method, args)
|
202
|
+
signature = Support::MethodSignature.new(job_class.public_instance_method(job_method))
|
203
|
+
verifier = Support::StrictSignatureVerifier.new(signature, args)
|
204
|
+
|
205
|
+
unless verifier.valid?
|
206
|
+
"Incorrect arguments passed to #{job_class.name}: #{verifier.error_message}"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def queue_match?(job)
|
211
|
+
return true unless @queue
|
212
|
+
|
213
|
+
@queue == job[:queue]
|
214
|
+
end
|
215
|
+
|
216
|
+
def priority_match?(job)
|
217
|
+
return true unless @priority
|
218
|
+
|
219
|
+
@priority == job[:priority]
|
220
|
+
end
|
221
|
+
|
222
|
+
def at_match?(job)
|
223
|
+
return true unless @at
|
224
|
+
return job[:at].nil? if @at == :no_wait
|
225
|
+
return false unless job[:at]
|
226
|
+
|
227
|
+
scheduled_at = Time.at(job[:at])
|
228
|
+
values_match?(@at, scheduled_at) || check_for_inprecise_value(scheduled_at)
|
229
|
+
end
|
230
|
+
|
231
|
+
def check_for_inprecise_value(scheduled_at)
|
232
|
+
return unless Time === @at && values_match?(@at.change(usec: 0), scheduled_at)
|
233
|
+
|
234
|
+
RSpec.warn_with((<<-WARNING).gsub(/^\s+\|/, '').chomp)
|
235
|
+
|[WARNING] Your expected `at(...)` value does not match the job scheduled_at value
|
236
|
+
|unless microseconds are removed. This precision error often occurs when checking
|
237
|
+
|values against `Time.current` / `Time.now` which have usec precision, but Rails
|
238
|
+
|uses `n.seconds.from_now` internally which has a usec count of `0`.
|
239
|
+
|
|
240
|
+
|Use `change(usec: 0)` to correct these values. For example:
|
241
|
+
|
|
242
|
+
|`Time.current.change(usec: 0)`
|
243
|
+
|
|
244
|
+
|Note: RSpec cannot do this for you because jobs can be scheduled with usec
|
245
|
+
|precision and we do not know whether it is on purpose or not.
|
246
|
+
|
|
247
|
+
|
|
248
|
+
WARNING
|
249
|
+
false
|
250
|
+
end
|
251
|
+
|
252
|
+
def set_expected_number(relativity, count)
|
253
|
+
@expectation_type = relativity
|
254
|
+
@expected_number = case count
|
255
|
+
when :once then 1
|
256
|
+
when :twice then 2
|
257
|
+
when :thrice then 3
|
258
|
+
else Integer(count)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def serialize_and_deserialize_arguments(args)
|
263
|
+
serialized = ::ActiveJob::Arguments.serialize(args)
|
264
|
+
::ActiveJob::Arguments.deserialize(serialized)
|
265
|
+
rescue ::ActiveJob::SerializationError
|
266
|
+
args
|
267
|
+
end
|
268
|
+
|
269
|
+
def deserialize_arguments(job)
|
270
|
+
::ActiveJob::Arguments.deserialize(job[:args])
|
271
|
+
rescue ::ActiveJob::DeserializationError
|
272
|
+
job[:args]
|
273
|
+
end
|
274
|
+
|
275
|
+
def queue_adapter
|
276
|
+
::ActiveJob::Base.queue_adapter
|
277
|
+
end
|
278
|
+
end
|
279
|
+
# rubocop: enable Metrics/ClassLength
|
280
|
+
|
281
|
+
# @private
|
282
|
+
class HaveEnqueuedJob < Base
|
283
|
+
FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
|
284
|
+
MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze
|
285
|
+
|
286
|
+
def initialize(job)
|
287
|
+
super()
|
288
|
+
@job = job
|
289
|
+
end
|
290
|
+
|
291
|
+
def matches?(proc)
|
292
|
+
raise ArgumentError, "have_enqueued_job and enqueue_job only support block expectations" unless Proc === proc
|
293
|
+
|
294
|
+
original_enqueued_jobs = Set.new(queue_adapter.enqueued_jobs)
|
295
|
+
proc.call
|
296
|
+
enqueued_jobs = Set.new(queue_adapter.enqueued_jobs)
|
297
|
+
|
298
|
+
check(enqueued_jobs - original_enqueued_jobs)
|
299
|
+
end
|
300
|
+
|
301
|
+
def does_not_match?(proc)
|
302
|
+
set_expected_number(:at_least, 1)
|
303
|
+
|
304
|
+
!matches?(proc)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# @private
|
309
|
+
class HaveBeenEnqueued < Base
|
310
|
+
FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
|
311
|
+
MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze
|
312
|
+
|
313
|
+
def matches?(job)
|
314
|
+
@job = job
|
315
|
+
check(queue_adapter.enqueued_jobs)
|
316
|
+
end
|
317
|
+
|
318
|
+
def does_not_match?(proc)
|
319
|
+
set_expected_number(:at_least, 1)
|
320
|
+
|
321
|
+
!matches?(proc)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# @private
|
326
|
+
class HavePerformedJob < Base
|
327
|
+
FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
|
328
|
+
MESSAGE_EXPECTATION_ACTION = 'performed'.freeze
|
329
|
+
|
330
|
+
def initialize(job)
|
331
|
+
super()
|
332
|
+
@job = job
|
333
|
+
end
|
334
|
+
|
335
|
+
def matches?(proc)
|
336
|
+
raise ArgumentError, "have_performed_job only supports block expectations" unless Proc === proc
|
337
|
+
|
338
|
+
original_performed_jobs_count = queue_adapter.performed_jobs.count
|
339
|
+
proc.call
|
340
|
+
in_block_jobs = queue_adapter.performed_jobs.drop(original_performed_jobs_count)
|
341
|
+
|
342
|
+
check(in_block_jobs)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# @private
|
347
|
+
class HaveBeenPerformed < Base
|
348
|
+
FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
|
349
|
+
MESSAGE_EXPECTATION_ACTION = 'performed'.freeze
|
350
|
+
|
351
|
+
def matches?(job)
|
352
|
+
@job = job
|
353
|
+
check(queue_adapter.performed_jobs)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# @api public
|
359
|
+
# Passes if a job has been enqueued inside block. May chain at_least, at_most or exactly to specify a number of times.
|
360
|
+
#
|
361
|
+
# @example
|
362
|
+
# expect {
|
363
|
+
# HeavyLiftingJob.perform_later
|
364
|
+
# }.to have_enqueued_job
|
365
|
+
#
|
366
|
+
# # Using alias
|
367
|
+
# expect {
|
368
|
+
# HeavyLiftingJob.perform_later
|
369
|
+
# }.to enqueue_job
|
370
|
+
#
|
371
|
+
# expect {
|
372
|
+
# HelloJob.perform_later
|
373
|
+
# HeavyLiftingJob.perform_later
|
374
|
+
# }.to have_enqueued_job(HelloJob).exactly(:once)
|
375
|
+
#
|
376
|
+
# expect {
|
377
|
+
# 3.times { HelloJob.perform_later }
|
378
|
+
# }.to have_enqueued_job(HelloJob).at_least(2).times
|
379
|
+
#
|
380
|
+
# expect {
|
381
|
+
# HelloJob.perform_later
|
382
|
+
# }.to have_enqueued_job(HelloJob).at_most(:twice)
|
383
|
+
#
|
384
|
+
# expect {
|
385
|
+
# HelloJob.perform_later
|
386
|
+
# HeavyLiftingJob.perform_later
|
387
|
+
# }.to have_enqueued_job(HelloJob).and have_enqueued_job(HeavyLiftingJob)
|
388
|
+
#
|
389
|
+
# expect {
|
390
|
+
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
|
391
|
+
# }.to have_enqueued_job.with(42).on_queue("low").at(Date.tomorrow.noon)
|
392
|
+
#
|
393
|
+
# expect {
|
394
|
+
# HelloJob.set(queue: "low").perform_later(42)
|
395
|
+
# }.to have_enqueued_job.with(42).on_queue("low").at(:no_wait)
|
396
|
+
#
|
397
|
+
# expect {
|
398
|
+
# HelloJob.perform_later('rspec_rails', 'rails', 42)
|
399
|
+
# }.to have_enqueued_job.with { |from, to, times|
|
400
|
+
# # Perform more complex argument matching using dynamic arguments
|
401
|
+
# expect(from).to include "_#{to}"
|
402
|
+
# }
|
403
|
+
def have_enqueued_job(job = nil)
|
404
|
+
check_active_job_adapter
|
405
|
+
ActiveJob::HaveEnqueuedJob.new(job)
|
406
|
+
end
|
407
|
+
alias_method :enqueue_job, :have_enqueued_job
|
408
|
+
|
409
|
+
# @api public
|
410
|
+
# Passes if a job has been enqueued. May chain at_least, at_most or exactly to specify a number of times.
|
411
|
+
#
|
412
|
+
# @example
|
413
|
+
# before { ActiveJob::Base.queue_adapter.enqueued_jobs.clear }
|
414
|
+
#
|
415
|
+
# HeavyLiftingJob.perform_later
|
416
|
+
# expect(HeavyLiftingJob).to have_been_enqueued
|
417
|
+
#
|
418
|
+
# HelloJob.perform_later
|
419
|
+
# HeavyLiftingJob.perform_later
|
420
|
+
# expect(HeavyLiftingJob).to have_been_enqueued.exactly(:once)
|
421
|
+
#
|
422
|
+
# 3.times { HelloJob.perform_later }
|
423
|
+
# expect(HelloJob).to have_been_enqueued.at_least(2).times
|
424
|
+
#
|
425
|
+
# HelloJob.perform_later
|
426
|
+
# expect(HelloJob).to enqueue_job(HelloJob).at_most(:twice)
|
427
|
+
#
|
428
|
+
# HelloJob.perform_later
|
429
|
+
# HeavyLiftingJob.perform_later
|
430
|
+
# expect(HelloJob).to have_been_enqueued
|
431
|
+
# expect(HeavyLiftingJob).to have_been_enqueued
|
432
|
+
#
|
433
|
+
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
|
434
|
+
# expect(HelloJob).to have_been_enqueued.with(42).on_queue("low").at(Date.tomorrow.noon)
|
435
|
+
#
|
436
|
+
# HelloJob.set(queue: "low").perform_later(42)
|
437
|
+
# expect(HelloJob).to have_been_enqueued.with(42).on_queue("low").at(:no_wait)
|
438
|
+
def have_been_enqueued
|
439
|
+
check_active_job_adapter
|
440
|
+
ActiveJob::HaveBeenEnqueued.new
|
441
|
+
end
|
442
|
+
|
443
|
+
# @api public
|
444
|
+
# Passes if a job has been performed inside block. May chain at_least, at_most or exactly to specify a number of times.
|
445
|
+
#
|
446
|
+
# @example
|
447
|
+
# expect {
|
448
|
+
# perform_enqueued_jobs { HeavyLiftingJob.perform_later }
|
449
|
+
# }.to have_performed_job
|
450
|
+
#
|
451
|
+
# expect {
|
452
|
+
# perform_enqueued_jobs {
|
453
|
+
# HelloJob.perform_later
|
454
|
+
# HeavyLiftingJob.perform_later
|
455
|
+
# }
|
456
|
+
# }.to have_performed_job(HelloJob).exactly(:once)
|
457
|
+
#
|
458
|
+
# expect {
|
459
|
+
# perform_enqueued_jobs { 3.times { HelloJob.perform_later } }
|
460
|
+
# }.to have_performed_job(HelloJob).at_least(2).times
|
461
|
+
#
|
462
|
+
# expect {
|
463
|
+
# perform_enqueued_jobs { HelloJob.perform_later }
|
464
|
+
# }.to have_performed_job(HelloJob).at_most(:twice)
|
465
|
+
#
|
466
|
+
# expect {
|
467
|
+
# perform_enqueued_jobs {
|
468
|
+
# HelloJob.perform_later
|
469
|
+
# HeavyLiftingJob.perform_later
|
470
|
+
# }
|
471
|
+
# }.to have_performed_job(HelloJob).and have_performed_job(HeavyLiftingJob)
|
472
|
+
#
|
473
|
+
# expect {
|
474
|
+
# perform_enqueued_jobs {
|
475
|
+
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
|
476
|
+
# }
|
477
|
+
# }.to have_performed_job.with(42).on_queue("low").at(Date.tomorrow.noon)
|
478
|
+
def have_performed_job(job = nil)
|
479
|
+
check_active_job_adapter
|
480
|
+
ActiveJob::HavePerformedJob.new(job)
|
481
|
+
end
|
482
|
+
alias_method :perform_job, :have_performed_job
|
483
|
+
|
484
|
+
# @api public
|
485
|
+
# Passes if a job has been performed. May chain at_least, at_most or exactly to specify a number of times.
|
486
|
+
#
|
487
|
+
# @example
|
488
|
+
# before do
|
489
|
+
# ActiveJob::Base.queue_adapter.performed_jobs.clear
|
490
|
+
# ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
|
491
|
+
# ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
|
492
|
+
# end
|
493
|
+
#
|
494
|
+
# HeavyLiftingJob.perform_later
|
495
|
+
# expect(HeavyLiftingJob).to have_been_performed
|
496
|
+
#
|
497
|
+
# HelloJob.perform_later
|
498
|
+
# HeavyLiftingJob.perform_later
|
499
|
+
# expect(HeavyLiftingJob).to have_been_performed.exactly(:once)
|
500
|
+
#
|
501
|
+
# 3.times { HelloJob.perform_later }
|
502
|
+
# expect(HelloJob).to have_been_performed.at_least(2).times
|
503
|
+
#
|
504
|
+
# HelloJob.perform_later
|
505
|
+
# HeavyLiftingJob.perform_later
|
506
|
+
# expect(HelloJob).to have_been_performed
|
507
|
+
# expect(HeavyLiftingJob).to have_been_performed
|
508
|
+
#
|
509
|
+
# HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
|
510
|
+
# expect(HelloJob).to have_been_performed.with(42).on_queue("low").at(Date.tomorrow.noon)
|
511
|
+
def have_been_performed
|
512
|
+
check_active_job_adapter
|
513
|
+
ActiveJob::HaveBeenPerformed.new
|
514
|
+
end
|
515
|
+
|
516
|
+
private
|
517
|
+
|
518
|
+
# @private
|
519
|
+
def check_active_job_adapter
|
520
|
+
return if ::ActiveJob::QueueAdapters::TestAdapter === ::ActiveJob::Base.queue_adapter
|
521
|
+
|
522
|
+
raise StandardError, "To use ActiveJob matchers set `ActiveJob::Base.queue_adapter = :test`"
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Rails
|
3
|
+
module Matchers
|
4
|
+
# @api private
|
5
|
+
#
|
6
|
+
# Base class to build matchers. Should not be instantiated directly.
|
7
|
+
class BaseMatcher
|
8
|
+
include RSpec::Matchers::Composable
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
# Used to detect when no arg is passed to `initialize`.
|
12
|
+
# `nil` cannot be used because it's a valid value to pass.
|
13
|
+
UNDEFINED = Object.new.freeze
|
14
|
+
|
15
|
+
# @private
|
16
|
+
attr_reader :actual, :expected, :rescued_exception
|
17
|
+
|
18
|
+
# @private
|
19
|
+
attr_writer :matcher_name
|
20
|
+
|
21
|
+
def initialize(expected = UNDEFINED)
|
22
|
+
@expected = expected unless UNDEFINED.equal?(expected)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
# Indicates if the match is successful. Delegates to `match`, which
|
27
|
+
# should be defined on a subclass. Takes care of consistently
|
28
|
+
# initializing the `actual` attribute.
|
29
|
+
def matches?(actual)
|
30
|
+
@actual = actual
|
31
|
+
match(expected, actual)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @api private
|
35
|
+
# Used to wrap a block of code that will indicate failure by
|
36
|
+
# raising one of the named exceptions.
|
37
|
+
#
|
38
|
+
# This is used by rspec-rails for some of its matchers that
|
39
|
+
# wrap rails' assertions.
|
40
|
+
def match_unless_raises(*exceptions)
|
41
|
+
exceptions.unshift Exception if exceptions.empty?
|
42
|
+
begin
|
43
|
+
yield
|
44
|
+
true
|
45
|
+
rescue *exceptions => @rescued_exception
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
# Generates a description using {RSpec::Matchers::EnglishPhrasing}.
|
52
|
+
# @return [String]
|
53
|
+
def description
|
54
|
+
desc = RSpec::Matchers::EnglishPhrasing.split_words(self.class.matcher_name)
|
55
|
+
desc << RSpec::Matchers::EnglishPhrasing.list(@expected) if defined?(@expected)
|
56
|
+
desc
|
57
|
+
end
|
58
|
+
|
59
|
+
# @api private
|
60
|
+
# Matchers are not diffable by default. Override this to make your
|
61
|
+
# subclass diffable.
|
62
|
+
def diffable?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
# @api private
|
67
|
+
# Most matchers are value matchers (i.e. meant to work with `expect(value)`)
|
68
|
+
# rather than block matchers (i.e. meant to work with `expect { }`), so
|
69
|
+
# this defaults to false. Block matchers must override this to return true.
|
70
|
+
def supports_block_expectations?
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
74
|
+
# @api private
|
75
|
+
def expects_call_stack_jump?
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
# @private
|
80
|
+
def expected_formatted
|
81
|
+
RSpec::Support::ObjectFormatter.format(@expected)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @private
|
85
|
+
def actual_formatted
|
86
|
+
RSpec::Support::ObjectFormatter.format(@actual)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @private
|
90
|
+
def self.matcher_name
|
91
|
+
@matcher_name ||= underscore(name.split('::').last)
|
92
|
+
end
|
93
|
+
|
94
|
+
# @private
|
95
|
+
def matcher_name
|
96
|
+
if defined?(@matcher_name)
|
97
|
+
@matcher_name
|
98
|
+
else
|
99
|
+
self.class.matcher_name
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# @private
|
104
|
+
# Borrowed from ActiveSupport.
|
105
|
+
def self.underscore(camel_cased_word)
|
106
|
+
word = camel_cased_word.to_s.dup
|
107
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
108
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
109
|
+
word.tr!('-', '_')
|
110
|
+
word.downcase!
|
111
|
+
word
|
112
|
+
end
|
113
|
+
private_class_method :underscore
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def assert_ivars(*expected_ivars)
|
118
|
+
return unless (expected_ivars - present_ivars).any?
|
119
|
+
|
120
|
+
ivar_list = RSpec::Matchers::EnglishPhrasing.list(expected_ivars)
|
121
|
+
raise "#{self.class.name} needs to supply#{ivar_list}"
|
122
|
+
end
|
123
|
+
|
124
|
+
alias present_ivars instance_variables
|
125
|
+
|
126
|
+
# @private
|
127
|
+
module HashFormatting
|
128
|
+
# `{ :a => 5, :b => 2 }.inspect` produces:
|
129
|
+
#
|
130
|
+
# {:a=>5, :b=>2}
|
131
|
+
#
|
132
|
+
# ...but it looks much better as:
|
133
|
+
#
|
134
|
+
# {:a => 5, :b => 2}
|
135
|
+
#
|
136
|
+
# This is idempotent and safe to run on a string multiple times.
|
137
|
+
def improve_hash_formatting(inspect_string)
|
138
|
+
inspect_string.gsub(/(\S)=>(\S)/, '\1 => \2')
|
139
|
+
end
|
140
|
+
module_function :improve_hash_formatting
|
141
|
+
end
|
142
|
+
|
143
|
+
include HashFormatting
|
144
|
+
|
145
|
+
# @api private
|
146
|
+
# Provides default implementations of failure messages, based on the `description`.
|
147
|
+
module DefaultFailureMessages
|
148
|
+
# @api private
|
149
|
+
# Provides a good generic failure message. Based on `description`.
|
150
|
+
# When subclassing, if you are not satisfied with this failure message
|
151
|
+
# you often only need to override `description`.
|
152
|
+
# @return [String]
|
153
|
+
def failure_message
|
154
|
+
"expected #{description_of @actual} to #{description}".dup
|
155
|
+
end
|
156
|
+
|
157
|
+
# @api private
|
158
|
+
# Provides a good generic negative failure message. Based on `description`.
|
159
|
+
# When subclassing, if you are not satisfied with this failure message
|
160
|
+
# you often only need to override `description`.
|
161
|
+
# @return [String]
|
162
|
+
def failure_message_when_negated
|
163
|
+
"expected #{description_of @actual} not to #{description}".dup
|
164
|
+
end
|
165
|
+
|
166
|
+
# @private
|
167
|
+
def self.has_default_failure_messages?(matcher)
|
168
|
+
matcher.method(:failure_message).owner == self &&
|
169
|
+
matcher.method(:failure_message_when_negated).owner == self
|
170
|
+
rescue NameError
|
171
|
+
false
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
include DefaultFailureMessages
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|