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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.document +1 -1
- data/.yardopts +1 -1
- data/Changelog.md +512 -2
- data/{License.txt → LICENSE.md} +5 -4
- data/README.md +113 -30
- data/lib/rspec/mocks/any_instance/chain.rb +5 -3
- data/lib/rspec/mocks/any_instance/error_generator.rb +31 -0
- data/lib/rspec/mocks/any_instance/expect_chain_chain.rb +1 -5
- data/lib/rspec/mocks/any_instance/expectation_chain.rb +9 -8
- data/lib/rspec/mocks/any_instance/message_chains.rb +7 -8
- data/lib/rspec/mocks/any_instance/proxy.rb +14 -5
- data/lib/rspec/mocks/any_instance/recorder.rb +61 -31
- data/lib/rspec/mocks/any_instance/stub_chain.rb +15 -11
- data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +1 -5
- data/lib/rspec/mocks/any_instance.rb +1 -0
- data/lib/rspec/mocks/argument_list_matcher.rb +55 -10
- data/lib/rspec/mocks/argument_matchers.rb +88 -30
- data/lib/rspec/mocks/configuration.rb +61 -13
- data/lib/rspec/mocks/error_generator.rb +250 -107
- data/lib/rspec/mocks/example_methods.rb +151 -28
- data/lib/rspec/mocks/instance_method_stasher.rb +17 -6
- data/lib/rspec/mocks/matchers/have_received.rb +50 -20
- data/lib/rspec/mocks/matchers/receive.rb +39 -11
- data/lib/rspec/mocks/matchers/receive_message_chain.rb +22 -7
- data/lib/rspec/mocks/matchers/receive_messages.rb +12 -7
- data/lib/rspec/mocks/message_chain.rb +3 -7
- data/lib/rspec/mocks/message_expectation.rb +466 -307
- data/lib/rspec/mocks/method_double.rb +88 -29
- data/lib/rspec/mocks/method_reference.rb +85 -25
- data/lib/rspec/mocks/minitest_integration.rb +68 -0
- data/lib/rspec/mocks/mutate_const.rb +50 -109
- data/lib/rspec/mocks/object_reference.rb +89 -32
- data/lib/rspec/mocks/order_group.rb +4 -5
- data/lib/rspec/mocks/proxy.rb +156 -60
- data/lib/rspec/mocks/space.rb +52 -35
- data/lib/rspec/mocks/standalone.rb +1 -1
- data/lib/rspec/mocks/syntax.rb +26 -30
- data/lib/rspec/mocks/targets.rb +55 -28
- data/lib/rspec/mocks/test_double.rb +43 -7
- data/lib/rspec/mocks/verifying_double.rb +27 -33
- data/lib/rspec/mocks/{verifying_message_expecation.rb → verifying_message_expectation.rb} +11 -16
- data/lib/rspec/mocks/verifying_proxy.rb +77 -26
- data/lib/rspec/mocks/version.rb +1 -1
- data/lib/rspec/mocks.rb +8 -1
- data.tar.gz.sig +0 -0
- metadata +80 -43
- metadata.gz.sig +0 -0
data/README.md
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
# RSpec Mocks [![Build Status](https://
|
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/
|
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
|
106
|
-
test.
|
107
|
-
|
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
|
-
|
110
|
-
pattern.
|
168
|
+
Verifying messages received in this way implements the Test Spy pattern.
|
111
169
|
|
112
170
|
```ruby
|
113
|
-
|
171
|
+
invitation = spy('invitation')
|
114
172
|
|
115
|
-
|
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
|
-
|
195
|
+
message = "Hello"
|
196
|
+
greeter.greet_with(message)
|
197
|
+
message << ", World"
|
118
198
|
|
119
|
-
|
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
|
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
|
-
|
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
|
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/
|
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
|
-
|
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://
|
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
|
-
* [
|
379
|
-
* [
|
380
|
-
* [
|
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 :
|
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
|
-
|
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
|
-
|
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
|
@@ -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
|
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(
|
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
|
-
|
40
|
-
|
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 |
|
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
|
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
|
-
|
78
|
-
|
79
|
-
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
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(
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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
|
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
|
-
|
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
|
-
|
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 |*
|
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
|
-
|
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
|
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
|
-
|
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(
|
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
|