rspec-mocks 2.5.0 → 2.6.0
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/.travis.yml +7 -0
- data/Gemfile +14 -13
- data/Guardfile +3 -3
- data/README.md +2 -0
- data/features/.nav +4 -0
- data/features/Changelog.md +12 -0
- data/features/README.markdown +44 -4
- data/features/Upgrade.md +10 -2
- data/features/message_expectations/any_instance.feature +19 -0
- data/features/message_expectations/block_local_expectations.feature.pending +2 -2
- data/features/message_expectations/expect_message.feature +3 -3
- data/features/message_expectations/warn_when_expectation_is_set_on_nil.feature +3 -3
- data/features/method_stubs/README.md +32 -24
- data/features/method_stubs/any_instance.feature +23 -0
- data/features/method_stubs/as_null_object.feature +36 -0
- data/features/method_stubs/simple_return_value.feature +2 -2
- data/features/method_stubs/stub_chain.feature +13 -6
- data/features/method_stubs/stub_implementation.feature +1 -1
- data/features/method_stubs/to_ary.feature +45 -0
- data/features/outside_rspec/configuration.feature +3 -3
- data/features/outside_rspec/standalone.feature +2 -2
- data/features/step_definitions/additional_cli_steps.rb +4 -0
- data/features/support/env.rb +5 -1
- data/lib/rspec/mocks/any_instance.rb +256 -0
- data/lib/rspec/mocks/argument_expectation.rb +9 -8
- data/lib/rspec/mocks/extensions/marshal.rb +0 -4
- data/lib/rspec/mocks/extensions/psych.rb +23 -0
- data/lib/rspec/mocks/framework.rb +1 -0
- data/lib/rspec/mocks/message_expectation.rb +7 -4
- data/lib/rspec/mocks/methods.rb +1 -1
- data/lib/rspec/mocks/mock.rb +2 -2
- data/lib/rspec/mocks/serialization.rb +5 -3
- data/lib/rspec/mocks/space.rb +1 -1
- data/lib/rspec/mocks/version.rb +1 -1
- data/lib/rspec/mocks.rb +2 -1
- data/spec/rspec/mocks/and_yield_spec.rb +3 -3
- data/spec/rspec/mocks/any_instance_spec.rb +590 -0
- data/spec/rspec/mocks/any_number_of_times_spec.rb +22 -28
- data/spec/rspec/mocks/at_least_spec.rb +6 -0
- data/spec/rspec/mocks/at_most_spec.rb +6 -0
- data/spec/rspec/mocks/bug_report_10263_spec.rb +12 -14
- data/spec/rspec/mocks/bug_report_7611_spec.rb +2 -4
- data/spec/rspec/mocks/failing_argument_matchers_spec.rb +45 -46
- data/spec/rspec/mocks/nil_expectation_warning_spec.rb +1 -2
- data/spec/rspec/mocks/passing_argument_matchers_spec.rb +119 -122
- data/spec/rspec/mocks/precise_counts_spec.rb +6 -0
- data/spec/rspec/mocks/serialization_spec.rb +21 -3
- data/spec/rspec/mocks/stub_chain_spec.rb +72 -37
- data/spec/rspec/mocks/stub_spec.rb +64 -61
- data/spec/rspec/mocks/to_ary_spec.rb +31 -0
- metadata +24 -11
- data/spec/rspec/mocks/bug_report_7805_spec.rb +0 -22
- data/spec/rspec/mocks/bug_report_8302_spec.rb +0 -26
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Mocks
|
|
3
|
+
module AnyInstance
|
|
4
|
+
class Chain
|
|
5
|
+
[
|
|
6
|
+
:with, :and_return, :and_raise, :and_yield,
|
|
7
|
+
:once, :twice, :any_number_of_times,
|
|
8
|
+
:exactly, :times, :never,
|
|
9
|
+
:at_least, :at_most
|
|
10
|
+
].each do |method_name|
|
|
11
|
+
class_eval(<<-EOM, __FILE__, __LINE__)
|
|
12
|
+
def #{method_name}(*args, &block)
|
|
13
|
+
record(:#{method_name}, *args, &block)
|
|
14
|
+
end
|
|
15
|
+
EOM
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def playback!(instance)
|
|
19
|
+
messages.inject(instance) do |instance, message|
|
|
20
|
+
instance.send(*message.first, &message.last)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def constrained_to_any_of?(*constraints)
|
|
25
|
+
constraints.any? do |constraint|
|
|
26
|
+
messages.any? do |message|
|
|
27
|
+
message.first.first == constraint
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
def messages
|
|
34
|
+
@messages ||= []
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def last_message
|
|
38
|
+
messages.last.first.first unless messages.empty?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def record(rspec_method_name, *args, &block)
|
|
42
|
+
verify_invocation_order(rspec_method_name, *args, &block)
|
|
43
|
+
messages << [args.unshift(rspec_method_name), block]
|
|
44
|
+
self
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class StubChain < Chain
|
|
49
|
+
def initialize(*args, &block)
|
|
50
|
+
record(:stub, *args, &block)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def invocation_order
|
|
54
|
+
@invocation_order ||= {
|
|
55
|
+
:stub => [nil],
|
|
56
|
+
:with => [:stub],
|
|
57
|
+
:and_return => [:with, :stub],
|
|
58
|
+
:and_raise => [:with, :stub],
|
|
59
|
+
:and_yield => [:with, :stub]
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def expectation_filfilled?
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
def verify_invocation_order(rspec_method_name, *args, &block)
|
|
69
|
+
unless invocation_order[rspec_method_name].include?(last_message)
|
|
70
|
+
raise(NoMethodError, "Undefined method #{rspec_method_name}")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class ExpectationChain < Chain
|
|
76
|
+
def initialize(*args, &block)
|
|
77
|
+
record(:should_receive, *args, &block)
|
|
78
|
+
@expectation_fulfilled = false
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def invocation_order
|
|
82
|
+
@invocation_order ||= {
|
|
83
|
+
:should_receive => [nil],
|
|
84
|
+
:with => [:should_receive],
|
|
85
|
+
:and_return => [:with, :should_receive],
|
|
86
|
+
:and_raise => [:with, :should_receive]
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def expectation_fulfilled!
|
|
91
|
+
@expectation_fulfilled = true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def expectation_filfilled?
|
|
95
|
+
@expectation_fulfilled || constrained_to_any_of?(:never, :any_number_of_times)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
def verify_invocation_order(rspec_method_name, *args, &block)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class Recorder
|
|
104
|
+
def initialize(klass)
|
|
105
|
+
@message_chains = {}
|
|
106
|
+
@observed_methods = []
|
|
107
|
+
@played_methods = {}
|
|
108
|
+
@klass = klass
|
|
109
|
+
@expectation_set = false
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def stub(method_name, *args, &block)
|
|
113
|
+
observe!(method_name)
|
|
114
|
+
@message_chains[method_name] = StubChain.new(method_name, *args, &block)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def should_receive(method_name, *args, &block)
|
|
118
|
+
observe!(method_name)
|
|
119
|
+
@expectation_set = true
|
|
120
|
+
@message_chains[method_name] = ExpectationChain.new(method_name, *args, &block)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def stop_all_observation!
|
|
124
|
+
@observed_methods.each do |method_name|
|
|
125
|
+
restore_method!(method_name)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def playback!(instance, method_name)
|
|
130
|
+
RSpec::Mocks::space.add(instance)
|
|
131
|
+
@message_chains[method_name].playback!(instance)
|
|
132
|
+
@played_methods[method_name] = instance
|
|
133
|
+
received_expected_message!(method_name) if has_expectation?(method_name)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def instance_that_received(method_name)
|
|
137
|
+
@played_methods[method_name]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def verify
|
|
141
|
+
if @expectation_set && !each_expectation_filfilled?
|
|
142
|
+
raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
def each_expectation_filfilled?
|
|
148
|
+
@message_chains.all? do |method_name, chain|
|
|
149
|
+
chain.expectation_filfilled?
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def has_expectation?(method_name)
|
|
154
|
+
@message_chains[method_name].is_a?(ExpectationChain)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def unfulfilled_expectations
|
|
158
|
+
@message_chains.map do |method_name, chain|
|
|
159
|
+
method_name.to_s if chain.is_a?(ExpectationChain) unless chain.expectation_filfilled?
|
|
160
|
+
end.compact
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def received_expected_message!(method_name)
|
|
164
|
+
@message_chains[method_name].expectation_fulfilled!
|
|
165
|
+
restore_method!(method_name)
|
|
166
|
+
mark_invoked!(method_name)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def restore_method!(method_name)
|
|
170
|
+
if @klass.method_defined?(build_alias_method_name(method_name))
|
|
171
|
+
restore_original_method!(method_name)
|
|
172
|
+
else
|
|
173
|
+
remove_dummy_method!(method_name)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def build_alias_method_name(method_name)
|
|
178
|
+
"__#{method_name}_without_any_instance__"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def restore_original_method!(method_name)
|
|
182
|
+
alias_method_name = build_alias_method_name(method_name)
|
|
183
|
+
@klass.class_eval do
|
|
184
|
+
alias_method method_name, alias_method_name
|
|
185
|
+
remove_method alias_method_name
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def remove_dummy_method!(method_name)
|
|
190
|
+
@klass.class_eval do
|
|
191
|
+
remove_method method_name
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def backup_method!(method_name)
|
|
196
|
+
alias_method_name = build_alias_method_name(method_name)
|
|
197
|
+
@klass.class_eval do
|
|
198
|
+
if method_defined?(method_name)
|
|
199
|
+
alias_method alias_method_name, method_name
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def stop_observing!(method_name)
|
|
205
|
+
restore_method!(method_name)
|
|
206
|
+
@observed_methods.delete(method_name)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def already_observing?(method_name)
|
|
210
|
+
@observed_methods.include?(method_name)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def observe!(method_name)
|
|
214
|
+
stop_observing!(method_name) if already_observing?(method_name)
|
|
215
|
+
@observed_methods << method_name
|
|
216
|
+
backup_method!(method_name)
|
|
217
|
+
@klass.class_eval(<<-EOM, __FILE__, __LINE__)
|
|
218
|
+
def #{method_name}(*args, &blk)
|
|
219
|
+
self.class.__recorder.playback!(self, :#{method_name})
|
|
220
|
+
self.send(:#{method_name}, *args, &blk)
|
|
221
|
+
end
|
|
222
|
+
EOM
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def mark_invoked!(method_name)
|
|
226
|
+
backup_method!(method_name)
|
|
227
|
+
@klass.class_eval(<<-EOM, __FILE__, __LINE__)
|
|
228
|
+
def #{method_name}(*args, &blk)
|
|
229
|
+
method_name = :#{method_name}
|
|
230
|
+
current_instance = self
|
|
231
|
+
invoked_instance = self.class.__recorder.instance_that_received(method_name)
|
|
232
|
+
raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by \#{self.inspect} but has already been received by \#{invoked_instance}"
|
|
233
|
+
end
|
|
234
|
+
EOM
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def any_instance
|
|
239
|
+
RSpec::Mocks::space.add(self)
|
|
240
|
+
__recorder
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def rspec_verify
|
|
244
|
+
__recorder.verify
|
|
245
|
+
super
|
|
246
|
+
ensure
|
|
247
|
+
__recorder.stop_all_observation!
|
|
248
|
+
@__recorder = nil
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def __recorder
|
|
252
|
+
@__recorder ||= AnyInstance::Recorder.new(self)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
@@ -5,7 +5,7 @@ module RSpec
|
|
|
5
5
|
|
|
6
6
|
def initialize(*args, &block)
|
|
7
7
|
@args = args
|
|
8
|
-
@
|
|
8
|
+
@block = args.empty? ? block : nil
|
|
9
9
|
@match_any_args = false
|
|
10
10
|
@matchers = nil
|
|
11
11
|
|
|
@@ -26,19 +26,20 @@ module RSpec
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def is_matcher?(obj)
|
|
29
|
-
!
|
|
29
|
+
!null_object?(obj) & obj.respond_to?(:matches?) & obj.respond_to?(:description)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def
|
|
33
|
-
|
|
32
|
+
def args_match?(*args)
|
|
33
|
+
match_any_args? || block_passes?(*args) || matchers_match?(*args)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
private
|
|
37
|
+
def null_object?(obj)
|
|
38
|
+
obj.respond_to?(:__rspec_double_acting_as_null_object?) && obj.__rspec_double_acting_as_null_object?
|
|
38
39
|
end
|
|
39
40
|
|
|
40
|
-
def
|
|
41
|
-
@
|
|
41
|
+
def block_passes?(*args)
|
|
42
|
+
@block.call(*args) if @block
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
def matchers_match?(*args)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
module Marshal
|
|
2
|
-
|
|
3
2
|
class << self
|
|
4
|
-
|
|
5
3
|
def dump_with_mocks(*args)
|
|
6
4
|
object = args.shift
|
|
7
5
|
return dump_without_mocks(*args.unshift(object)) unless object.instance_variable_defined?(:@mock_proxy)
|
|
@@ -21,7 +19,5 @@ module Marshal
|
|
|
21
19
|
alias_method :dump_without_mocks, :dump
|
|
22
20
|
undef_method :dump
|
|
23
21
|
alias_method :dump, :dump_with_mocks
|
|
24
|
-
|
|
25
22
|
end
|
|
26
|
-
|
|
27
23
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
if defined?(Psych) && Psych.respond_to?(:dump)
|
|
2
|
+
module Psych
|
|
3
|
+
class << self
|
|
4
|
+
def dump_with_mocks(object, *args)
|
|
5
|
+
return dump_without_mocks(object, *args) unless object.instance_variable_defined?(:@mock_proxy)
|
|
6
|
+
|
|
7
|
+
mp = object.instance_variable_get(:@mock_proxy)
|
|
8
|
+
return dump_without_mocks(object, *args) unless mp.is_a?(::RSpec::Mocks::Proxy)
|
|
9
|
+
|
|
10
|
+
object.send(:remove_instance_variable, :@mock_proxy)
|
|
11
|
+
|
|
12
|
+
begin
|
|
13
|
+
dump_without_mocks(object, *args)
|
|
14
|
+
ensure
|
|
15
|
+
object.instance_variable_set(:@mock_proxy,mp)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
alias_method :dump_without_mocks, :dump
|
|
20
|
+
alias_method :dump, :dump_with_mocks
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -249,22 +249,25 @@ module RSpec
|
|
|
249
249
|
end
|
|
250
250
|
|
|
251
251
|
def with(*args, &block)
|
|
252
|
-
@return_block = block if block_given?
|
|
252
|
+
@return_block = block if block_given? unless args.empty?
|
|
253
253
|
@args_expectation = ArgumentExpectation.new(*args, &block)
|
|
254
254
|
self
|
|
255
255
|
end
|
|
256
256
|
|
|
257
|
-
def exactly(n)
|
|
257
|
+
def exactly(n, &block)
|
|
258
|
+
@method_block = block if block
|
|
258
259
|
set_expected_received_count :exactly, n
|
|
259
260
|
self
|
|
260
261
|
end
|
|
261
262
|
|
|
262
|
-
def at_least(n)
|
|
263
|
+
def at_least(n, &block)
|
|
264
|
+
@method_block = block if block
|
|
263
265
|
set_expected_received_count :at_least, n
|
|
264
266
|
self
|
|
265
267
|
end
|
|
266
268
|
|
|
267
|
-
def at_most(n)
|
|
269
|
+
def at_most(n, &block)
|
|
270
|
+
@method_block = block if block
|
|
268
271
|
set_expected_received_count :at_most, n
|
|
269
272
|
self
|
|
270
273
|
end
|
data/lib/rspec/mocks/methods.rb
CHANGED
|
@@ -39,7 +39,7 @@ module RSpec
|
|
|
39
39
|
if chain.length > 1
|
|
40
40
|
if matching_stub = __mock_proxy.__send__(:find_matching_method_stub, chain[0].to_sym)
|
|
41
41
|
chain.shift
|
|
42
|
-
matching_stub.invoke.stub_chain(*chain)
|
|
42
|
+
matching_stub.invoke.stub_chain(*chain, &blk)
|
|
43
43
|
else
|
|
44
44
|
next_in_chain = Object.new
|
|
45
45
|
stub(chain.shift) { next_in_chain }
|
data/lib/rspec/mocks/mock.rb
CHANGED
|
@@ -41,10 +41,10 @@ module RSpec
|
|
|
41
41
|
private
|
|
42
42
|
|
|
43
43
|
def method_missing(sym, *args, &block)
|
|
44
|
+
raise NoMethodError if sym == :to_ary
|
|
44
45
|
__mock_proxy.record_message_received(sym, *args, &block)
|
|
45
46
|
begin
|
|
46
|
-
|
|
47
|
-
super
|
|
47
|
+
__mock_proxy.null_object? ? self : super
|
|
48
48
|
rescue NameError
|
|
49
49
|
__mock_proxy.raise_unexpected_message_error(sym, *args)
|
|
50
50
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'rspec/mocks/extensions/marshal'
|
|
2
|
+
require 'rspec/mocks/extensions/psych' if defined?(::Psych)
|
|
2
3
|
|
|
3
4
|
module RSpec
|
|
4
5
|
module Mocks
|
|
@@ -8,14 +9,15 @@ module RSpec
|
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
module YAML
|
|
11
|
-
def to_yaml(
|
|
12
|
-
return
|
|
12
|
+
def to_yaml(options = {})
|
|
13
|
+
return nil if defined?(::Psych) && options.respond_to?(:[]) && options[:nodump]
|
|
14
|
+
return super(options) unless instance_variable_defined?(:@mock_proxy)
|
|
13
15
|
|
|
14
16
|
mp = @mock_proxy
|
|
15
17
|
remove_instance_variable(:@mock_proxy)
|
|
16
18
|
|
|
17
19
|
begin
|
|
18
|
-
super(
|
|
20
|
+
super(options)
|
|
19
21
|
ensure
|
|
20
22
|
@mock_proxy = mp
|
|
21
23
|
end
|
data/lib/rspec/mocks/space.rb
CHANGED
data/lib/rspec/mocks/version.rb
CHANGED
data/lib/rspec/mocks.rb
CHANGED
|
@@ -177,7 +177,8 @@ module RSpec
|
|
|
177
177
|
attr_accessor :space
|
|
178
178
|
|
|
179
179
|
def setup(includer)
|
|
180
|
-
Object.class_eval { include RSpec::Mocks::Methods }
|
|
180
|
+
Object.class_eval { include RSpec::Mocks::Methods }
|
|
181
|
+
Class.class_eval { include RSpec::Mocks::AnyInstance }
|
|
181
182
|
(class << includer; self; end).class_eval do
|
|
182
183
|
include RSpec::Mocks::ExampleMethods
|
|
183
184
|
end
|