rspec-mocks 3.0.4 → 3.12.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +512 -2
  6. data/{License.txt → LICENSE.md} +5 -4
  7. data/README.md +113 -30
  8. data/lib/rspec/mocks/any_instance/chain.rb +5 -3
  9. data/lib/rspec/mocks/any_instance/error_generator.rb +31 -0
  10. data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +1 -5
  11. data/lib/rspec/mocks/any_instance/expectation_chain.rb +9 -8
  12. data/lib/rspec/mocks/any_instance/message_chains.rb +7 -8
  13. data/lib/rspec/mocks/any_instance/proxy.rb +14 -5
  14. data/lib/rspec/mocks/any_instance/recorder.rb +61 -31
  15. data/lib/rspec/mocks/any_instance/stub_chain.rb +15 -11
  16. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +1 -5
  17. data/lib/rspec/mocks/any_instance.rb +1 -0
  18. data/lib/rspec/mocks/argument_list_matcher.rb +55 -10
  19. data/lib/rspec/mocks/argument_matchers.rb +88 -30
  20. data/lib/rspec/mocks/configuration.rb +61 -13
  21. data/lib/rspec/mocks/error_generator.rb +250 -107
  22. data/lib/rspec/mocks/example_methods.rb +151 -28
  23. data/lib/rspec/mocks/instance_method_stasher.rb +17 -6
  24. data/lib/rspec/mocks/matchers/have_received.rb +50 -20
  25. data/lib/rspec/mocks/matchers/receive.rb +39 -11
  26. data/lib/rspec/mocks/matchers/receive_message_chain.rb +22 -7
  27. data/lib/rspec/mocks/matchers/receive_messages.rb +12 -7
  28. data/lib/rspec/mocks/message_chain.rb +3 -7
  29. data/lib/rspec/mocks/message_expectation.rb +466 -307
  30. data/lib/rspec/mocks/method_double.rb +88 -29
  31. data/lib/rspec/mocks/method_reference.rb +85 -25
  32. data/lib/rspec/mocks/minitest_integration.rb +68 -0
  33. data/lib/rspec/mocks/mutate_const.rb +50 -109
  34. data/lib/rspec/mocks/object_reference.rb +89 -32
  35. data/lib/rspec/mocks/order_group.rb +4 -5
  36. data/lib/rspec/mocks/proxy.rb +156 -60
  37. data/lib/rspec/mocks/space.rb +52 -35
  38. data/lib/rspec/mocks/standalone.rb +1 -1
  39. data/lib/rspec/mocks/syntax.rb +26 -30
  40. data/lib/rspec/mocks/targets.rb +55 -28
  41. data/lib/rspec/mocks/test_double.rb +43 -7
  42. data/lib/rspec/mocks/verifying_double.rb +27 -33
  43. data/lib/rspec/mocks/{verifying_message_expecation.rb → verifying_message_expectation.rb} +11 -16
  44. data/lib/rspec/mocks/verifying_proxy.rb +77 -26
  45. data/lib/rspec/mocks/version.rb +1 -1
  46. data/lib/rspec/mocks.rb +8 -1
  47. data.tar.gz.sig +0 -0
  48. metadata +80 -43
  49. metadata.gz.sig +0 -0
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- # RSpec Mocks [![Build Status](https://secure.travis-ci.org/rspec/rspec-mocks.png?branch=master)](http://travis-ci.org/rspec/rspec-mocks) [![Code Climate](https://codeclimate.com/github/rspec/rspec-mocks.png)](https://codeclimate.com/github/rspec/rspec-mocks) [![Inline docs](http://inch-pages.github.io/github/rspec/rspec-mocks.png)](http://inch-pages.github.io/github/rspec/rspec-mocks)
2
-
1
+ # RSpec Mocks [![Build Status](https://github.com/rspec/rspec-mocks/workflows/RSpec%20CI/badge.svg)](https://github.com/rspec/rspec-mocks/actions) [![Code Climate](https://codeclimate.com/github/rspec/rspec-mocks.svg)](https://codeclimate.com/github/rspec/rspec-mocks)
3
2
  rspec-mocks is a test-double framework for rspec with support for method stubs,
4
3
  fakes, and message expectations on generated test-doubles and real objects
5
4
  alike.
@@ -9,6 +8,29 @@ alike.
9
8
  gem install rspec # for rspec-core, rspec-expectations, rspec-mocks
10
9
  gem install rspec-mocks # for rspec-mocks only
11
10
 
11
+ Want to run against the `main` branch? You'll need to include the dependent
12
+ RSpec repos as well. Add the following to your `Gemfile`:
13
+
14
+ ```ruby
15
+ %w[rspec-core rspec-expectations rspec-mocks rspec-support].each do |lib|
16
+ gem lib, :git => "https://github.com/rspec/#{lib}.git", :branch => 'main'
17
+ end
18
+ ```
19
+ ## Contributing
20
+
21
+ Once you've set up the environment, you'll need to cd into the working
22
+ directory of whichever repo you want to work in. From there you can run the
23
+ specs and cucumber features, and make patches.
24
+
25
+ NOTE: You do not need to use rspec-dev to work on a specific RSpec repo. You
26
+ can treat each RSpec repo as an independent project.
27
+
28
+ For information about contributing to RSpec, please refer to the following markdown files:
29
+ * [Build details](BUILD_DETAIL.md)
30
+ * [Code of Conduct](CODE_OF_CONDUCT.md)
31
+ * [Detailed contributing guide](CONTRIBUTING.md)
32
+ * [Development setup guide](DEVELOPMENT.md)
33
+
12
34
  ## Test Doubles
13
35
 
14
36
  A test double is an object that stands in for another object in your system
@@ -31,7 +53,17 @@ book = instance_double("Book", :pages => 250)
31
53
  Verifying doubles have some clever tricks to enable you to both test in
32
54
  isolation without your dependencies loaded while still being able to validate
33
55
  them against real objects. More detail is available in [their
34
- documentation](https://github.com/rspec/rspec-mocks/blob/master/features/verifying_doubles).
56
+ documentation](https://github.com/rspec/rspec-mocks/blob/main/features/verifying_doubles).
57
+
58
+ Verifying doubles can also accept custom identifiers, just like double(), e.g.:
59
+
60
+ ```ruby
61
+ books = []
62
+ books << instance_double("Book", :rspec_book, :pages => 250)
63
+ books << instance_double("Book", "(Untitled)", :pages => 5000)
64
+
65
+ puts books.inspect # with names, it's clearer which were actually added
66
+ ```
35
67
 
36
68
  ## Method Stubs
37
69
 
@@ -42,6 +74,9 @@ rspec-mocks supports 3 forms for declaring method stubs:
42
74
  ```ruby
43
75
  allow(book).to receive(:title) { "The RSpec Book" }
44
76
  allow(book).to receive(:title).and_return("The RSpec Book")
77
+ allow(book).to receive_messages(
78
+ :title => "The RSpec Book",
79
+ :subtitle => "Behaviour-Driven Development with RSpec, Cucumber, and Friends")
45
80
  ```
46
81
 
47
82
  You can also use this shortcut, which creates a test double and declares a
@@ -66,6 +101,21 @@ that iterates through them:
66
101
  order.calculate_total_price(double(:price => 1.99), double(:price => 2.99))
67
102
  ```
68
103
 
104
+ ### Stubbing a chain of methods
105
+
106
+ You can use `receive_message_chain` in place of `receive` to stub a chain of messages:
107
+
108
+ ```ruby
109
+ allow(double).to receive_message_chain("foo.bar") { :baz }
110
+ allow(double).to receive_message_chain(:foo, :bar => :baz)
111
+ allow(double).to receive_message_chain(:foo, :bar) { :baz }
112
+
113
+ # Given any of the above forms:
114
+ double.foo.bar # => :baz
115
+ ```
116
+
117
+ Chains can be arbitrarily long, which makes it quite painless to violate the Law of Demeter in violent ways, so you should consider any use of `receive_message_chain` a code smell. Even though not all code smells indicate real problems (think fluent interfaces), `receive_message_chain` still results in brittle examples. For example, if you write `allow(foo).to receive_message_chain(:bar, :baz => 37)` in a spec and then the implementation calls `foo.baz.bar`, the stub will not work.
118
+
69
119
  ## Consecutive return values
70
120
 
71
121
  When a stub might be invoked more than once, you can provide additional
@@ -102,24 +152,51 @@ zipcode.valid?
102
152
 
103
153
  ## Test Spies
104
154
 
105
- Verifies the given object received the expected message during the course of the
106
- test. The method must have previously been stubbed in order for messages to be
107
- verified.
155
+ Verifies the given object received the expected message during the course of
156
+ the test. For a message to be verified, the given object must be setup to spy
157
+ on it, either by having it explicitly stubbed or by being a null object double
158
+ (e.g. `double(...).as_null_object`). Convenience methods are provided to easily
159
+ create null object doubles for this purpose:
160
+
161
+ ```ruby
162
+ spy("invitation") # => same as `double("invitation").as_null_object`
163
+ instance_spy("Invitation") # => same as `instance_double("Invitation").as_null_object`
164
+ class_spy("Invitation") # => same as `class_double("Invitation").as_null_object`
165
+ object_spy("Invitation") # => same as `object_double("Invitation").as_null_object`
166
+ ```
108
167
 
109
- Stubbing and verifying messages received in this way implements the Test Spy
110
- pattern.
168
+ Verifying messages received in this way implements the Test Spy pattern.
111
169
 
112
170
  ```ruby
113
- invitation = double('invitation', :accept => true)
171
+ invitation = spy('invitation')
114
172
 
115
- user.accept_invitation(invitation)
173
+ user.accept_invitation(invitation)
174
+
175
+ expect(invitation).to have_received(:accept)
176
+
177
+ # You can also use other common message expectations. For example:
178
+ expect(invitation).to have_received(:accept).with(mailer)
179
+ expect(invitation).to have_received(:accept).twice
180
+ expect(invitation).to_not have_received(:accept).with(mailer)
181
+
182
+ # One can specify a return value on the spy the same way one would a double.
183
+ invitation = spy('invitation', :accept => true)
184
+ expect(invitation).to have_received(:accept).with(mailer)
185
+ expect(invitation.accept).to eq(true)
186
+ ```
187
+
188
+ Note that `have_received(...).with(...)` is unable to work properly when
189
+ passed arguments are mutated after the spy records the received message.
190
+ For example, this does not work properly:
191
+
192
+ ```ruby
193
+ greeter = spy("greeter")
116
194
 
117
- expect(invitation).to have_received(:accept)
195
+ message = "Hello"
196
+ greeter.greet_with(message)
197
+ message << ", World"
118
198
 
119
- # You can also use other common message expectations. For example:
120
- expect(invitation).to have_received(:accept).with(mailer)
121
- expect(invitation).to have_received(:accept).twice
122
- expect(invitation).to_not have_received(:accept).with(mailer)
199
+ expect(greeter).to have_received(:greet_with).with("Hello")
123
200
  ```
124
201
 
125
202
  ## Nomenclature
@@ -180,7 +257,7 @@ expect(double).to receive(:msg).with("B", 2, 4)
180
257
  ## Argument Matchers
181
258
 
182
259
  Arguments that are passed to `with` are compared with actual arguments
183
- received using ==. In cases in which you want to specify things about the
260
+ received using ===. In cases in which you want to specify things about the
184
261
  arguments rather than the arguments themselves, you can use any of the
185
262
  matchers that ship with rspec-expectations. They don't all make syntactic
186
263
  sense (they were primarily designed for use with RSpec::Expectations), but
@@ -190,14 +267,17 @@ rspec-mocks also adds some keyword Symbols that you can use to
190
267
  specify certain kinds of arguments:
191
268
 
192
269
  ```ruby
193
- expect(double).to receive(:msg).with(no_args())
194
- expect(double).to receive(:msg).with(any_args())
270
+ expect(double).to receive(:msg).with(no_args)
271
+ expect(double).to receive(:msg).with(any_args)
272
+ expect(double).to receive(:msg).with(1, any_args) # any args acts like an arg splat and can go anywhere
195
273
  expect(double).to receive(:msg).with(1, kind_of(Numeric), "b") #2nd argument can be any kind of Numeric
196
274
  expect(double).to receive(:msg).with(1, boolean(), "b") #2nd argument can be true or false
197
275
  expect(double).to receive(:msg).with(1, /abc/, "b") #2nd argument can be any String matching the submitted Regexp
198
276
  expect(double).to receive(:msg).with(1, anything(), "b") #2nd argument can be anything at all
199
- expect(double).to receive(:msg).with(1, duck_type(:abs, :div), "b")
200
- #2nd argument can be object that responds to #abs and #div
277
+ expect(double).to receive(:msg).with(1, duck_type(:abs, :div), "b") #2nd argument can be object that responds to #abs and #div
278
+ expect(double).to receive(:msg).with(hash_including(:a => 5)) # first arg is a hash with a: 5 as one of the key-values
279
+ expect(double).to receive(:msg).with(array_including(5)) # first arg is an array with 5 as one of the key-values
280
+ expect(double).to receive(:msg).with(hash_excluding(:a => 5)) # first arg is a hash without a: 5 as one of the key-values
201
281
  ```
202
282
 
203
283
  ## Receive Counts
@@ -205,14 +285,16 @@ expect(double).to receive(:msg).with(1, duck_type(:abs, :div), "b")
205
285
  ```ruby
206
286
  expect(double).to receive(:msg).once
207
287
  expect(double).to receive(:msg).twice
288
+ expect(double).to receive(:msg).exactly(n).time
208
289
  expect(double).to receive(:msg).exactly(n).times
209
290
  expect(double).to receive(:msg).at_least(:once)
210
291
  expect(double).to receive(:msg).at_least(:twice)
292
+ expect(double).to receive(:msg).at_least(n).time
211
293
  expect(double).to receive(:msg).at_least(n).times
212
294
  expect(double).to receive(:msg).at_most(:once)
213
295
  expect(double).to receive(:msg).at_most(:twice)
296
+ expect(double).to receive(:msg).at_most(n).time
214
297
  expect(double).to receive(:msg).at_most(n).times
215
- expect(double).to receive(:msg).any_number_of_times
216
298
  ```
217
299
 
218
300
  ## Ordering
@@ -248,7 +330,7 @@ expect(double).to receive(:msg).and_return(value)
248
330
  expect(double).to receive(:msg).exactly(3).times.and_return(value1, value2, value3)
249
331
  # returns value1 the first time, value2 the second, etc
250
332
  expect(double).to receive(:msg).and_raise(error)
251
- # error can be an instantiated object or a class
333
+ # `error` can be an instantiated object (e.g. `StandardError.new(some_arg)`) or a class (e.g. `StandardError`)
252
334
  # if it is a class, it must be instantiable with no args
253
335
  expect(double).to receive(:msg).and_throw(:msg)
254
336
  expect(double).to receive(:msg).and_yield(values, to, yield)
@@ -296,7 +378,7 @@ end
296
378
  ## Delegating to the Original Implementation
297
379
 
298
380
  When working with a partial mock object, you may occasionally
299
- want to set a message expecation without interfering with how
381
+ want to set a message expectation without interfering with how
300
382
  the object responds to the message. You can use `and_call_original`
301
383
  to achieve this:
302
384
 
@@ -321,7 +403,7 @@ your code.
321
403
  ## Stubbing and Hiding Constants
322
404
 
323
405
  See the [mutating constants
324
- README](https://github.com/rspec/rspec-mocks/blob/master/features/mutating_constants/README.md)
406
+ README](https://github.com/rspec/rspec-mocks/blob/main/features/mutating_constants/README.md)
325
407
  for info on this feature.
326
408
 
327
409
  ## Use `before(:example)`, not `before(:context)`
@@ -349,7 +431,7 @@ general we discourage its use for a number of reasons:
349
431
 
350
432
  * The `rspec-mocks` API is designed for individual object instances, but this
351
433
  feature operates on entire classes of objects. As a result there are some
352
- sematically confusing edge cases. For example in
434
+ semantically confusing edge cases. For example in
353
435
  `expect_any_instance_of(Widget).to receive(:name).twice` it isn't clear
354
436
  whether each specific instance is expected to receive `name` twice, or if two
355
437
  receives total are expected. (It's the former.)
@@ -367,14 +449,15 @@ There are many different viewpoints about the meaning of mocks and stubs. If
367
449
  you are interested in learning more, here is some recommended reading:
368
450
 
369
451
  * Mock Objects: http://www.mockobjects.com/
370
- * Endo-Testing: http://stalatest.googlecode.com/svn/trunk/Literatur/mockobjects.pdf
371
- * Mock Roles, Not Objects: http://jmock.org/oopsla2004.pdf
452
+ * Endo-Testing: http://www.ccs.neu.edu/research/demeter/related-work/extreme-programming/MockObjectsFinal.PDF
453
+ * Mock Roles, Not Objects: http://www.jmock.org/oopsla2004.pdf
372
454
  * Test Double: http://www.martinfowler.com/bliki/TestDouble.html
373
455
  * Test Double Patterns: http://xunitpatterns.com/Test%20Double%20Patterns.html
374
456
  * Mocks aren't stubs: http://www.martinfowler.com/articles/mocksArentStubs.html
375
457
 
376
458
  ## Also see
377
459
 
378
- * [http://github.com/rspec/rspec](http://github.com/rspec/rspec)
379
- * [http://github.com/rspec/rspec-core](http://github.com/rspec/rspec-core)
380
- * [http://github.com/rspec/rspec-expectations](http://github.com/rspec/rspec-expectations)
460
+ * [https://github.com/rspec/rspec](https://github.com/rspec/rspec)
461
+ * [https://github.com/rspec/rspec-core](https://github.com/rspec/rspec-core)
462
+ * [https://github.com/rspec/rspec-expectations](https://github.com/rspec/rspec-expectations)
463
+ * [https://github.com/rspec/rspec-rails](https://github.com/rspec/rspec-rails)
@@ -34,12 +34,14 @@ module RSpec
34
34
  record :and_throw
35
35
  record :and_yield
36
36
  record :and_call_original
37
+ record :and_wrap_original
37
38
  record :with
38
39
  record :once
39
40
  record :twice
40
- record :any_number_of_times
41
+ record :thrice
41
42
  record :exactly
42
43
  record :times
44
+ record :time
43
45
  record :never
44
46
  record :at_least
45
47
  record :at_most
@@ -75,7 +77,7 @@ module RSpec
75
77
  end
76
78
 
77
79
  def never
78
- ErrorGenerator.raise_double_negation_error("expect_any_instance_of(MyClass)") if negated?
80
+ AnyInstance.error_generator.raise_double_negation_error("expect_any_instance_of(MyClass)") if negated?
79
81
  super
80
82
  end
81
83
 
@@ -84,7 +86,7 @@ module RSpec
84
86
  super
85
87
  end
86
88
 
87
- private
89
+ private
88
90
 
89
91
  def negated?
90
92
  messages.any? { |(message, *_), _| message == :never }
@@ -0,0 +1,31 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ # @private
5
+ class ErrorGenerator < ::RSpec::Mocks::ErrorGenerator
6
+ def raise_second_instance_received_message_error(unfulfilled_expectations)
7
+ __raise "Exactly one instance should have received the following " \
8
+ "message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}"
9
+ end
10
+
11
+ def raise_does_not_implement_error(klass, method_name)
12
+ __raise "#{klass} does not implement ##{method_name}"
13
+ end
14
+
15
+ def raise_message_already_received_by_other_instance_error(method_name, object_inspect, invoked_instance)
16
+ __raise "The message '#{method_name}' was received by #{object_inspect} " \
17
+ "but has already been received by #{invoked_instance}"
18
+ end
19
+
20
+ def raise_not_supported_with_prepend_error(method_name, problem_mod)
21
+ __raise "Using `any_instance` to stub a method (#{method_name}) that has been " \
22
+ "defined on a prepended module (#{problem_mod}) is not supported."
23
+ end
24
+ end
25
+
26
+ def self.error_generator
27
+ @error_generator ||= ErrorGenerator.new
28
+ end
29
+ end
30
+ end
31
+ end
@@ -23,11 +23,7 @@ module RSpec
23
23
  end
24
24
 
25
25
  def invocation_order
26
- @invocation_order ||= {
27
- :and_return => [nil],
28
- :and_raise => [nil],
29
- :and_yield => [nil]
30
- }
26
+ EmptyInvocationOrder
31
27
  end
32
28
  end
33
29
  end
@@ -4,7 +4,7 @@ module RSpec
4
4
  # @private
5
5
  class ExpectationChain < Chain
6
6
  def expectation_fulfilled?
7
- @expectation_fulfilled || constrained_to_any_of?(:never, :any_number_of_times)
7
+ @expectation_fulfilled || constrained_to_any_of?(:never)
8
8
  end
9
9
 
10
10
  def initialize(*args, &block)
@@ -14,13 +14,12 @@ module RSpec
14
14
 
15
15
  private
16
16
 
17
- def verify_invocation_order(rspec_method_name, *args, &block)
17
+ def verify_invocation_order(_rspec_method_name, *_args, &_block)
18
18
  end
19
19
  end
20
20
 
21
21
  # @private
22
22
  class PositiveExpectationChain < ExpectationChain
23
-
24
23
  private
25
24
 
26
25
  def create_message_expectation_on(instance)
@@ -36,12 +35,14 @@ module RSpec
36
35
  me
37
36
  end
38
37
 
39
- def invocation_order
40
- @invocation_order ||= {
41
- :with => [nil],
38
+ ExpectationInvocationOrder =
39
+ {
42
40
  :and_return => [:with, nil],
43
- :and_raise => [:with, nil]
44
- }
41
+ :and_raise => [:with, nil],
42
+ }.freeze
43
+
44
+ def invocation_order
45
+ ExpectationInvocationOrder
45
46
  end
46
47
  end
47
48
  end
@@ -35,15 +35,13 @@ module RSpec
35
35
  # @private
36
36
  def each_unfulfilled_expectation_matching(method_name, *args)
37
37
  @chains_by_method_name[method_name].each do |chain|
38
- if !chain.expectation_fulfilled? && chain.matches_args?(*args)
39
- yield chain
40
- end
38
+ yield chain if !chain.expectation_fulfilled? && chain.matches_args?(*args)
41
39
  end
42
40
  end
43
41
 
44
42
  # @private
45
43
  def all_expectations_fulfilled?
46
- @chains_by_method_name.all? do |method_name, chains|
44
+ @chains_by_method_name.all? do |_method_name, chains|
47
45
  chains.all? { |chain| chain.expectation_fulfilled? }
48
46
  end
49
47
  end
@@ -51,7 +49,7 @@ module RSpec
51
49
  # @private
52
50
  def unfulfilled_expectations
53
51
  @chains_by_method_name.map do |method_name, chains|
54
- method_name.to_s if ExpectationChain === chains.last unless chains.last.expectation_fulfilled?
52
+ method_name.to_s if ExpectationChain === chains.last && !chains.last.expectation_fulfilled?
55
53
  end.compact
56
54
  end
57
55
 
@@ -74,9 +72,10 @@ module RSpec
74
72
 
75
73
  def raise_if_second_instance_to_receive_message(instance)
76
74
  @instance_with_expectation ||= instance if ExpectationChain === instance
77
- if ExpectationChain === instance && !@instance_with_expectation.equal?(instance)
78
- raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}"
79
- end
75
+ return unless ExpectationChain === instance
76
+ return if @instance_with_expectation.equal?(instance)
77
+
78
+ AnyInstance.error_generator.raise_second_instance_received_message_error(unfulfilled_expectations)
80
79
  end
81
80
  end
82
81
  end
@@ -10,7 +10,7 @@ module RSpec
10
10
  #
11
11
  # This proxy sits in front of the recorder and delegates both to it
12
12
  # and to the `RSpec::Mocks::Proxy` for each already mocked or stubbed
13
- # instance of the class, in order to propogates changes to the instances.
13
+ # instance of the class, in order to propagates changes to the instances.
14
14
  #
15
15
  # Note that unlike `RSpec::Mocks::Proxy`, this proxy class is stateless
16
16
  # and is not persisted in `RSpec::Mocks.space`.
@@ -83,6 +83,15 @@ module RSpec
83
83
  end
84
84
  end
85
85
 
86
+ unless defined?(BasicObject)
87
+ class BasicObject
88
+ # Remove all methods except those expected to be defined on BasicObject
89
+ (instance_methods.map(&:to_sym) - [:__send__, :"!", :instance_eval, :==, :instance_exec, :"!=", :equal?, :__id__, :__binding__, :object_id]).each do |method|
90
+ undef_method method
91
+ end
92
+ end
93
+ end
94
+
86
95
  # @private
87
96
  # Delegates messages to each of the given targets in order to
88
97
  # provide the fluent interface that is available off of message
@@ -91,17 +100,17 @@ module RSpec
91
100
  # `targets` will typically contain 1 of the `AnyInstance::Recorder`
92
101
  # return values and N `MessageExpectation` instances (one per instance
93
102
  # of the `any_instance` klass).
94
- class FluentInterfaceProxy
103
+ class FluentInterfaceProxy < BasicObject
95
104
  def initialize(targets)
96
105
  @targets = targets
97
106
  end
98
107
 
99
- if RUBY_VERSION.to_f > 1.8
100
- def respond_to_missing?(method_name, include_private = false)
108
+ if ::RUBY_VERSION.to_f > 1.8
109
+ def respond_to_missing?(method_name, include_private=false)
101
110
  super || @targets.first.respond_to?(method_name, include_private)
102
111
  end
103
112
  else
104
- def respond_to?(method_name, include_private = false)
113
+ def respond_to?(method_name, include_private=false)
105
114
  super || @targets.first.respond_to?(method_name, include_private)
106
115
  end
107
116
  end
@@ -15,11 +15,17 @@ module RSpec
15
15
 
16
16
  def initialize(klass)
17
17
  @message_chains = MessageChains.new
18
- @stubs = Hash.new { |hash,key| hash[key] = [] }
18
+ @stubs = Hash.new { |hash, key| hash[key] = [] }
19
19
  @observed_methods = []
20
20
  @played_methods = {}
21
+ @backed_up_method_owner = {}
21
22
  @klass = klass
22
23
  @expectation_set = false
24
+
25
+ return unless RSpec::Mocks.configuration.verify_partial_doubles?
26
+ RSpec::Mocks.configuration.verifying_double_callbacks.each do |block|
27
+ block.call(ObjectReference.for(klass))
28
+ end
23
29
  end
24
30
 
25
31
  # Initializes the recording a stub to be played back against any
@@ -76,7 +82,7 @@ module RSpec
76
82
  # @see Methods#unstub
77
83
  def unstub(method_name)
78
84
  unless @observed_methods.include?(method_name.to_sym)
79
- raise RSpec::Mocks::MockExpectationError, "The method `#{method_name}` was not stubbed or was already unstubbed"
85
+ AnyInstance.error_generator.raise_method_not_stubbed_error(method_name)
80
86
  end
81
87
  message_chains.remove_stub_chains_for!(method_name)
82
88
  stubs[method_name].clear
@@ -88,14 +94,15 @@ module RSpec
88
94
  # Used internally to verify that message expectations have been
89
95
  # fulfilled.
90
96
  def verify
91
- if @expectation_set && !message_chains.all_expectations_fulfilled?
92
- raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{message_chains.unfulfilled_expectations.sort.join(', ')}"
93
- end
97
+ return unless @expectation_set
98
+ return if message_chains.all_expectations_fulfilled?
99
+
100
+ AnyInstance.error_generator.raise_second_instance_received_message_error(message_chains.unfulfilled_expectations)
94
101
  end
95
102
 
96
103
  # @private
97
104
  def stop_all_observation!
98
- @observed_methods.each {|method_name| restore_method!(method_name)}
105
+ @observed_methods.each { |method_name| restore_method!(method_name) }
99
106
  end
100
107
 
101
108
  # @private
@@ -122,7 +129,7 @@ module RSpec
122
129
  end
123
130
 
124
131
  # @private
125
- def notify_received_message(object, message, args, blk)
132
+ def notify_received_message(_object, message, args, _blk)
126
133
  has_expectation = false
127
134
 
128
135
  message_chains.each_unfulfilled_expectation_matching(message, *args) do |expectation|
@@ -130,10 +137,10 @@ module RSpec
130
137
  expectation.expectation_fulfilled!
131
138
  end
132
139
 
133
- if has_expectation
134
- restore_method!(message)
135
- mark_invoked!(message)
136
- end
140
+ return unless has_expectation
141
+
142
+ restore_method!(message)
143
+ mark_invoked!(message)
137
144
  end
138
145
 
139
146
  protected
@@ -167,7 +174,7 @@ module RSpec
167
174
  end
168
175
 
169
176
  def normalize_chain(*args)
170
- args.shift.to_s.split('.').map {|s| s.to_sym}.reverse.each {|a| args.unshift a}
177
+ args.shift.to_s.split('.').map { |s| s.to_sym }.reverse.each { |a| args.unshift a }
171
178
  yield args.first, args
172
179
  end
173
180
 
@@ -186,13 +193,34 @@ module RSpec
186
193
  end
187
194
 
188
195
  def restore_original_method!(method_name)
189
- if @klass.instance_method(method_name).owner == @klass
190
- alias_method_name = build_alias_method_name(method_name)
191
- @klass.class_exec do
192
- remove_method method_name
193
- alias_method method_name, alias_method_name
194
- remove_method alias_method_name
196
+ return unless @klass.instance_method(method_name).owner == @klass
197
+
198
+ alias_method_name = build_alias_method_name(method_name)
199
+ @klass.class_exec(@backed_up_method_owner) do |backed_up_method_owner|
200
+ remove_method method_name
201
+
202
+ # A @klass can have methods implemented (see Method#owner) in @klass
203
+ # or inherited from a superclass. In ruby 2.2 and earlier, we can copy
204
+ # a method regardless of the 'owner' and restore it to @klass after
205
+ # because a call to 'super' from @klass's copied method would end up
206
+ # calling the original class's superclass's method.
207
+ #
208
+ # With the commit below, available starting in 2.3.0, ruby changed
209
+ # this behavior and a call to 'super' from the method copied to @klass
210
+ # will call @klass's superclass method, which is the original
211
+ # implementer of this method! This leads to very strange errors
212
+ # if @klass's copied method calls 'super', since it would end up
213
+ # calling itself, the original method implemented in @klass's
214
+ # superclass.
215
+ #
216
+ # For ruby 2.3 and above, we need to only restore methods that
217
+ # @klass originally owned.
218
+ #
219
+ # https://github.com/ruby/ruby/commit/c8854d2ca4be9ee6946e6d17b0e17d9ef130ee81
220
+ if RUBY_VERSION < "2.3" || backed_up_method_owner[method_name.to_sym] == self
221
+ alias_method method_name, alias_method_name
195
222
  end
223
+ remove_method alias_method_name
196
224
  end
197
225
  end
198
226
 
@@ -203,10 +231,13 @@ module RSpec
203
231
  end
204
232
 
205
233
  def backup_method!(method_name)
234
+ return unless public_protected_or_private_method_defined?(method_name)
235
+
206
236
  alias_method_name = build_alias_method_name(method_name)
237
+ @backed_up_method_owner[method_name.to_sym] ||= @klass.instance_method(method_name).owner
207
238
  @klass.class_exec do
208
239
  alias_method alias_method_name, method_name
209
- end if public_protected_or_private_method_defined?(method_name)
240
+ end
210
241
  end
211
242
 
212
243
  def public_protected_or_private_method_defined?(method_name)
@@ -216,10 +247,9 @@ module RSpec
216
247
  def observe!(method_name)
217
248
  allow_no_prepended_module_definition_of(method_name)
218
249
 
219
- if RSpec::Mocks.configuration.verify_partial_doubles?
250
+ if RSpec::Mocks.configuration.verify_partial_doubles? && !Mocks.configuration.temporarily_suppress_partial_double_verification
220
251
  unless public_protected_or_private_method_defined?(method_name)
221
- raise MockExpectationError,
222
- "#{@klass} does not implement ##{method_name}"
252
+ AnyInstance.error_generator.raise_does_not_implement_error(@klass, method_name)
223
253
  end
224
254
  end
225
255
 
@@ -229,36 +259,36 @@ module RSpec
229
259
  recorder = self
230
260
  @klass.__send__(:define_method, method_name) do |*args, &blk|
231
261
  recorder.playback!(self, method_name)
232
- self.__send__(method_name, *args, &blk)
262
+ __send__(method_name, *args, &blk)
233
263
  end
264
+ @klass.__send__(:ruby2_keywords, method_name) if @klass.respond_to?(:ruby2_keywords, true)
234
265
  end
235
266
 
236
267
  def mark_invoked!(method_name)
237
268
  backup_method!(method_name)
238
269
  recorder = self
239
- @klass.__send__(:define_method, method_name) do |*args, &blk|
270
+ @klass.__send__(:define_method, method_name) do |*_args, &_blk|
240
271
  invoked_instance = recorder.instance_that_received(method_name)
241
272
  inspect = "#<#{self.class}:#{object_id} #{instance_variables.map { |name| "#{name}=#{instance_variable_get name}" }.join(', ')}>"
242
- raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by #{inspect} but has already been received by #{invoked_instance}"
273
+ AnyInstance.error_generator.raise_message_already_received_by_other_instance_error(
274
+ method_name, inspect, invoked_instance
275
+ )
243
276
  end
244
277
  end
245
278
 
246
279
  if Support::RubyFeatures.module_prepends_supported?
247
280
  def allow_no_prepended_module_definition_of(method_name)
248
- prepended_modules = @klass.ancestors.take_while { |mod| !(Class === mod) }
281
+ prepended_modules = RSpec::Mocks::Proxy.prepended_modules_of(@klass)
249
282
  problem_mod = prepended_modules.find { |mod| mod.method_defined?(method_name) }
250
283
  return unless problem_mod
251
284
 
252
- raise RSpec::Mocks::MockExpectationError,
253
- "Using `any_instance` to stub a method (#{method_name}) that has been " +
254
- "defined on a prepended module (#{problem_mod}) is not supported."
285
+ AnyInstance.error_generator.raise_not_supported_with_prepend_error(method_name, problem_mod)
255
286
  end
256
287
  else
257
- def allow_no_prepended_module_definition_of(method_name)
288
+ def allow_no_prepended_module_definition_of(_method_name)
258
289
  # nothing to do; prepends aren't supported on this version of ruby
259
290
  end
260
291
  end
261
-
262
292
  end
263
293
  end
264
294
  end