rspec-rails 3.8.2 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Capybara.md +5 -54
  4. data/Changelog.md +440 -76
  5. data/README.md +281 -500
  6. data/lib/generators/rspec/channel/channel_generator.rb +12 -0
  7. data/lib/generators/rspec/{observer/templates/observer_spec.rb → channel/templates/channel_spec.rb.erb} +1 -1
  8. data/lib/generators/rspec/controller/controller_generator.rb +24 -7
  9. data/lib/generators/rspec/controller/templates/request_spec.rb +19 -0
  10. data/lib/generators/rspec/controller/templates/routing_spec.rb +13 -0
  11. data/lib/generators/rspec/feature/feature_generator.rb +3 -3
  12. data/lib/generators/rspec/generator/generator_generator.rb +24 -0
  13. data/lib/generators/rspec/generator/templates/generator_spec.rb +5 -0
  14. data/lib/generators/rspec/helper/helper_generator.rb +2 -2
  15. data/lib/generators/rspec/install/install_generator.rb +23 -6
  16. data/lib/generators/rspec/install/templates/spec/rails_helper.rb +32 -17
  17. data/lib/generators/rspec/job/job_generator.rb +2 -1
  18. data/lib/generators/rspec/job/templates/job_spec.rb.erb +1 -1
  19. data/lib/generators/rspec/mailbox/mailbox_generator.rb +14 -0
  20. data/lib/generators/rspec/mailbox/templates/mailbox_spec.rb.erb +7 -0
  21. data/lib/generators/rspec/mailer/mailer_generator.rb +7 -4
  22. data/lib/generators/rspec/mailer/templates/mailer_spec.rb +2 -2
  23. data/lib/generators/rspec/mailer/templates/preview.rb +4 -4
  24. data/lib/generators/rspec/model/model_generator.rb +8 -7
  25. data/lib/generators/rspec/model/templates/fixtures.yml +1 -1
  26. data/lib/generators/rspec/request/request_generator.rb +10 -3
  27. data/lib/generators/rspec/scaffold/scaffold_generator.rb +36 -22
  28. data/lib/generators/rspec/scaffold/templates/api_controller_spec.rb +13 -49
  29. data/lib/generators/rspec/scaffold/templates/api_request_spec.rb +131 -0
  30. data/lib/generators/rspec/scaffold/templates/controller_spec.rb +14 -62
  31. data/lib/generators/rspec/scaffold/templates/edit_spec.rb +9 -9
  32. data/lib/generators/rspec/scaffold/templates/index_spec.rb +3 -2
  33. data/lib/generators/rspec/scaffold/templates/new_spec.rb +2 -6
  34. data/lib/generators/rspec/scaffold/templates/request_spec.rb +138 -0
  35. data/lib/generators/rspec/scaffold/templates/routing_spec.rb +8 -10
  36. data/lib/generators/rspec/scaffold/templates/show_spec.rb +2 -2
  37. data/lib/generators/rspec/system/system_generator.rb +24 -0
  38. data/lib/generators/rspec/system/templates/system_spec.rb +9 -0
  39. data/lib/generators/rspec/view/view_generator.rb +4 -4
  40. data/lib/generators/rspec.rb +16 -5
  41. data/lib/rspec/rails/adapters.rb +22 -76
  42. data/lib/rspec/rails/configuration.rb +112 -38
  43. data/lib/rspec/rails/example/channel_example_group.rb +93 -0
  44. data/lib/rspec/rails/example/controller_example_group.rb +5 -4
  45. data/lib/rspec/rails/example/feature_example_group.rb +6 -26
  46. data/lib/rspec/rails/example/helper_example_group.rb +2 -9
  47. data/lib/rspec/rails/example/mailbox_example_group.rb +80 -0
  48. data/lib/rspec/rails/example/mailer_example_group.rb +2 -2
  49. data/lib/rspec/rails/example/rails_example_group.rb +7 -1
  50. data/lib/rspec/rails/example/request_example_group.rb +1 -4
  51. data/lib/rspec/rails/example/routing_example_group.rb +0 -2
  52. data/lib/rspec/rails/example/system_example_group.rb +88 -16
  53. data/lib/rspec/rails/example/view_example_group.rb +40 -28
  54. data/lib/rspec/rails/example.rb +2 -0
  55. data/lib/rspec/rails/extensions/active_record/proxy.rb +5 -10
  56. data/lib/rspec/rails/feature_check.rb +16 -29
  57. data/lib/rspec/rails/file_fixture_support.rb +11 -10
  58. data/lib/rspec/rails/fixture_file_upload_support.rb +20 -15
  59. data/lib/rspec/rails/fixture_support.rb +64 -34
  60. data/lib/rspec/rails/matchers/action_cable/have_broadcasted_to.rb +173 -0
  61. data/lib/rspec/rails/matchers/action_cable/have_streams.rb +58 -0
  62. data/lib/rspec/rails/matchers/action_cable.rb +65 -0
  63. data/lib/rspec/rails/matchers/action_mailbox.rb +73 -0
  64. data/lib/rspec/rails/matchers/active_job.rb +224 -24
  65. data/lib/rspec/rails/matchers/base_matcher.rb +179 -0
  66. data/lib/rspec/rails/matchers/be_a_new.rb +1 -1
  67. data/lib/rspec/rails/matchers/be_new_record.rb +1 -1
  68. data/lib/rspec/rails/matchers/be_valid.rb +1 -1
  69. data/lib/rspec/rails/matchers/have_enqueued_mail.rb +258 -0
  70. data/lib/rspec/rails/matchers/have_http_status.rb +23 -32
  71. data/lib/rspec/rails/matchers/have_rendered.rb +2 -1
  72. data/lib/rspec/rails/matchers/redirect_to.rb +1 -1
  73. data/lib/rspec/rails/matchers/relation_match_array.rb +1 -1
  74. data/lib/rspec/rails/matchers/routing_matchers.rb +13 -13
  75. data/lib/rspec/rails/matchers/send_email.rb +122 -0
  76. data/lib/rspec/rails/matchers.rb +12 -0
  77. data/lib/rspec/rails/tasks/rspec.rake +9 -17
  78. data/lib/rspec/rails/vendor/capybara.rb +10 -17
  79. data/lib/rspec/rails/version.rb +1 -1
  80. data/lib/rspec/rails/view_assigns.rb +0 -18
  81. data/lib/rspec/rails/view_path_builder.rb +1 -1
  82. data/lib/rspec/rails/view_rendering.rb +20 -7
  83. data/lib/rspec-rails.rb +36 -18
  84. data.tar.gz.sig +0 -0
  85. metadata +55 -37
  86. metadata.gz.sig +0 -0
  87. data/lib/generators/rspec/integration/integration_generator.rb +0 -22
  88. data/lib/generators/rspec/observer/observer_generator.rb +0 -13
  89. /data/lib/generators/rspec/{integration → request}/templates/request_spec.rb +0 -0
@@ -8,14 +8,15 @@ module RSpec
8
8
  #
9
9
  # @api private
10
10
  module ActiveJob
11
- # rubocop: disable Style/ClassLength
11
+ # rubocop: disable Metrics/ClassLength
12
12
  # @private
13
- class Base < RSpec::Matchers::BuiltIn::BaseMatcher
13
+ class Base < RSpec::Rails::Matchers::BaseMatcher
14
14
  def initialize
15
15
  @args = []
16
16
  @queue = nil
17
+ @priority = nil
17
18
  @at = nil
18
- @block = Proc.new {}
19
+ @block = proc { }
19
20
  set_expected_number(:exactly, 1)
20
21
  end
21
22
 
@@ -26,12 +27,21 @@ module RSpec
26
27
  end
27
28
 
28
29
  def on_queue(queue)
29
- @queue = queue
30
+ @queue = queue.to_s
30
31
  self
31
32
  end
32
33
 
33
- def at(date)
34
- @at = date
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
35
45
  self
36
46
  end
37
47
 
@@ -67,7 +77,9 @@ module RSpec
67
77
  end
68
78
 
69
79
  def failure_message
70
- "expected to enqueue #{base_message}".tap do |msg|
80
+ return @failure_message if defined?(@failure_message)
81
+
82
+ "expected to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}".tap do |msg|
71
83
  if @unmatching_jobs.any?
72
84
  msg << "\nQueued jobs:"
73
85
  @unmatching_jobs.each do |job|
@@ -78,7 +90,7 @@ module RSpec
78
90
  end
79
91
 
80
92
  def failure_message_when_negated
81
- "expected not to enqueue #{base_message}"
93
+ "expected not to #{self.class::FAILURE_MESSAGE_EXPECTATION_ACTION} #{base_message}"
82
94
  end
83
95
 
84
96
  def message_expectation_modifier
@@ -97,7 +109,7 @@ module RSpec
97
109
 
98
110
  def check(jobs)
99
111
  @matching_jobs, @unmatching_jobs = jobs.partition do |job|
100
- if arguments_match?(job) && other_attributes_match?(job)
112
+ if matches_constraints?(job)
101
113
  args = deserialize_arguments(job)
102
114
  @block.call(*args)
103
115
  true
@@ -105,6 +117,12 @@ module RSpec
105
117
  false
106
118
  end
107
119
  end
120
+
121
+ if (signature_mismatch = detect_args_signature_mismatch(@matching_jobs))
122
+ @failure_message = signature_mismatch
123
+ return false
124
+ end
125
+
108
126
  @matching_jobs_count = @matching_jobs.size
109
127
 
110
128
  case @expectation_type
@@ -119,7 +137,8 @@ module RSpec
119
137
  msg << " with #{@args}," if @args.any?
120
138
  msg << " on queue #{@queue}," if @queue
121
139
  msg << " at #{@at.inspect}," if @at
122
- msg << " but enqueued #{@matching_jobs_count}"
140
+ msg << " with priority #{@priority}," if @priority
141
+ msg << " but #{self.class::MESSAGE_EXPECTATION_ACTION} #{@matching_jobs_count}"
123
142
  end
124
143
  end
125
144
 
@@ -128,35 +147,97 @@ module RSpec
128
147
  msg_parts << "with #{deserialize_arguments(job)}" if job[:args].any?
129
148
  msg_parts << "on queue #{job[:queue]}" if job[:queue]
130
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
131
156
 
132
157
  "#{job[:job].name} job".tap do |msg|
133
158
  msg << " #{msg_parts.join(', ')}" if msg_parts.any?
134
159
  end
135
160
  end
136
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
+
137
170
  def arguments_match?(job)
138
171
  if @args.any?
172
+ args = serialize_and_deserialize_arguments(@args)
139
173
  deserialized_args = deserialize_arguments(job)
140
- RSpec::Mocks::ArgumentListMatcher.new(*@args).args_match?(*deserialized_args)
174
+ RSpec::Mocks::ArgumentListMatcher.new(*args).args_match?(*deserialized_args)
141
175
  else
142
176
  true
143
177
  end
144
178
  end
145
179
 
146
- def other_attributes_match?(job)
147
- serialized_attributes.all? { |key, value| value == job[key] }
180
+ def detect_args_signature_mismatch(jobs)
181
+ jobs.each do |job|
182
+ args = deserialize_arguments(job)
183
+
184
+ if (signature_mismatch = check_args_signature_mismatch(job.fetch(:job), :perform, args))
185
+ return signature_mismatch
186
+ end
187
+ end
188
+
189
+ nil
148
190
  end
149
191
 
150
- def serialized_attributes
151
- {}.tap do |attributes|
152
- attributes[:at] = serialized_at if @at
153
- attributes[:queue] = @queue if @queue
154
- attributes[:job] = @job if @job
192
+ def check_args_signature_mismatch(job_class, job_method, args)
193
+ signature = Support::MethodSignature.new(job_class.public_instance_method(job_method))
194
+ verifier = Support::StrictSignatureVerifier.new(signature, args)
195
+
196
+ unless verifier.valid?
197
+ "Incorrect arguments passed to #{job_class.name}: #{verifier.error_message}"
155
198
  end
156
199
  end
157
200
 
158
- def serialized_at
159
- @at == :no_wait ? nil : @at.to_f
201
+ def queue_match?(job)
202
+ return true unless @queue
203
+
204
+ @queue == job[:queue]
205
+ end
206
+
207
+ def priority_match?(job)
208
+ return true unless @priority
209
+
210
+ @priority == job[:priority]
211
+ end
212
+
213
+ def at_match?(job)
214
+ return true unless @at
215
+ return job[:at].nil? if @at == :no_wait
216
+ return false unless job[:at]
217
+
218
+ scheduled_at = Time.at(job[:at])
219
+ values_match?(@at, scheduled_at) || check_for_inprecise_value(scheduled_at)
220
+ end
221
+
222
+ def check_for_inprecise_value(scheduled_at)
223
+ return unless Time === @at && values_match?(@at.change(usec: 0), scheduled_at)
224
+
225
+ RSpec.warn_with((<<-WARNING).gsub(/^\s+\|/, '').chomp)
226
+ |[WARNING] Your expected `at(...)` value does not match the job scheduled_at value
227
+ |unless microseconds are removed. This precision error often occurs when checking
228
+ |values against `Time.current` / `Time.now` which have usec precision, but Rails
229
+ |uses `n.seconds.from_now` internally which has a usec count of `0`.
230
+ |
231
+ |Use `change(usec: 0)` to correct these values. For example:
232
+ |
233
+ |`Time.current.change(usec: 0)`
234
+ |
235
+ |Note: RSpec cannot do this for you because jobs can be scheduled with usec
236
+ |precision and we do not know whether it is on purpose or not.
237
+ |
238
+ |
239
+ WARNING
240
+ false
160
241
  end
161
242
 
162
243
  def set_expected_number(relativity, count)
@@ -169,6 +250,13 @@ module RSpec
169
250
  end
170
251
  end
171
252
 
253
+ def serialize_and_deserialize_arguments(args)
254
+ serialized = ::ActiveJob::Arguments.serialize(args)
255
+ ::ActiveJob::Arguments.deserialize(serialized)
256
+ rescue ::ActiveJob::SerializationError
257
+ args
258
+ end
259
+
172
260
  def deserialize_arguments(job)
173
261
  ::ActiveJob::Arguments.deserialize(job[:args])
174
262
  rescue ::ActiveJob::DeserializationError
@@ -179,10 +267,13 @@ module RSpec
179
267
  ::ActiveJob::Base.queue_adapter
180
268
  end
181
269
  end
182
- # rubocop: enable Style/ClassLength
270
+ # rubocop: enable Metrics/ClassLength
183
271
 
184
272
  # @private
185
273
  class HaveEnqueuedJob < Base
274
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
275
+ MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze
276
+
186
277
  def initialize(job)
187
278
  super()
188
279
  @job = job
@@ -191,11 +282,11 @@ module RSpec
191
282
  def matches?(proc)
192
283
  raise ArgumentError, "have_enqueued_job and enqueue_job only support block expectations" unless Proc === proc
193
284
 
194
- original_enqueued_jobs_count = queue_adapter.enqueued_jobs.count
285
+ original_enqueued_jobs = Set.new(queue_adapter.enqueued_jobs)
195
286
  proc.call
196
- in_block_jobs = queue_adapter.enqueued_jobs.drop(original_enqueued_jobs_count)
287
+ enqueued_jobs = Set.new(queue_adapter.enqueued_jobs)
197
288
 
198
- check(in_block_jobs)
289
+ check(enqueued_jobs - original_enqueued_jobs)
199
290
  end
200
291
 
201
292
  def does_not_match?(proc)
@@ -207,6 +298,9 @@ module RSpec
207
298
 
208
299
  # @private
209
300
  class HaveBeenEnqueued < Base
301
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'enqueue'.freeze
302
+ MESSAGE_EXPECTATION_ACTION = 'enqueued'.freeze
303
+
210
304
  def matches?(job)
211
305
  @job = job
212
306
  check(queue_adapter.enqueued_jobs)
@@ -218,6 +312,38 @@ module RSpec
218
312
  !matches?(proc)
219
313
  end
220
314
  end
315
+
316
+ # @private
317
+ class HavePerformedJob < Base
318
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
319
+ MESSAGE_EXPECTATION_ACTION = 'performed'.freeze
320
+
321
+ def initialize(job)
322
+ super()
323
+ @job = job
324
+ end
325
+
326
+ def matches?(proc)
327
+ raise ArgumentError, "have_performed_job only supports block expectations" unless Proc === proc
328
+
329
+ original_performed_jobs_count = queue_adapter.performed_jobs.count
330
+ proc.call
331
+ in_block_jobs = queue_adapter.performed_jobs.drop(original_performed_jobs_count)
332
+
333
+ check(in_block_jobs)
334
+ end
335
+ end
336
+
337
+ # @private
338
+ class HaveBeenPerformed < Base
339
+ FAILURE_MESSAGE_EXPECTATION_ACTION = 'perform'.freeze
340
+ MESSAGE_EXPECTATION_ACTION = 'performed'.freeze
341
+
342
+ def matches?(job)
343
+ @job = job
344
+ check(queue_adapter.performed_jobs)
345
+ end
346
+ end
221
347
  end
222
348
 
223
349
  # @api public
@@ -305,11 +431,85 @@ module RSpec
305
431
  ActiveJob::HaveBeenEnqueued.new
306
432
  end
307
433
 
434
+ # @api public
435
+ # Passes if a job has been performed inside block. May chain at_least, at_most or exactly to specify a number of times.
436
+ #
437
+ # @example
438
+ # expect {
439
+ # perform_enqueued_jobs { HeavyLiftingJob.perform_later }
440
+ # }.to have_performed_job
441
+ #
442
+ # expect {
443
+ # perform_enqueued_jobs {
444
+ # HelloJob.perform_later
445
+ # HeavyLiftingJob.perform_later
446
+ # }
447
+ # }.to have_performed_job(HelloJob).exactly(:once)
448
+ #
449
+ # expect {
450
+ # perform_enqueued_jobs { 3.times { HelloJob.perform_later } }
451
+ # }.to have_performed_job(HelloJob).at_least(2).times
452
+ #
453
+ # expect {
454
+ # perform_enqueued_jobs { HelloJob.perform_later }
455
+ # }.to have_performed_job(HelloJob).at_most(:twice)
456
+ #
457
+ # expect {
458
+ # perform_enqueued_jobs {
459
+ # HelloJob.perform_later
460
+ # HeavyLiftingJob.perform_later
461
+ # }
462
+ # }.to have_performed_job(HelloJob).and have_performed_job(HeavyLiftingJob)
463
+ #
464
+ # expect {
465
+ # perform_enqueued_jobs {
466
+ # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
467
+ # }
468
+ # }.to have_performed_job.with(42).on_queue("low").at(Date.tomorrow.noon)
469
+ def have_performed_job(job = nil)
470
+ check_active_job_adapter
471
+ ActiveJob::HavePerformedJob.new(job)
472
+ end
473
+ alias_method :perform_job, :have_performed_job
474
+
475
+ # @api public
476
+ # Passes if a job has been performed. May chain at_least, at_most or exactly to specify a number of times.
477
+ #
478
+ # @example
479
+ # before do
480
+ # ActiveJob::Base.queue_adapter.performed_jobs.clear
481
+ # ActiveJob::Base.queue_adapter.perform_enqueued_jobs = true
482
+ # ActiveJob::Base.queue_adapter.perform_enqueued_at_jobs = true
483
+ # end
484
+ #
485
+ # HeavyLiftingJob.perform_later
486
+ # expect(HeavyLiftingJob).to have_been_performed
487
+ #
488
+ # HelloJob.perform_later
489
+ # HeavyLiftingJob.perform_later
490
+ # expect(HeavyLiftingJob).to have_been_performed.exactly(:once)
491
+ #
492
+ # 3.times { HelloJob.perform_later }
493
+ # expect(HelloJob).to have_been_performed.at_least(2).times
494
+ #
495
+ # HelloJob.perform_later
496
+ # HeavyLiftingJob.perform_later
497
+ # expect(HelloJob).to have_been_performed
498
+ # expect(HeavyLiftingJob).to have_been_performed
499
+ #
500
+ # HelloJob.set(wait_until: Date.tomorrow.noon, queue: "low").perform_later(42)
501
+ # expect(HelloJob).to have_been_performed.with(42).on_queue("low").at(Date.tomorrow.noon)
502
+ def have_been_performed
503
+ check_active_job_adapter
504
+ ActiveJob::HaveBeenPerformed.new
505
+ end
506
+
308
507
  private
309
508
 
310
509
  # @private
311
510
  def check_active_job_adapter
312
511
  return if ::ActiveJob::QueueAdapters::TestAdapter === ::ActiveJob::Base.queue_adapter
512
+
313
513
  raise StandardError, "To use ActiveJob matchers set `ActiveJob::Base.queue_adapter = :test`"
314
514
  end
315
515
  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
@@ -6,7 +6,7 @@ module RSpec
6
6
  # Matcher class for `be_a_new`. Should not be instantiated directly.
7
7
  #
8
8
  # @see RSpec::Rails::Matchers#be_a_new
9
- class BeANew < RSpec::Matchers::BuiltIn::BaseMatcher
9
+ class BeANew < RSpec::Rails::Matchers::BaseMatcher
10
10
  # @private
11
11
  def initialize(expected)
12
12
  @expected = expected
@@ -2,7 +2,7 @@ module RSpec
2
2
  module Rails
3
3
  module Matchers
4
4
  # @private
5
- class BeANewRecord < RSpec::Matchers::BuiltIn::BaseMatcher
5
+ class BeANewRecord < RSpec::Rails::Matchers::BaseMatcher
6
6
  def matches?(actual)
7
7
  actual.new_record?
8
8
  end
@@ -15,7 +15,7 @@ module RSpec
15
15
  def failure_message
16
16
  message = "expected #{actual.inspect} to be valid"
17
17
 
18
- if actual.respond_to?(:errors)
18
+ if actual.respond_to?(:errors) && actual.method(:errors).arity < 1
19
19
  errors = if actual.errors.respond_to?(:full_messages)
20
20
  actual.errors.full_messages
21
21
  else