rspec-mocks 2.5.0 → 2.6.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +7 -0
- data/Gemfile +13 -12
- 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/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.rb +2 -1
- data/lib/rspec/mocks/any_instance.rb +246 -0
- 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/spec/rspec/mocks/and_yield_spec.rb +3 -3
- data/spec/rspec/mocks/any_instance_spec.rb +578 -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 +32 -15
- 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,45 @@
|
|
1
|
+
Feature: double handling to_ary
|
2
|
+
|
3
|
+
Ruby implicitly sends to_ary to any objects in an Array when the array
|
4
|
+
receives `flatten`:
|
5
|
+
|
6
|
+
[obj].flatten # => obj.to_ary
|
7
|
+
|
8
|
+
To support this, an RSpec double will raise a NoMethodError when it receives
|
9
|
+
`to_ary`, unless that method is explicitly stubbed.
|
10
|
+
|
11
|
+
Scenario: double receiving to_ary
|
12
|
+
Given a file named "example.rb" with:
|
13
|
+
"""
|
14
|
+
describe "a double receiving to_ary" do
|
15
|
+
shared_examples "to_ary" do
|
16
|
+
it "returns nil" do
|
17
|
+
expect do
|
18
|
+
obj.to_ary.should be_nil
|
19
|
+
end.to raise_error(NoMethodError)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "can be overridden with a stub" do
|
23
|
+
obj.stub(:to_ary) { :non_nil_value }
|
24
|
+
obj.to_ary.should be(:non_nil_value)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "supports Array#flatten" do
|
28
|
+
obj = double('foo')
|
29
|
+
[obj].flatten.should eq([obj])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "double as_null_object" do
|
34
|
+
let(:obj) { double('obj').as_null_object }
|
35
|
+
include_examples "to_ary"
|
36
|
+
end
|
37
|
+
|
38
|
+
context "double without as_null_object" do
|
39
|
+
let(:obj) { double('obj') }
|
40
|
+
include_examples "to_ary"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
"""
|
44
|
+
When I run `rspec example.rb`
|
45
|
+
Then the examples should all pass
|
@@ -42,7 +42,7 @@ Feature: configure any test framework to use rspec-mocks
|
|
42
42
|
puts example.respond_to?(:stub)
|
43
43
|
"""
|
44
44
|
|
45
|
-
When I run
|
45
|
+
When I run `ruby foo.rb`
|
46
46
|
Then the output should contain "true"
|
47
47
|
But the output should not contain "false"
|
48
48
|
|
@@ -60,7 +60,7 @@ Feature: configure any test framework to use rspec-mocks
|
|
60
60
|
puts obj.respond_to?(:stub)
|
61
61
|
"""
|
62
62
|
|
63
|
-
When I run
|
63
|
+
When I run `ruby foo.rb`
|
64
64
|
Then the output should contain "true"
|
65
65
|
But the output should not contain "false"
|
66
66
|
|
@@ -76,7 +76,7 @@ Feature: configure any test framework to use rspec-mocks
|
|
76
76
|
puts obj.respond_to?(:stub)
|
77
77
|
"""
|
78
78
|
|
79
|
-
When I run
|
79
|
+
When I run `ruby foo.rb`
|
80
80
|
Then the output should contain "false"
|
81
81
|
But the output should not contain "true"
|
82
82
|
|
@@ -13,7 +13,7 @@ Feature: standalone
|
|
13
13
|
greeter.stub(:say_hi) { "Hello!" }
|
14
14
|
puts greeter.say_hi
|
15
15
|
"""
|
16
|
-
When I run
|
16
|
+
When I run `ruby example.rb`
|
17
17
|
Then the output should contain "Hello!"
|
18
18
|
|
19
19
|
Scenario: message expectation outside rspec
|
@@ -26,7 +26,7 @@ Feature: standalone
|
|
26
26
|
|
27
27
|
RSpec::Mocks.verify
|
28
28
|
"""
|
29
|
-
When I run
|
29
|
+
When I run `ruby example.rb`
|
30
30
|
Then the output should contain "say_hi(any args) (RSpec::Mocks::MockExpectationError)"
|
31
31
|
Then the output should contain "expected: 1 time"
|
32
32
|
Then the output should contain "received: 0 times"
|
data/features/support/env.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
|
@@ -0,0 +1,246 @@
|
|
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_observing!
|
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 observe!(method_name)
|
205
|
+
@observed_methods << method_name
|
206
|
+
backup_method!(method_name)
|
207
|
+
@klass.class_eval(<<-EOM, __FILE__, __LINE__)
|
208
|
+
def #{method_name}(*args, &blk)
|
209
|
+
self.class.__recorder.playback!(self, :#{method_name})
|
210
|
+
self.send(:#{method_name}, *args, &blk)
|
211
|
+
end
|
212
|
+
EOM
|
213
|
+
end
|
214
|
+
|
215
|
+
def mark_invoked!(method_name)
|
216
|
+
backup_method!(method_name)
|
217
|
+
@klass.class_eval(<<-EOM, __FILE__, __LINE__)
|
218
|
+
def #{method_name}(*args, &blk)
|
219
|
+
method_name = :#{method_name}
|
220
|
+
current_instance = self
|
221
|
+
invoked_instance = self.class.__recorder.instance_that_received(method_name)
|
222
|
+
raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by \#{self.inspect} but has already been received by \#{invoked_instance}"
|
223
|
+
end
|
224
|
+
EOM
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def any_instance
|
229
|
+
RSpec::Mocks::space.add(self)
|
230
|
+
__recorder
|
231
|
+
end
|
232
|
+
|
233
|
+
def rspec_verify
|
234
|
+
__recorder.verify
|
235
|
+
super
|
236
|
+
ensure
|
237
|
+
__recorder.stop_observing!
|
238
|
+
@__recorder = nil
|
239
|
+
end
|
240
|
+
|
241
|
+
def __recorder
|
242
|
+
@__recorder ||= AnyInstance::Recorder.new(self)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
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
|