rspec-mocks 3.0.4 → 3.12.6
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.
- 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 [](https://github.com/rspec/rspec-mocks/actions) [](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
|