rspec-rails 4.0.0.beta3 → 4.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Capybara.md +1 -1
  5. data/Changelog.md +48 -12
  6. data/README.md +3 -4
  7. data/lib/generators/rspec.rb +0 -6
  8. data/lib/generators/rspec/controller/controller_generator.rb +13 -5
  9. data/lib/generators/rspec/controller/templates/request_spec.rb +14 -0
  10. data/lib/generators/rspec/controller/templates/routing_spec.rb +1 -1
  11. data/lib/generators/rspec/feature/feature_generator.rb +2 -2
  12. data/lib/generators/rspec/generators/generator_generator.rb +1 -1
  13. data/lib/generators/rspec/helper/helper_generator.rb +1 -1
  14. data/lib/generators/rspec/install/install_generator.rb +4 -4
  15. data/lib/generators/rspec/install/templates/spec/rails_helper.rb +2 -15
  16. data/lib/generators/rspec/integration/integration_generator.rb +3 -3
  17. data/lib/generators/rspec/mailer/mailer_generator.rb +1 -1
  18. data/lib/generators/rspec/model/model_generator.rb +4 -4
  19. data/lib/generators/rspec/scaffold/scaffold_generator.rb +10 -10
  20. data/lib/generators/rspec/scaffold/templates/api_controller_spec.rb +0 -36
  21. data/lib/generators/rspec/scaffold/templates/controller_spec.rb +10 -10
  22. data/lib/generators/rspec/scaffold/templates/edit_spec.rb +1 -1
  23. data/lib/generators/rspec/scaffold/templates/index_spec.rb +2 -2
  24. data/lib/generators/rspec/scaffold/templates/new_spec.rb +1 -1
  25. data/lib/generators/rspec/scaffold/templates/routing_spec.rb +8 -10
  26. data/lib/generators/rspec/scaffold/templates/show_spec.rb +1 -1
  27. data/lib/generators/rspec/system/system_generator.rb +1 -1
  28. data/lib/generators/rspec/view/view_generator.rb +2 -2
  29. data/lib/rspec-rails.rb +4 -7
  30. data/lib/rspec/rails/adapters.rb +8 -74
  31. data/lib/rspec/rails/configuration.rb +32 -32
  32. data/lib/rspec/rails/example/controller_example_group.rb +4 -4
  33. data/lib/rspec/rails/example/feature_example_group.rb +5 -7
  34. data/lib/rspec/rails/example/helper_example_group.rb +2 -10
  35. data/lib/rspec/rails/example/rails_example_group.rb +1 -1
  36. data/lib/rspec/rails/example/system_example_group.rb +6 -3
  37. data/lib/rspec/rails/example/view_example_group.rb +38 -48
  38. data/lib/rspec/rails/extensions/active_record/proxy.rb +1 -9
  39. data/lib/rspec/rails/feature_check.rb +1 -28
  40. data/lib/rspec/rails/fixture_file_upload_support.rb +1 -1
  41. data/lib/rspec/rails/fixture_support.rb +8 -13
  42. data/lib/rspec/rails/matchers/action_cable.rb +1 -1
  43. data/lib/rspec/rails/matchers/active_job.rb +124 -14
  44. data/lib/rspec/rails/matchers/base_matcher.rb +4 -10
  45. data/lib/rspec/rails/matchers/have_enqueued_mail.rb +5 -2
  46. data/lib/rspec/rails/matchers/have_http_status.rb +7 -7
  47. data/lib/rspec/rails/matchers/routing_matchers.rb +10 -10
  48. data/lib/rspec/rails/tasks/rspec.rake +3 -13
  49. data/lib/rspec/rails/vendor/capybara.rb +10 -10
  50. data/lib/rspec/rails/version.rb +1 -1
  51. data/lib/rspec/rails/view_path_builder.rb +1 -1
  52. data/lib/rspec/rails/view_rendering.rb +3 -3
  53. metadata +16 -17
  54. metadata.gz.sig +0 -0
  55. data/lib/generators/rspec/observer/observer_generator.rb +0 -13
  56. data/lib/generators/rspec/observer/templates/observer_spec.rb +0 -7
@@ -94,9 +94,9 @@ module RSpec
94
94
  self.routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
95
95
  r.draw do
96
96
  resources resource_name,
97
- :as => resource_as,
98
- :module => resource_module,
99
- :path => resource_path
97
+ as: resource_as,
98
+ module: resource_module,
99
+ path: resource_path
100
100
  end
101
101
  end
102
102
  end
@@ -159,7 +159,7 @@ module RSpec
159
159
  #
160
160
  # expect do
161
161
  # bypass_rescue
162
- # get :show, :id => profile.id + 1
162
+ # get :show, id: profile.id + 1
163
163
  # end.to raise_error(/403 Forbidden/)
164
164
  # end
165
165
  # end
@@ -15,9 +15,7 @@ module RSpec
15
15
  include app.routes.url_helpers if app.routes.respond_to?(:url_helpers)
16
16
  include app.routes.mounted_helpers if app.routes.respond_to?(:mounted_helpers)
17
17
 
18
- if respond_to?(:default_url_options)
19
- default_url_options[:host] ||= ::RSpec::Rails::FeatureExampleGroup::DEFAULT_HOST
20
- end
18
+ default_url_options[:host] ||= ::RSpec::Rails::FeatureExampleGroup::DEFAULT_HOST
21
19
  end
22
20
  end
23
21
 
@@ -37,9 +35,9 @@ end
37
35
 
38
36
  unless RSpec.respond_to?(:feature)
39
37
  opts = {
40
- :capybara_feature => true,
41
- :type => :feature,
42
- :skip => <<-EOT.squish
38
+ capybara_feature: true,
39
+ type: :feature,
40
+ skip: <<-EOT.squish
43
41
  Feature specs require the Capybara (https://github.com/jnicklas/capybara)
44
42
  gem, version 2.2.0 or later. We recommend version 2.4.0 or later to avoid
45
43
  some deprecation warnings and have support for
@@ -63,7 +61,7 @@ unless RSpec.respond_to?(:feature)
63
61
  main_feature = nil unless c.expose_dsl_globally?
64
62
  c.alias_example_group_to :feature, opts
65
63
  c.alias_example_to :scenario
66
- c.alias_example_to :xscenario, :skip => 'Temporarily skipped with xscenario'
64
+ c.alias_example_to :xscenario, skip: 'Temporarily skipped with xscenario'
67
65
  end
68
66
 
69
67
  # Due to load order issues and `config.expose_dsl_globally?` defaulting to
@@ -12,16 +12,8 @@ module RSpec
12
12
 
13
13
  # @private
14
14
  module ClassMethods
15
- if ::Rails::VERSION::MAJOR > 3
16
- def determine_constant_from_test_name(_ignore)
17
- described_class if yield(described_class)
18
- end
19
- else
20
- def determine_default_helper_class(_ignore)
21
- return unless Module === described_class && !(Class === described_class)
22
-
23
- described_class
24
- end
15
+ def determine_constant_from_test_name(_ignore)
16
+ described_class if yield(described_class)
25
17
  end
26
18
  end
27
19
 
@@ -9,7 +9,7 @@ module RSpec
9
9
  module RailsExampleGroup
10
10
  extend ActiveSupport::Concern
11
11
  include RSpec::Rails::SetupAndTeardownAdapter
12
- include RSpec::Rails::MinitestLifecycleAdapter if ::Rails::VERSION::STRING >= '4'
12
+ include RSpec::Rails::MinitestLifecycleAdapter
13
13
  include RSpec::Rails::MinitestAssertionAdapter
14
14
  include RSpec::Rails::FixtureSupport
15
15
  end
@@ -85,6 +85,12 @@ module RSpec
85
85
  def initialize(*args, &blk)
86
86
  super(*args, &blk)
87
87
  @driver = nil
88
+
89
+ self.class.before do
90
+ # A user may have already set the driver, so only default if driver
91
+ # is not set
92
+ driven_by(:selenium) unless @driver
93
+ end
88
94
  end
89
95
 
90
96
  def driven_by(*args, &blk)
@@ -92,9 +98,6 @@ module RSpec
92
98
  end
93
99
 
94
100
  before do
95
- # A user may have already set the driver, so only default if driver
96
- # is not set
97
- driven_by(:selenium) unless @driver
98
101
  @routes = ::Rails.application.routes
99
102
  end
100
103
 
@@ -43,16 +43,13 @@ module RSpec
43
43
 
44
44
  included do
45
45
  include ::Rails.application.routes.url_helpers
46
-
47
- if ::Rails.application.routes.respond_to?(:mounted_helpers)
48
- include ::Rails.application.routes.mounted_helpers
49
- end
46
+ include ::Rails.application.routes.mounted_helpers
50
47
  end
51
48
 
52
49
  # @overload render
53
- # @overload render({:partial => path_to_file})
54
- # @overload render({:partial => path_to_file}, {... locals ...})
55
- # @overload render({:partial => path_to_file}, {... locals ...}) do ... end
50
+ # @overload render({partial: path_to_file})
51
+ # @overload render({partial: path_to_file}, {... locals ...})
52
+ # @overload render({partial: path_to_file}, {... locals ...}) do ... end
56
53
  #
57
54
  # Delegates to ActionView::Base#render, so see documentation on that
58
55
  # for more info.
@@ -62,7 +59,7 @@ module RSpec
62
59
  #
63
60
  # describe "widgets/new.html.erb" do
64
61
  # it "shows all the widgets" do
65
- # render # => view.render(:file => "widgets/new.html.erb")
62
+ # render # => view.render(file: "widgets/new.html.erb")
66
63
  # # ...
67
64
  # end
68
65
  # end
@@ -105,7 +102,7 @@ module RSpec
105
102
 
106
103
  # @deprecated Use `view` instead.
107
104
  def template
108
- RSpec.deprecate("template", :replacement => "view")
105
+ RSpec.deprecate("template", replacement: "view")
109
106
  view
110
107
  end
111
108
 
@@ -128,41 +125,37 @@ module RSpec
128
125
  private
129
126
 
130
127
  def _default_render_options
131
- if ::Rails::VERSION::STRING >= '3.2'
132
- formats = if ActionView::Template::Types.respond_to?(:symbols)
133
- ActionView::Template::Types.symbols
134
- else
135
- [:html, :text, :js, :css, :xml, :json].map(&:to_s)
136
- end.map { |x| Regexp.escape(x) }.join("|")
137
-
138
- handlers = ActionView::Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
139
- locales = "[a-z]{2}(?:-[A-Z]{2})?"
140
- variants = "[^.]*"
141
- path_regex = %r{
142
- \A
143
- (?<template>.*?)
144
- (?:\.(?<locale>#{locales}))??
145
- (?:\.(?<format>#{formats}))??
146
- (?:\+(?<variant>#{variants}))??
147
- (?:\.(?<handler>#{handlers}))?
148
- \z
149
- }x
150
-
151
- # This regex should always find a match.
152
- # Worst case, everything will be nil, and :template will just be
153
- # the original string.
154
- match = path_regex.match(_default_file_to_render)
155
-
156
- render_options = { :template => match[:template] }
157
- render_options[:handlers] = [match[:handler]] if match[:handler]
158
- render_options[:formats] = [match[:format].to_sym] if match[:format]
159
- render_options[:locales] = [match[:locale]] if match[:locale]
160
- render_options[:variants] = [match[:variant]] if match[:variant]
161
-
162
- render_options
163
- else
164
- { :template => _default_file_to_render }
165
- end
128
+ formats = if ActionView::Template::Types.respond_to?(:symbols)
129
+ ActionView::Template::Types.symbols
130
+ else
131
+ [:html, :text, :js, :css, :xml, :json].map(&:to_s)
132
+ end.map { |x| Regexp.escape(x) }.join("|")
133
+
134
+ handlers = ActionView::Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
135
+ locales = "[a-z]{2}(?:-[A-Z]{2})?"
136
+ variants = "[^.]*"
137
+ path_regex = %r{
138
+ \A
139
+ (?<template>.*?)
140
+ (?:\.(?<locale>#{locales}))??
141
+ (?:\.(?<format>#{formats}))??
142
+ (?:\+(?<variant>#{variants}))??
143
+ (?:\.(?<handler>#{handlers}))?
144
+ \z
145
+ }x
146
+
147
+ # This regex should always find a match.
148
+ # Worst case, everything will be nil, and :template will just be
149
+ # the original string.
150
+ match = path_regex.match(_default_file_to_render)
151
+
152
+ render_options = { template: match[:template] }
153
+ render_options[:handlers] = [match[:handler]] if match[:handler]
154
+ render_options[:formats] = [match[:format].to_sym] if match[:format]
155
+ render_options[:locales] = [match[:locale]] if match[:locale]
156
+ render_options[:variants] = [match[:variant]] if match[:variant]
157
+
158
+ render_options
166
159
  end
167
160
 
168
161
  def _path_parts
@@ -192,10 +185,7 @@ module RSpec
192
185
 
193
186
  before do
194
187
  _include_controller_helpers
195
- if view.lookup_context.respond_to?(:prefixes)
196
- # rails 3.1
197
- view.lookup_context.prefixes << _controller_path
198
- end
188
+ view.lookup_context.prefixes << _controller_path
199
189
 
200
190
  controller.controller_path = _controller_path
201
191
 
@@ -2,15 +2,7 @@ RSpec.configure do |rspec|
2
2
  # Delay this in order to give users a chance to configure `expect_with`...
3
3
  rspec.before(:suite) do
4
4
  if defined?(RSpec::Matchers) && RSpec::Matchers.configuration.syntax.include?(:should) && defined?(ActiveRecord::Associations)
5
- # In Rails 3.0, it was AssociationProxy.
6
- # In 3.1+, it's CollectionProxy.
7
- const_name = [:CollectionProxy, :AssociationProxy].find do |const|
8
- ActiveRecord::Associations.const_defined?(const)
9
- end
10
-
11
- proxy_class = ActiveRecord::Associations.const_get(const_name)
12
-
13
- RSpec::Matchers.configuration.add_should_and_should_not_to proxy_class
5
+ RSpec::Matchers.configuration.add_should_and_should_not_to ActiveRecord::Associations::CollectionProxy
14
6
  end
15
7
  end
16
8
  end
@@ -3,16 +3,6 @@ module RSpec
3
3
  # @private
4
4
  module FeatureCheck
5
5
  module_function
6
- def can_check_pending_migrations?
7
- has_active_record_migration? &&
8
- ::ActiveRecord::Migration.respond_to?(:check_pending!)
9
- end
10
-
11
- def can_maintain_test_schema?
12
- has_active_record_migration? &&
13
- ::ActiveRecord::Migration.respond_to?(:maintain_test_schema!)
14
- end
15
-
16
6
  def has_active_job?
17
7
  defined?(::ActiveJob)
18
8
  end
@@ -37,11 +27,6 @@ module RSpec
37
27
  defined?(::ActionCable) && ActionCable::VERSION::MAJOR >= 6
38
28
  end
39
29
 
40
- def has_action_mailer_show_preview?
41
- has_action_mailer_preview? &&
42
- ::ActionMailer::Base.respond_to?(:show_previews=)
43
- end
44
-
45
30
  def has_action_mailer_parameterized?
46
31
  has_action_mailer? && defined?(::ActionMailer::Parameterized)
47
32
  end
@@ -54,20 +39,8 @@ module RSpec
54
39
  defined?(::ActionMailbox)
55
40
  end
56
41
 
57
- def has_1_9_hash_syntax?
58
- ::Rails::VERSION::STRING > '4.0'
59
- end
60
-
61
- def has_file_fixture?
62
- ::Rails::VERSION::STRING > '5.0'
63
- end
64
-
65
42
  def type_metatag(type)
66
- if has_1_9_hash_syntax?
67
- "type: :#{type}"
68
- else
69
- ":type => :#{type}"
70
- end
43
+ "type: :#{type}"
71
44
  end
72
45
  end
73
46
  end
@@ -2,7 +2,7 @@ module RSpec
2
2
  module Rails
3
3
  # @private
4
4
  module FixtureFileUploadSupport
5
- delegate :fixture_file_upload, :to => :rails_fixture_file_wrapper
5
+ delegate :fixture_file_upload, to: :rails_fixture_file_wrapper
6
6
 
7
7
  private
8
8
 
@@ -5,29 +5,18 @@ module RSpec
5
5
  if defined?(ActiveRecord::TestFixtures)
6
6
  extend ActiveSupport::Concern
7
7
  include RSpec::Rails::SetupAndTeardownAdapter
8
- include RSpec::Rails::MinitestLifecycleAdapter if ::ActiveRecord::VERSION::STRING > '4'
8
+ include RSpec::Rails::MinitestLifecycleAdapter
9
9
  include RSpec::Rails::MinitestAssertionAdapter
10
10
  include ActiveRecord::TestFixtures
11
11
 
12
12
  included do
13
- # TODO: (DC 2011-06-25) this is necessary because fixture_file_upload
14
- # accesses fixture_path directly on ActiveSupport::TestCase. This is
15
- # fixed in rails by https://github.com/rails/rails/pull/1861, which
16
- # should be part of the 3.1 release, at which point we can include
17
- # these lines for rails < 3.1.
18
- ActiveSupport::TestCase.class_exec do
19
- include ActiveRecord::TestFixtures
20
- self.fixture_path = RSpec.configuration.fixture_path
21
- end
22
- # /TODO
23
-
24
13
  self.fixture_path = RSpec.configuration.fixture_path
25
14
  if ::Rails::VERSION::STRING > '5'
26
15
  self.use_transactional_tests = RSpec.configuration.use_transactional_fixtures
27
16
  else
28
17
  self.use_transactional_fixtures = RSpec.configuration.use_transactional_fixtures
29
18
  end
30
- self.use_instantiated_fixtures = RSpec.configuration.use_instantiated_fixtures
19
+ self.use_instantiated_fixtures = RSpec.configuration.use_instantiated_fixtures
31
20
 
32
21
  def self.fixtures(*args)
33
22
  orig_methods = private_instance_methods
@@ -50,6 +39,12 @@ module RSpec
50
39
  end
51
40
  end
52
41
 
42
+ if ::Rails.version.to_f >= 6.1
43
+ def name
44
+ @example
45
+ end
46
+ end
47
+
53
48
  fixtures RSpec.configuration.global_fixtures if RSpec.configuration.global_fixtures
54
49
  end
55
50
  end
@@ -48,7 +48,7 @@ module RSpec
48
48
  def have_broadcasted_to(target = nil)
49
49
  check_action_cable_adapter
50
50
 
51
- ActionCable::HaveBroadcastedTo.new(target, :channel => described_class)
51
+ ActionCable::HaveBroadcastedTo.new(target, channel: described_class)
52
52
  end
53
53
  alias_method :broadcast_to, :have_broadcasted_to
54
54
 
@@ -67,7 +67,7 @@ module RSpec
67
67
  end
68
68
 
69
69
  def failure_message
70
- "expected to enqueue #{base_message}".tap do |msg|
70
+ "expected to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}".tap do |msg|
71
71
  if @unmatching_jobs.any?
72
72
  msg << "\nQueued jobs:"
73
73
  @unmatching_jobs.each do |job|
@@ -78,7 +78,7 @@ module RSpec
78
78
  end
79
79
 
80
80
  def failure_message_when_negated
81
- "expected not to enqueue #{base_message}"
81
+ "expected not to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}"
82
82
  end
83
83
 
84
84
  def message_expectation_modifier
@@ -97,7 +97,7 @@ module RSpec
97
97
 
98
98
  def check(jobs)
99
99
  @matching_jobs, @unmatching_jobs = jobs.partition do |job|
100
- if job_match?(job) && arguments_match?(job) && other_attributes_match?(job)
100
+ if job_match?(job) && arguments_match?(job) && queue_match?(job) && at_match?(job)
101
101
  args = deserialize_arguments(job)
102
102
  @block.call(*args)
103
103
  true
@@ -119,7 +119,7 @@ module RSpec
119
119
  msg << " with #{@args}," if @args.any?
120
120
  msg << " on queue #{@queue}," if @queue
121
121
  msg << " at #{@at.inspect}," if @at
122
- msg << " but enqueued #{@matching_jobs_count}"
122
+ msg << " but #{self.class::MESSAGE_EXPECTATION_ACTION} #{@matching_jobs_count}"
123
123
  end
124
124
  end
125
125
 
@@ -148,19 +148,18 @@ module RSpec
148
148
  end
149
149
  end
150
150
 
151
- def other_attributes_match?(job)
152
- serialized_attributes.all? { |key, value| value == job[key] }
153
- end
151
+ def queue_match?(job)
152
+ return true unless @queue
154
153
 
155
- def serialized_attributes
156
- {}.tap do |attributes|
157
- attributes[:at] = serialized_at if @at
158
- attributes[:queue] = @queue if @queue
159
- end
154
+ @queue == job[:queue]
160
155
  end
161
156
 
162
- def serialized_at
163
- @at == :no_wait ? nil : @at.to_f
157
+ def at_match?(job)
158
+ return true unless @at
159
+ return job[:at].nil? if @at == :no_wait
160
+ return false unless job[:at]
161
+
162
+ values_match?(@at, Time.at(job[:at]))
164
163
  end
165
164
 
166
165
  def set_expected_number(relativity, count)
@@ -194,6 +193,9 @@ module RSpec
194
193
 
195
194
  # @private
196
195
  class HaveEnqueuedJob < Base
196
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
197
+ MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze
198
+
197
199
  def initialize(job)
198
200
  super()
199
201
  @job = job
@@ -218,6 +220,9 @@ module RSpec
218
220
 
219
221
  # @private
220
222
  class HaveBeenEnqueued < Base
223
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
224
+ MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze
225
+
221
226
  def matches?(job)
222
227
  @job = job
223
228
  check(queue_adapter.enqueued_jobs)
@@ -229,6 +234,38 @@ module RSpec
229
234
  !matches?(proc)
230
235
  end
231
236
  end
237
+
238
+ # @private
239
+ class HavePerformedJob < Base
240
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
241
+ MESSAGE_EXPECTATION_ACTION = 'performed'.freeze
242
+
243
+ def initialize(job)
244
+ super()
245
+ @job = job
246
+ end
247
+
248
+ def matches?(proc)
249
+ raise ArgumentError, "have_performed_job only supports block expectations" unless Proc === proc
250
+
251
+ original_performed_jobs_count = queue_adapter.performed_jobs.count
252
+ proc.call
253
+ in_block_jobs = queue_adapter.performed_jobs.drop(original_performed_jobs_count)
254
+
255
+ check(in_block_jobs)
256
+ end
257
+ end
258
+
259
+ # @private
260
+ class HaveBeenPerformed < Base
261
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
262
+ MESSAGE_EXPECTATION_ACTION = 'performed'.freeze
263
+
264
+ def matches?(job)
265
+ @job = job
266
+ check(queue_adapter.performed_jobs)
267
+ end
268
+ end
232
269
  end
233
270
 
234
271
  # @api public
@@ -316,6 +353,79 @@ module RSpec
316
353
  ActiveJob::HaveBeenEnqueued.new
317
354
  end
318
355
 
356
+ # @api public
357
+ # Passes if a job has been performed inside block. May chain at_least, at_most or exactly to specify a number of times.
358
+ #
359
+ # @example
360
+ # expect {
361
+ # perform_jobs { HeavyLiftingJob.perform_later }
362
+ # }.to have_performed_job
363
+ #
364
+ # expect {
365
+ # perform_jobs {
366
+ # HelloJob.perform_later
367
+ # HeavyLiftingJob.perform_later
368
+ # }
369
+ # }.to have_performed_job(HelloJob).exactly(:once)
370
+ #
371
+ # expect {
372
+ # perform_jobs { 3.times { HelloJob.perform_later } }
373
+ # }.to have_performed_job(HelloJob).at_least(2).times
374
+ #
375
+ # expect {
376
+ # perform_jobs { HelloJob.perform_later }
377
+ # }.to have_performed_job(HelloJob).at_most(:twice)
378
+ #
379
+ # expect {
380
+ # perform_jobs {
381
+ # HelloJob.perform_later
382
+ # HeavyLiftingJob.perform_later
383
+ # }
384
+ # }.to have_performed_job(HelloJob).and have_performed_job(HeavyLiftingJob)
385
+ #
386
+ # expect {
387
+ # perform_jobs {
388
+ # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
389
+ # }
390
+ # }.to have_performed_job.with(42).on_queue("low").at(Date.tomorrow.noon)
391
+ def have_performed_job(job = nil)
392
+ check_active_job_adapter
393
+ ActiveJob::HavePerformedJob.new(job)
394
+ end
395
+ alias_method :perform_job, :have_performed_job
396
+
397
+ # @api public
398
+ # Passes if a job has been performed. May chain at_least, at_most or exactly to specify a number of times.
399
+ #
400
+ # @example
401
+ # before do
402
+ # ActiveJob::Base.queue_adapter.performed_jobs.clear
403
+ # ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
404
+ # ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
405
+ # end
406
+ #
407
+ # HeavyLiftingJob.perform_later
408
+ # expect(HeavyLiftingJob).to have_been_performed
409
+ #
410
+ # HelloJob.perform_later
411
+ # HeavyLiftingJob.perform_later
412
+ # expect(HeavyLiftingJob).to have_been_performed.exactly(:once)
413
+ #
414
+ # 3.times { HelloJob.perform_later }
415
+ # expect(HelloJob).to have_been_performed.at_least(2).times
416
+ #
417
+ # HelloJob.perform_later
418
+ # HeavyLiftingJob.perform_later
419
+ # expect(HelloJob).to have_been_performed
420
+ # expect(HeavyLiftingJob).to have_been_performed
421
+ #
422
+ # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
423
+ # expect(HelloJob).to have_been_performed.with(42).on_queue("low").at(Date.tomorrow.noon)
424
+ def have_been_performed
425
+ check_active_job_adapter
426
+ ActiveJob::HaveBeenPerformed.new
427
+ end
428
+
319
429
  private
320
430
 
321
431
  # @private