rspec-mocks 2.8.0.rc1 → 2.8.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|