rspec-mocks 2.8.0.rc1 → 2.8.0.rc2
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.
- data/License.txt +23 -0
- data/README.md +238 -21
- data/lib/rspec/mocks.rb +16 -173
- data/lib/rspec/mocks/any_instance.rb +19 -3
- data/lib/rspec/mocks/any_instance/chain.rb +115 -6
- data/lib/rspec/mocks/any_instance/message_chains.rb +35 -23
- data/lib/rspec/mocks/any_instance/recorder.rb +74 -49
- data/lib/rspec/mocks/argument_expectation.rb +1 -0
- data/lib/rspec/mocks/argument_matchers.rb +42 -39
- data/lib/rspec/mocks/error_generator.rb +25 -15
- data/lib/rspec/mocks/errors.rb +2 -0
- data/lib/rspec/mocks/{spec_methods.rb → example_methods.rb} +12 -11
- data/lib/rspec/mocks/extensions/instance_exec.rb +3 -0
- data/lib/rspec/mocks/message_expectation.rb +275 -131
- data/lib/rspec/mocks/method_double.rb +32 -7
- data/lib/rspec/mocks/methods.rb +82 -30
- data/lib/rspec/mocks/mock.rb +7 -5
- data/lib/rspec/mocks/order_group.rb +9 -5
- data/lib/rspec/mocks/proxy.rb +33 -12
- data/lib/rspec/mocks/serialization.rb +4 -0
- data/lib/rspec/mocks/space.rb +1 -0
- data/lib/rspec/mocks/version.rb +1 -1
- data/spec/rspec/mocks/{hash_not_including_matcher_spec.rb → hash_excluding_matcher_spec.rb} +2 -2
- data/spec/rspec/mocks/stub_spec.rb +12 -9
- metadata +15 -15
- data/lib/rspec/mocks/any_instance/expectation_chain.rb +0 -33
- data/lib/rspec/mocks/any_instance/stub_chain.rb +0 -35
- data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +0 -34
@@ -1,18 +1,33 @@
|
|
1
1
|
require 'rspec/mocks/any_instance/chain'
|
2
|
-
require 'rspec/mocks/any_instance/stub_chain'
|
3
|
-
require 'rspec/mocks/any_instance/stub_chain_chain'
|
4
|
-
require 'rspec/mocks/any_instance/expectation_chain'
|
5
2
|
require 'rspec/mocks/any_instance/message_chains'
|
6
3
|
require 'rspec/mocks/any_instance/recorder'
|
7
4
|
|
8
5
|
module RSpec
|
9
6
|
module Mocks
|
10
7
|
module AnyInstance
|
8
|
+
# Used to set stubs and message expectations on any instance of a given
|
9
|
+
# class. Returns a [Recorder](Recorder), which records messages like
|
10
|
+
# `stub` and `should_receive` for later playback on instances of the
|
11
|
+
# class.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
#
|
15
|
+
# Car.any_instance.should_receive(:go)
|
16
|
+
# race = Race.new
|
17
|
+
# race.cars << Car.new
|
18
|
+
# race.go # assuming this delegates to all of its cars
|
19
|
+
# # this example would pass
|
20
|
+
#
|
21
|
+
# Account.any_instance.stub(:balance) { Money.new(:USD, 25) }
|
22
|
+
# Account.new.balance # => Money.new(:USD, 25))
|
23
|
+
#
|
24
|
+
# @return [Recorder]
|
11
25
|
def any_instance
|
12
26
|
RSpec::Mocks::space.add(self)
|
13
27
|
__recorder
|
14
28
|
end
|
15
29
|
|
30
|
+
# @private
|
16
31
|
def rspec_verify
|
17
32
|
__recorder.verify
|
18
33
|
super
|
@@ -21,6 +36,7 @@ module RSpec
|
|
21
36
|
@__recorder = nil
|
22
37
|
end
|
23
38
|
|
39
|
+
# @private
|
24
40
|
def __recorder
|
25
41
|
@__recorder ||= AnyInstance::Recorder.new(self)
|
26
42
|
end
|
@@ -2,25 +2,47 @@ module RSpec
|
|
2
2
|
module Mocks
|
3
3
|
module AnyInstance
|
4
4
|
class Chain
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
class << self
|
6
|
+
private
|
7
|
+
|
8
|
+
# @macro [attach] record
|
9
|
+
# @method $1(*args, &block)
|
10
|
+
# Records the `$1` message for playback against an instance that
|
11
|
+
# invokes a method stubbed or mocked using `any_instance`.
|
12
|
+
#
|
13
|
+
# @see RSpec::Mocks::MessageExpectation#$1
|
14
|
+
#
|
15
|
+
def record(method_name)
|
11
16
|
class_eval(<<-EOM, __FILE__, __LINE__)
|
12
17
|
def #{method_name}(*args, &block)
|
13
18
|
record(:#{method_name}, *args, &block)
|
14
19
|
end
|
15
20
|
EOM
|
21
|
+
end
|
16
22
|
end
|
17
23
|
|
24
|
+
record :and_return
|
25
|
+
record :and_raise
|
26
|
+
record :and_throw
|
27
|
+
record :and_yield
|
28
|
+
record :with
|
29
|
+
record :once
|
30
|
+
record :twice
|
31
|
+
record :any_number_of_times
|
32
|
+
record :exactly
|
33
|
+
record :times
|
34
|
+
record :never
|
35
|
+
record :at_least
|
36
|
+
record :at_most
|
37
|
+
|
38
|
+
# @private
|
18
39
|
def playback!(instance)
|
19
40
|
messages.inject(instance) do |_instance, message|
|
20
41
|
_instance.__send__(*message.first, &message.last)
|
21
42
|
end
|
22
43
|
end
|
23
44
|
|
45
|
+
# @private
|
24
46
|
def constrained_to_any_of?(*constraints)
|
25
47
|
constraints.any? do |constraint|
|
26
48
|
messages.any? do |message|
|
@@ -29,7 +51,13 @@ module RSpec
|
|
29
51
|
end
|
30
52
|
end
|
31
53
|
|
54
|
+
# @private
|
55
|
+
def expectation_fulfilled!
|
56
|
+
@expectation_fulfilled = true
|
57
|
+
end
|
58
|
+
|
32
59
|
private
|
60
|
+
|
33
61
|
def messages
|
34
62
|
@messages ||= []
|
35
63
|
end
|
@@ -44,6 +72,87 @@ module RSpec
|
|
44
72
|
self
|
45
73
|
end
|
46
74
|
end
|
75
|
+
|
76
|
+
# @private
|
77
|
+
class ExpectationChain < Chain
|
78
|
+
|
79
|
+
# @private
|
80
|
+
def initialize(*args, &block)
|
81
|
+
record(:should_receive, *args, &block)
|
82
|
+
@expectation_fulfilled = false
|
83
|
+
end
|
84
|
+
|
85
|
+
# @private
|
86
|
+
def expectation_fulfilled?
|
87
|
+
@expectation_fulfilled || constrained_to_any_of?(:never, :any_number_of_times)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def verify_invocation_order(rspec_method_name, *args, &block)
|
93
|
+
end
|
94
|
+
|
95
|
+
def invocation_order
|
96
|
+
@invocation_order ||= {
|
97
|
+
:should_receive => [nil],
|
98
|
+
:with => [:should_receive],
|
99
|
+
:and_return => [:with, :should_receive],
|
100
|
+
:and_raise => [:with, :should_receive]
|
101
|
+
}
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# @private
|
106
|
+
class StubChain < Chain
|
107
|
+
|
108
|
+
# @private
|
109
|
+
def initialize(*args, &block)
|
110
|
+
record(:stub, *args, &block)
|
111
|
+
end
|
112
|
+
|
113
|
+
# @private
|
114
|
+
def expectation_fulfilled?
|
115
|
+
true
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def invocation_order
|
121
|
+
@invocation_order ||= {
|
122
|
+
:stub => [nil],
|
123
|
+
:with => [:stub],
|
124
|
+
:and_return => [:with, :stub],
|
125
|
+
:and_raise => [:with, :stub],
|
126
|
+
:and_yield => [:with, :stub]
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
def verify_invocation_order(rspec_method_name, *args, &block)
|
131
|
+
unless invocation_order[rspec_method_name].include?(last_message)
|
132
|
+
raise(NoMethodError, "Undefined method #{rspec_method_name}")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# @private
|
138
|
+
class StubChainChain < StubChain
|
139
|
+
|
140
|
+
# @private
|
141
|
+
def initialize(*args, &block)
|
142
|
+
record(:stub_chain, *args, &block)
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def invocation_order
|
148
|
+
@invocation_order ||= {
|
149
|
+
:stub_chain => [nil],
|
150
|
+
:and_return => [:stub_chain],
|
151
|
+
:and_raise => [:stub_chain],
|
152
|
+
:and_yield => [:stub_chain]
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
47
156
|
end
|
48
157
|
end
|
49
158
|
end
|
@@ -1,48 +1,60 @@
|
|
1
1
|
module RSpec
|
2
2
|
module Mocks
|
3
3
|
module AnyInstance
|
4
|
+
# @private
|
4
5
|
class MessageChains < Hash
|
6
|
+
def initialize
|
7
|
+
super {|h,k| h[k] = []}
|
8
|
+
end
|
9
|
+
|
10
|
+
# @private
|
5
11
|
def add(method_name, chain)
|
6
|
-
|
12
|
+
self[method_name] << chain
|
13
|
+
chain
|
7
14
|
end
|
8
|
-
|
15
|
+
|
16
|
+
# @private
|
9
17
|
def remove_stub_chains_for!(method_name)
|
10
|
-
|
11
|
-
chains.reject! { |chain| chain.is_a?(StubChain) || chain.is_a?(StubChainChain) }
|
18
|
+
self[method_name].reject! {|chain| chain.is_a?(StubChain)}
|
12
19
|
end
|
13
|
-
|
20
|
+
|
21
|
+
# @private
|
14
22
|
def has_expectation?(method_name)
|
15
|
-
|
23
|
+
self[method_name].find {|chain| chain.is_a?(ExpectationChain)}
|
16
24
|
end
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
25
|
+
|
26
|
+
# @private
|
27
|
+
def all_expectations_fulfilled?
|
28
|
+
all? {|method_name, chains| chains.all? {|chain| chain.expectation_fulfilled?}}
|
22
29
|
end
|
23
30
|
|
31
|
+
# @private
|
24
32
|
def unfulfilled_expectations
|
25
|
-
|
33
|
+
map do |method_name, chains|
|
26
34
|
method_name.to_s if chains.last.is_a?(ExpectationChain) unless chains.last.expectation_fulfilled?
|
27
35
|
end.compact
|
28
36
|
end
|
29
37
|
|
38
|
+
# @private
|
30
39
|
def received_expected_message!(method_name)
|
31
|
-
self[method_name].each
|
32
|
-
chain.expectation_fulfilled!
|
33
|
-
end
|
40
|
+
self[method_name].each {|chain| chain.expectation_fulfilled!}
|
34
41
|
end
|
35
|
-
|
42
|
+
|
43
|
+
# @private
|
36
44
|
def playback!(instance, method_name)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
raise_if_second_instance_to_receive_message(instance)
|
46
|
+
self[method_name].each {|chain| chain.playback!(instance)}
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def raise_if_second_instance_to_receive_message(instance)
|
52
|
+
@instance_with_expectation ||= instance if instance.is_a?(ExpectationChain)
|
53
|
+
if instance.is_a?(ExpectationChain) && !@instance_with_expectation.equal?(instance)
|
54
|
+
raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}"
|
43
55
|
end
|
44
56
|
end
|
45
57
|
end
|
46
58
|
end
|
47
59
|
end
|
48
|
-
end
|
60
|
+
end
|
@@ -1,8 +1,18 @@
|
|
1
1
|
module RSpec
|
2
2
|
module Mocks
|
3
3
|
module AnyInstance
|
4
|
+
# Given a class `TheClass`, `TheClass.any_instance` returns a `Recorder`,
|
5
|
+
# which records stubs and message expectations for later playback on
|
6
|
+
# instances of `TheClass`.
|
7
|
+
#
|
8
|
+
# Further constraints are stored in instances of [Chain](Chain).
|
9
|
+
#
|
10
|
+
# @see AnyInstance
|
11
|
+
# @see Chain
|
4
12
|
class Recorder
|
13
|
+
# @private
|
5
14
|
attr_reader :message_chains
|
15
|
+
|
6
16
|
def initialize(klass)
|
7
17
|
@message_chains = MessageChains.new
|
8
18
|
@observed_methods = []
|
@@ -10,7 +20,49 @@ module RSpec
|
|
10
20
|
@klass = klass
|
11
21
|
@expectation_set = false
|
12
22
|
end
|
13
|
-
|
23
|
+
|
24
|
+
# Initializes the recording a stub to be played back against any
|
25
|
+
# instance of this object that invokes the submitted method.
|
26
|
+
#
|
27
|
+
# @see Methods#stub
|
28
|
+
def stub(method_name_or_method_map, &block)
|
29
|
+
if method_name_or_method_map.is_a?(Hash)
|
30
|
+
method_name_or_method_map.each do |method_name, return_value|
|
31
|
+
stub(method_name).and_return(return_value)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
observe!(method_name_or_method_map)
|
35
|
+
message_chains.add(method_name_or_method_map, StubChain.new(method_name_or_method_map, &block))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Initializes the recording a stub chain to be played back against any
|
40
|
+
# instance of this object that invokes the method matching the first
|
41
|
+
# argument.
|
42
|
+
#
|
43
|
+
# @see Methods#stub_chain
|
44
|
+
def stub_chain(*method_names_and_optional_return_values, &block)
|
45
|
+
normalize_chain(*method_names_and_optional_return_values) do |method_name, args|
|
46
|
+
observe!(method_name)
|
47
|
+
message_chains.add(method_name, StubChainChain.new(*args, &block))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Initializes the recording a message expectation to be played back
|
52
|
+
# against any instance of this object that invokes the submitted
|
53
|
+
# method.
|
54
|
+
#
|
55
|
+
# @see Methods#should_receive
|
56
|
+
def should_receive(method_name, &block)
|
57
|
+
@expectation_set = true
|
58
|
+
observe!(method_name)
|
59
|
+
message_chains.add(method_name, ExpectationChain.new(method_name, &block))
|
60
|
+
end
|
61
|
+
|
62
|
+
# Removes any previously recorded stubs, stub_chains or message
|
63
|
+
# expectations that use `method_name`.
|
64
|
+
#
|
65
|
+
# @see Methods#unstub
|
14
66
|
def unstub(method_name)
|
15
67
|
unless @observed_methods.include?(method_name.to_sym)
|
16
68
|
raise RSpec::Mocks::MockExpectationError, "The method `#{method_name}` was not stubbed or was already unstubbed"
|
@@ -18,52 +70,28 @@ module RSpec
|
|
18
70
|
message_chains.remove_stub_chains_for!(method_name)
|
19
71
|
stop_observing!(method_name) unless message_chains.has_expectation?(method_name)
|
20
72
|
end
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
method_map
|
31
|
-
else
|
32
|
-
method_name = method_name_or_method_map
|
33
|
-
observe!(method_name)
|
34
|
-
message_chains.add(method_name, chain = StubChain.new(method_name, *args, &block))
|
35
|
-
chain
|
73
|
+
|
74
|
+
# @api private
|
75
|
+
#
|
76
|
+
# Used internally to verify that message expectations have been
|
77
|
+
# fulfilled.
|
78
|
+
def verify
|
79
|
+
if @expectation_set && !message_chains.all_expectations_fulfilled?
|
80
|
+
raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{message_chains.unfulfilled_expectations.sort.join(', ')}"
|
36
81
|
end
|
37
82
|
end
|
38
83
|
|
84
|
+
# @private
|
39
85
|
def stub!(*)
|
40
86
|
raise "stub! is not supported on any_instance. Use stub instead."
|
41
87
|
end
|
42
88
|
|
43
|
-
|
44
|
-
if period_separated_method_chain?(method_name_or_string_chain)
|
45
|
-
first_method_name = method_name_or_string_chain.split('.').first.to_sym
|
46
|
-
else
|
47
|
-
first_method_name = method_name_or_string_chain
|
48
|
-
end
|
49
|
-
observe!(first_method_name)
|
50
|
-
message_chains.add(first_method_name, chain = StubChainChain.new(method_name_or_string_chain, *args, &block))
|
51
|
-
chain
|
52
|
-
end
|
53
|
-
|
54
|
-
def should_receive(method_name, *args, &block)
|
55
|
-
observe!(method_name)
|
56
|
-
@expectation_set = true
|
57
|
-
message_chains.add(method_name, chain = ExpectationChain.new(method_name, *args, &block))
|
58
|
-
chain
|
59
|
-
end
|
60
|
-
|
89
|
+
# @private
|
61
90
|
def stop_all_observation!
|
62
|
-
@observed_methods.each
|
63
|
-
restore_method!(method_name)
|
64
|
-
end
|
91
|
+
@observed_methods.each {|method_name| restore_method!(method_name)}
|
65
92
|
end
|
66
93
|
|
94
|
+
# @private
|
67
95
|
def playback!(instance, method_name)
|
68
96
|
RSpec::Mocks::space.add(instance)
|
69
97
|
message_chains.playback!(instance, method_name)
|
@@ -71,27 +99,24 @@ module RSpec
|
|
71
99
|
received_expected_message!(method_name) if message_chains.has_expectation?(method_name)
|
72
100
|
end
|
73
101
|
|
102
|
+
# @private
|
74
103
|
def instance_that_received(method_name)
|
75
104
|
@played_methods[method_name]
|
76
105
|
end
|
77
106
|
|
78
|
-
def verify
|
79
|
-
if @expectation_set && !message_chains.each_expectation_fulfilled?
|
80
|
-
raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{message_chains.unfulfilled_expectations.sort.join(', ')}"
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
107
|
private
|
85
|
-
|
86
|
-
|
108
|
+
|
109
|
+
def normalize_chain(*args)
|
110
|
+
args.shift.to_s.split('.').map {|s| s.to_sym}.reverse.each {|a| args.unshift a}
|
111
|
+
yield args.first, args
|
87
112
|
end
|
88
|
-
|
113
|
+
|
89
114
|
def received_expected_message!(method_name)
|
90
115
|
message_chains.received_expected_message!(method_name)
|
91
116
|
restore_method!(method_name)
|
92
117
|
mark_invoked!(method_name)
|
93
118
|
end
|
94
|
-
|
119
|
+
|
95
120
|
def restore_method!(method_name)
|
96
121
|
if public_protected_or_private_method_defined?(build_alias_method_name(method_name))
|
97
122
|
restore_original_method!(method_name)
|
@@ -125,11 +150,11 @@ module RSpec
|
|
125
150
|
alias_method alias_method_name, method_name
|
126
151
|
end if public_protected_or_private_method_defined?(method_name)
|
127
152
|
end
|
128
|
-
|
153
|
+
|
129
154
|
def public_protected_or_private_method_defined?(method_name)
|
130
155
|
@klass.method_defined?(method_name) || @klass.private_method_defined?(method_name)
|
131
156
|
end
|
132
|
-
|
157
|
+
|
133
158
|
def stop_observing!(method_name)
|
134
159
|
restore_method!(method_name)
|
135
160
|
@observed_methods.delete(method_name)
|