rspec-rails 6.1.3 → 8.0.2

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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Changelog.md +296 -198
  4. data/README.md +31 -33
  5. data/lib/generators/rspec/authentication/authentication_generator.rb +25 -0
  6. data/lib/generators/rspec/authentication/templates/user_spec.rb +5 -0
  7. data/lib/generators/rspec/authentication/templates/users.yml +11 -0
  8. data/lib/generators/rspec/install/templates/spec/rails_helper.rb +23 -16
  9. data/lib/generators/rspec/scaffold/templates/api_controller_spec.rb +2 -2
  10. data/lib/generators/rspec/scaffold/templates/api_request_spec.rb +2 -2
  11. data/lib/generators/rspec/scaffold/templates/controller_spec.rb +2 -17
  12. data/lib/generators/rspec/scaffold/templates/index_spec.rb +1 -1
  13. data/lib/generators/rspec/scaffold/templates/request_spec.rb +2 -17
  14. data/lib/rspec/rails/configuration.rb +3 -67
  15. data/lib/rspec/rails/example/rails_example_group.rb +5 -9
  16. data/lib/rspec/rails/example/system_example_group.rb +41 -36
  17. data/lib/rspec/rails/fixture_file_upload_support.rb +2 -8
  18. data/lib/rspec/rails/fixture_support.rb +13 -44
  19. data/lib/rspec/rails/matchers/action_cable/have_broadcasted_to.rb +10 -3
  20. data/lib/rspec/rails/matchers/action_cable.rb +6 -1
  21. data/lib/rspec/rails/matchers/active_job.rb +77 -8
  22. data/lib/rspec/rails/matchers/have_enqueued_mail.rb +37 -5
  23. data/lib/rspec/rails/matchers/have_http_status.rb +2 -2
  24. data/lib/rspec/rails/tasks/rspec.rake +3 -1
  25. data/lib/rspec/rails/version.rb +1 -1
  26. data/lib/rspec-rails.rb +20 -11
  27. data.tar.gz.sig +0 -0
  28. metadata +37 -39
  29. metadata.gz.sig +0 -0
  30. data/lib/generators/rspec/integration/integration_generator.rb +0 -29
@@ -14,10 +14,8 @@ module RSpec
14
14
  resolved_fixture_path =
15
15
  if respond_to?(:file_fixture_path) && !file_fixture_path.nil?
16
16
  file_fixture_path.to_s
17
- elsif respond_to?(:fixture_paths)
18
- (RSpec.configuration.fixture_paths&.first || '').to_s
19
17
  else
20
- (RSpec.configuration.fixture_path || '').to_s
18
+ (RSpec.configuration.fixture_paths&.first || '').to_s
21
19
  end
22
20
  RailsFixtureFileWrapper.file_fixture_path = File.join(resolved_fixture_path, '') unless resolved_fixture_path.strip.empty?
23
21
  RailsFixtureFileWrapper.instance
@@ -28,11 +26,7 @@ module RSpec
28
26
  include ActiveSupport::Testing::FileFixtures
29
27
 
30
28
  class << self
31
- if ::Rails::VERSION::STRING < "7.1.0"
32
- attr_accessor :fixture_path
33
- else
34
- attr_accessor :fixture_paths
35
- end
29
+ attr_accessor :fixture_paths
36
30
 
37
31
  # Get instance of wrapper
38
32
  def instance
@@ -10,8 +10,7 @@ module RSpec
10
10
  include ActiveRecord::TestFixtures
11
11
 
12
12
  # @private prevent ActiveSupport::TestFixtures to start a DB transaction.
13
- # Monkey patched to avoid collisions with 'let(:name)' in Rails 6.1 and after
14
- # and let(:method_name) before Rails 6.1.
13
+ # Monkey patched to avoid collisions with 'let(:name)' since Rails 6.1
15
14
  def run_in_transaction?
16
15
  current_example_name = (RSpec.current_example && RSpec.current_example.metadata[:description])
17
16
  use_transactional_tests && !self.class.uses_transaction?(current_example_name)
@@ -21,12 +20,7 @@ module RSpec
21
20
  if RSpec.configuration.use_active_record?
22
21
  include Fixtures
23
22
 
24
- # TestFixtures#fixture_path is deprecated and will be removed in Rails 7.2
25
- if respond_to?(:fixture_paths=)
26
- self.fixture_paths = RSpec.configuration.fixture_paths
27
- else
28
- self.fixture_path = RSpec.configuration.fixture_path
29
- end
23
+ self.fixture_paths = RSpec.configuration.fixture_paths
30
24
 
31
25
  self.use_transactional_tests = RSpec.configuration.use_transactional_fixtures
32
26
  self.use_instantiated_fixtures = RSpec.configuration.use_instantiated_fixtures
@@ -38,50 +32,25 @@ module RSpec
38
32
  module Fixtures
39
33
  extend ActiveSupport::Concern
40
34
 
41
- # rubocop:disable Metrics/BlockLength
42
35
  class_methods do
43
- if ::Rails.version.to_f >= 7.1
44
- def fixtures(*args)
45
- super.tap do
46
- fixture_sets.each_pair do |method_name, fixture_name|
47
- proxy_method_warning_if_called_in_before_context_scope(method_name, fixture_name)
48
- end
49
- end
50
- end
51
-
52
- def proxy_method_warning_if_called_in_before_context_scope(method_name, fixture_name)
53
- define_method(method_name) do |*args, **kwargs, &blk|
54
- if RSpec.current_scope == :before_context_hook
55
- RSpec.warn_with("Calling fixture method in before :context ")
56
- else
57
- access_fixture(fixture_name, *args, **kwargs, &blk)
58
- end
59
- end
60
- end
61
- else
62
- def fixtures(*args)
63
- orig_methods = private_instance_methods
64
- super.tap do
65
- new_methods = private_instance_methods - orig_methods
66
- new_methods.each do |method_name|
67
- proxy_method_warning_if_called_in_before_context_scope(method_name)
68
- end
36
+ def fixtures(*args)
37
+ super.tap do
38
+ fixture_sets.each_pair do |method_name, fixture_name|
39
+ proxy_method_warning_if_called_in_before_context_scope(method_name, fixture_name)
69
40
  end
70
41
  end
42
+ end
71
43
 
72
- def proxy_method_warning_if_called_in_before_context_scope(method_name)
73
- orig_implementation = instance_method(method_name)
74
- define_method(method_name) do |*args, &blk|
75
- if RSpec.current_scope == :before_context_hook
76
- RSpec.warn_with("Calling fixture method in before :context ")
77
- else
78
- orig_implementation.bind(self).call(*args, &blk)
79
- end
44
+ def proxy_method_warning_if_called_in_before_context_scope(method_name, fixture_name)
45
+ define_method(method_name) do |*args, **kwargs, &blk|
46
+ if RSpec.current_scope == :before_context_hook
47
+ RSpec.warn_with("Calling fixture method in before :context ")
48
+ else
49
+ access_fixture(fixture_name, *args, **kwargs, &blk)
80
50
  end
81
51
  end
82
52
  end
83
53
  end
84
- # rubocop:enable Metrics/BlockLength
85
54
  end
86
55
  end
87
56
  end
@@ -51,6 +51,10 @@ module RSpec
51
51
  exactly(:thrice)
52
52
  end
53
53
 
54
+ def description
55
+ "have broadcasted #{base_description}"
56
+ end
57
+
54
58
  def failure_message
55
59
  "expected to broadcast #{base_message}".tap do |msg|
56
60
  if @unmatching_msgs.any?
@@ -140,18 +144,21 @@ module RSpec
140
144
  end
141
145
  end
142
146
 
143
- def base_message
147
+ def base_description
144
148
  "#{message_expectation_modifier} #{@expected_number} messages to #{stream}".tap do |msg|
145
149
  msg << " with #{data_description(@data)}" unless @data.nil?
146
- msg << ", but broadcast #{@matching_msgs_count}"
147
150
  end
148
151
  end
149
152
 
153
+ def base_message
154
+ "#{base_description}, but broadcast #{@matching_msgs_count}"
155
+ end
156
+
150
157
  def data_description(data)
151
158
  if data.is_a?(RSpec::Matchers::Composable)
152
159
  data.description
153
160
  else
154
- data
161
+ data.inspect
155
162
  end
156
163
  end
157
164
 
@@ -3,6 +3,8 @@ require "rspec/rails/matchers/action_cable/have_broadcasted_to"
3
3
  module RSpec
4
4
  module Rails
5
5
  module Matchers
6
+ extend RSpec::Matchers::DSL
7
+
6
8
  # Namespace for various implementations of ActionCable features
7
9
  #
8
10
  # @api private
@@ -50,7 +52,10 @@ module RSpec
50
52
 
51
53
  ActionCable::HaveBroadcastedTo.new(target, channel: described_class)
52
54
  end
53
- alias_method :broadcast_to, :have_broadcasted_to
55
+
56
+ alias_matcher :broadcast_to, :have_broadcasted_to do |desc|
57
+ desc.gsub("have broadcasted", "broadcast")
58
+ end
54
59
 
55
60
  private
56
61
 
@@ -14,6 +14,7 @@ module RSpec
14
14
  def initialize
15
15
  @args = []
16
16
  @queue = nil
17
+ @priority = nil
17
18
  @at = nil
18
19
  @block = proc { }
19
20
  set_expected_number(:exactly, 1)
@@ -30,6 +31,11 @@ module RSpec
30
31
  self
31
32
  end
32
33
 
34
+ def at_priority(priority)
35
+ @priority = priority.to_i
36
+ self
37
+ end
38
+
33
39
  def at(time_or_date)
34
40
  case time_or_date
35
41
  when Time then @at = Time.at(time_or_date.to_f)
@@ -71,6 +77,8 @@ module RSpec
71
77
  end
72
78
 
73
79
  def failure_message
80
+ return @failure_message if defined?(@failure_message)
81
+
74
82
  "expected to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}".tap do |msg|
75
83
  if @unmatching_jobs.any?
76
84
  msg << "\nQueued jobs:"
@@ -101,7 +109,7 @@ module RSpec
101
109
 
102
110
  def check(jobs)
103
111
  @matching_jobs, @unmatching_jobs = jobs.partition do |job|
104
- if job_match?(job) && arguments_match?(job) && queue_match?(job) && at_match?(job)
112
+ if matches_constraints?(job)
105
113
  args = deserialize_arguments(job)
106
114
  @block.call(*args)
107
115
  true
@@ -109,6 +117,12 @@ module RSpec
109
117
  false
110
118
  end
111
119
  end
120
+
121
+ if (signature_mismatch = detect_args_signature_mismatch(@matching_jobs))
122
+ @failure_message = signature_mismatch
123
+ return false
124
+ end
125
+
112
126
  @matching_jobs_count = @matching_jobs.size
113
127
 
114
128
  case @expectation_type
@@ -123,6 +137,7 @@ module RSpec
123
137
  msg << " with #{@args}," if @args.any?
124
138
  msg << " on queue #{@queue}," if @queue
125
139
  msg << " at #{@at.inspect}," if @at
140
+ msg << " with priority #{@priority}," if @priority
126
141
  msg << " but #{self.class::MESSAGE_EXPECTATION_ACTION} #{@matching_jobs_count}"
127
142
  end
128
143
  end
@@ -132,13 +147,23 @@ module RSpec
132
147
  msg_parts << "with #{deserialize_arguments(job)}" if job[:args].any?
133
148
  msg_parts << "on queue #{job[:queue]}" if job[:queue]
134
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
135
156
 
136
157
  "#{job[:job].name} job".tap do |msg|
137
158
  msg << " #{msg_parts.join(', ')}" if msg_parts.any?
138
159
  end
139
160
  end
140
161
 
141
- def job_match?(job)
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)
142
167
  @job ? @job == job[:job] : true
143
168
  end
144
169
 
@@ -152,12 +177,48 @@ module RSpec
152
177
  end
153
178
  end
154
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
+
155
210
  def queue_match?(job)
156
211
  return true unless @queue
157
212
 
158
213
  @queue == job[:queue]
159
214
  end
160
215
 
216
+ def priority_match?(job)
217
+ return true unless @priority
218
+
219
+ @priority == job[:priority]
220
+ end
221
+
161
222
  def at_match?(job)
162
223
  return true unless @at
163
224
  return job[:at].nil? if @at == :no_wait
@@ -259,6 +320,10 @@ module RSpec
259
320
 
260
321
  !matches?(proc)
261
322
  end
323
+
324
+ def supports_block_expectations?
325
+ false
326
+ end
262
327
  end
263
328
 
264
329
  # @private
@@ -291,6 +356,10 @@ module RSpec
291
356
  @job = job
292
357
  check(queue_adapter.performed_jobs)
293
358
  end
359
+
360
+ def supports_block_expectations?
361
+ false
362
+ end
294
363
  end
295
364
  end
296
365
 
@@ -384,33 +453,33 @@ module RSpec
384
453
  #
385
454
  # @example
386
455
  # expect {
387
- # perform_jobs { HeavyLiftingJob.perform_later }
456
+ # perform_enqueued_jobs { HeavyLiftingJob.perform_later }
388
457
  # }.to have_performed_job
389
458
  #
390
459
  # expect {
391
- # perform_jobs {
460
+ # perform_enqueued_jobs {
392
461
  # HelloJob.perform_later
393
462
  # HeavyLiftingJob.perform_later
394
463
  # }
395
464
  # }.to have_performed_job(HelloJob).exactly(:once)
396
465
  #
397
466
  # expect {
398
- # perform_jobs { 3.times { HelloJob.perform_later } }
467
+ # perform_enqueued_jobs { 3.times { HelloJob.perform_later } }
399
468
  # }.to have_performed_job(HelloJob).at_least(2).times
400
469
  #
401
470
  # expect {
402
- # perform_jobs { HelloJob.perform_later }
471
+ # perform_enqueued_jobs { HelloJob.perform_later }
403
472
  # }.to have_performed_job(HelloJob).at_most(:twice)
404
473
  #
405
474
  # expect {
406
- # perform_jobs {
475
+ # perform_enqueued_jobs {
407
476
  # HelloJob.perform_later
408
477
  # HeavyLiftingJob.perform_later
409
478
  # }
410
479
  # }.to have_performed_job(HelloJob).and have_performed_job(HeavyLiftingJob)
411
480
  #
412
481
  # expect {
413
- # perform_jobs {
482
+ # perform_enqueued_jobs {
414
483
  # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
415
484
  # }
416
485
  # }.to have_performed_job.with(42).on_queue("low").at(Date.tomorrow.noon)
@@ -41,6 +41,8 @@ module RSpec
41
41
  end
42
42
 
43
43
  def failure_message
44
+ return @failure_message if defined?(@failure_message)
45
+
44
46
  "expected to enqueue #{base_message}".tap do |msg|
45
47
  msg << "\n#{unmatching_mail_jobs_message}" if unmatching_mail_jobs.any?
46
48
  end
@@ -70,7 +72,7 @@ module RSpec
70
72
  @mailer_class ? @mailer_class.name : 'ActionMailer::Base'
71
73
  end
72
74
 
73
- def job_match?(job)
75
+ def job_matches?(job)
74
76
  legacy_mail?(job) || parameterized_mail?(job) || unified_mail?(job)
75
77
  end
76
78
 
@@ -89,6 +91,23 @@ module RSpec
89
91
  super(job)
90
92
  end
91
93
 
94
+ def detect_args_signature_mismatch(jobs)
95
+ return if @method_name.nil?
96
+ return if skip_signature_verification?
97
+
98
+ mailer_class = mailer_class_name.constantize
99
+
100
+ jobs.each do |job|
101
+ mailer_args = extract_args_without_parameterized_params(job)
102
+
103
+ if (signature_mismatch = check_args_signature_mismatch(mailer_class, @method_name, mailer_args))
104
+ return signature_mismatch
105
+ end
106
+ end
107
+
108
+ nil
109
+ end
110
+
92
111
  def base_mailer_args
93
112
  [mailer_class_name, @method_name.to_s, MAILER_JOB_METHOD]
94
113
  end
@@ -105,18 +124,18 @@ module RSpec
105
124
 
106
125
  def unmatching_mail_jobs
107
126
  @unmatching_jobs.select do |job|
108
- job_match?(job)
127
+ job_matches?(job)
109
128
  end
110
129
  end
111
130
 
112
131
  def unmatching_mail_jobs_message
113
- msg = "Queued deliveries:"
132
+ messages = ["Queued deliveries:"]
114
133
 
115
134
  unmatching_mail_jobs.each do |job|
116
- msg << "\n #{mail_job_message(job)}"
135
+ messages << " #{mail_job_message(job)}"
117
136
  end
118
137
 
119
- msg
138
+ messages.join("\n")
120
139
  end
121
140
 
122
141
  def mail_job_message(job)
@@ -157,6 +176,19 @@ module RSpec
157
176
  end
158
177
  end
159
178
 
179
+ def extract_args_without_parameterized_params(job)
180
+ args = deserialize_arguments(job)
181
+ mailer_args = args - base_mailer_args
182
+
183
+ if parameterized_mail?(job)
184
+ mailer_args = mailer_args[1..-1] # ignore parameterized params
185
+ elsif mailer_args.last.is_a?(Hash) && mailer_args.last.key?(:args)
186
+ mailer_args = args.last[:args]
187
+ end
188
+
189
+ mailer_args
190
+ end
191
+
160
192
  def legacy_mail?(job)
161
193
  RSpec::Rails::FeatureCheck.has_action_mailer_legacy_delivery_job? && job[:job] <= ActionMailer::DeliveryJob
162
194
  end
@@ -33,7 +33,7 @@ module RSpec
33
33
  # @param obj [Object] object to convert to a response
34
34
  # @return [ActionDispatch::TestResponse]
35
35
  def as_test_response(obj)
36
- if ::ActionDispatch::Response === obj
36
+ if ::ActionDispatch::Response === obj || ::Rack::MockResponse === obj
37
37
  ::ActionDispatch::TestResponse.from_response(obj)
38
38
  elsif ::ActionDispatch::TestResponse === obj
39
39
  obj
@@ -233,7 +233,7 @@ module RSpec
233
233
  # expect(response).to have_http_status(:redirect)
234
234
  #
235
235
  # @see RSpec::Rails::Matchers#have_http_status
236
- # @see https://github.com/rails/rails/blob/6-0-stable/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
236
+ # @see https://github.com/rails/rails/blob/7-2-stable/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
237
237
  class GenericStatus < RSpec::Rails::Matchers::BaseMatcher
238
238
  include HaveHttpStatus
239
239
 
@@ -5,7 +5,9 @@ end
5
5
 
6
6
  task default: :spec
7
7
 
8
- task stats: "spec:statsetup"
8
+ if ::Rails::VERSION::STRING < "8.0.0"
9
+ task stats: "spec:statsetup"
10
+ end
9
11
 
10
12
  desc "Run all specs in spec directory (excluding plugin specs)"
11
13
  RSpec::Core::RakeTask.new(spec: "spec:prepare")
@@ -3,7 +3,7 @@ module RSpec
3
3
  # Version information for RSpec Rails.
4
4
  module Version
5
5
  # Current version of RSpec Rails, in semantic versioning format.
6
- STRING = '6.1.3'
6
+ STRING = '8.0.2'
7
7
  end
8
8
  end
9
9
  end
data/lib/rspec-rails.rb CHANGED
@@ -9,6 +9,23 @@ module RSpec
9
9
  # As of Rails 5.1.0 you can register directories to work with `rake notes`
10
10
  require 'rails/source_annotation_extractor'
11
11
  ::Rails::SourceAnnotationExtractor::Annotation.register_directories("spec")
12
+
13
+ # As of Rails 8.0.0 you can register directories to work with `rails stats`
14
+ if ::Rails::VERSION::STRING >= "8.0.0"
15
+ require 'rails/code_statistics'
16
+
17
+ dirs = Dir['./spec/**/*_spec.rb']
18
+ .map { |f| f.sub(/^\.\/(spec\/\w+)\/.*/, '\\1') }
19
+ .uniq
20
+ .select { |f| File.directory?(f) }
21
+
22
+ Hash[dirs.map { |d| [d.split('/').last, d] }].each do |type, dir|
23
+ name = type.singularize.capitalize
24
+
25
+ ::Rails::CodeStatistics.register_directory "#{name} specs", dir, test_directory: true
26
+ end
27
+ end
28
+
12
29
  generators = config.app_generators
13
30
  generators.integration_tool :rspec
14
31
  generators.test_framework :rspec
@@ -47,18 +64,10 @@ module RSpec
47
64
  end
48
65
  end
49
66
 
50
- if ::Rails::VERSION::STRING >= "7.1.0"
51
- def config_default_preview_path(options)
52
- return unless options.preview_paths.empty?
67
+ def config_default_preview_path(options)
68
+ return unless options.preview_paths.empty?
53
69
 
54
- options.preview_paths << "#{::Rails.root}/spec/mailers/previews"
55
- end
56
- else
57
- def config_default_preview_path(options)
58
- return unless options.preview_path.blank?
59
-
60
- options.preview_path = "#{::Rails.root}/spec/mailers/previews"
61
- end
70
+ options.preview_paths << "#{::Rails.root}/spec/mailers/previews"
62
71
  end
63
72
 
64
73
  def supports_action_mailer_previews?(config)
data.tar.gz.sig CHANGED
Binary file