Skip to content

Commit 4214427

Browse files
committed
more consistent setting of multiple expectations
When calling expects/stubs with a hash (method_names_vs_return_values), only the last expectation would get returned. Any further 'expectation setting' methods chained to that call would, therefore, get called only on the last expectation. This seems arbitrary, and neither evident nor intuitive. We now 'extract' the expectation setting methods into a _virtual_ interface, 'implemented' by both Expectation and ExpectationSetting, and return ExpectationSetting instead of Expectation from the expects/stubs methods. This allows us to pass any further expectation setting method calls on to _each_ of the multiple expectations, rather than just the _last_ (or some other arbitrary) single expectation.
1 parent 2840411 commit 4214427

File tree

5 files changed

+64
-25
lines changed

5 files changed

+64
-25
lines changed

Diff for: lib/mocha/expectation_setting.rb

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module Mocha
2+
class ExpectationSetting
3+
EXPECTATION_SETTING_METHODS = %w[
4+
times twice once never at_least at_least_once at_most at_most_once
5+
with yields multiple_yields returns raises throws then when in_sequence
6+
].map(&:to_sym).freeze
7+
8+
attr_reader :expectations
9+
10+
def initialize(expectations)
11+
@expectations = expectations
12+
end
13+
14+
def respond_to_missing?(symbol, _include_all = false)
15+
EXPECTATION_SETTING_METHODS.include?(symbol) || super
16+
end
17+
18+
def method_missing(symbol, *arguments, &block)
19+
if EXPECTATION_SETTING_METHODS.include?(symbol)
20+
ExpectationSetting.new(@expectations.map { |e| e.send(symbol, *arguments, &block) })
21+
else
22+
super
23+
end
24+
end
25+
end
26+
end

Diff for: lib/mocha/mock.rb

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'mocha/singleton_class'
22
require 'mocha/expectation'
33
require 'mocha/expectation_list'
4+
require 'mocha/expectation_setting'
45
require 'mocha/invocation'
56
require 'mocha/names'
67
require 'mocha/receivers'
@@ -137,7 +138,7 @@ def expects(method_name_or_hash, backtrace = nil)
137138
# object.stubs(:stubbed_method_one).returns(:result_one)
138139
# object.stubs(:stubbed_method_two).returns(:result_two)
139140
def stubs(method_name_or_hash, backtrace = nil)
140-
anticipates(method_name_or_hash, backtrace) { |expectation| expectation.at_least(0) }
141+
anticipates(method_name_or_hash, backtrace).at_least(0)
141142
end
142143

143144
# Removes the specified stubbed methods (added by calls to {#expects} or {#stubs}) and all expectations associated with them.
@@ -356,17 +357,16 @@ def any_expectations?
356357
end
357358

358359
# @private
359-
def anticipates(method_name_or_hash, backtrace = nil, object = Mock.new(@mockery), &block)
360-
Array(method_name_or_hash).map do |*args|
360+
def anticipates(method_name_or_hash, backtrace = nil, object = Mock.new(@mockery))
361+
ExpectationSetting.new(Array(method_name_or_hash).map do |*args|
361362
args = args.flatten
362363
method_name = args.shift
363364
Mockery.instance.stub_method(object, method_name) unless object.is_a?(Mock)
364365
ensure_method_not_already_defined(method_name)
365366
expectation = Expectation.new(self, method_name, backtrace)
366367
expectation.returns(args.shift) unless args.empty?
367-
yield expectation if block
368368
@expectations.add(expectation)
369-
end.last
369+
end)
370370
end
371371
end
372372
end

Diff for: lib/mocha/object_methods.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def expects(expected_methods_vs_return_values)
108108
#
109109
# @see Mock#stubs
110110
def stubs(stubbed_methods_vs_return_values)
111-
anticipates(stubbed_methods_vs_return_values) { |expectation| expectation.at_least(0) }
111+
anticipates(stubbed_methods_vs_return_values).at_least(0)
112112
end
113113

114114
# Removes the specified stubbed methods (added by calls to {#expects} or {#stubs}) and all expectations associated with them.
@@ -143,11 +143,11 @@ def unstub(*method_names)
143143

144144
private
145145

146-
def anticipates(expected_methods_vs_return_values, &block)
146+
def anticipates(expected_methods_vs_return_values)
147147
if frozen?
148148
raise StubbingError.new("can't stub method on frozen object: #{mocha_inspect}", caller)
149149
end
150-
mocha.anticipates(expected_methods_vs_return_values, caller, self, &block)
150+
mocha.anticipates(expected_methods_vs_return_values, caller, self)
151151
end
152152
end
153153
end

Diff for: test/acceptance/expectations_on_multiple_methods_test.rb

+27
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,31 @@ def my_instance_method_2
5252
end
5353
assert_passed(test_result)
5454
end
55+
56+
def test_should_configure_expectations_for_multiple_methods
57+
instance = Class.new do
58+
def my_instance_method_1
59+
:original_return_value_1
60+
end
61+
62+
def my_instance_method_2
63+
:original_return_value_2
64+
end
65+
end.new
66+
test_result = run_as_test do
67+
instance.stubs(
68+
:my_instance_method_1 => :new_return_value_1,
69+
:my_instance_method_2 => :new_return_value_2
70+
).at_least(2)
71+
assert_equal :new_return_value_1, instance.my_instance_method_1
72+
assert_equal :new_return_value_2, instance.my_instance_method_2
73+
end
74+
assert_failed(test_result)
75+
assert_equal [
76+
'not all expectations were satisfied',
77+
'unsatisfied expectations:',
78+
"- expected at least twice, invoked once: #{instance.mocha_inspect}.my_instance_method_2(any_parameters)",
79+
"- expected at least twice, invoked once: #{instance.mocha_inspect}.my_instance_method_1(any_parameters)"
80+
], test_result.failure_message_lines
81+
end
5582
end

Diff for: test/unit/mock_test.rb

+3-17
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def test_should_build_and_store_expectations
2121
mock = build_mock
2222
expectation = mock.expects(:method1)
2323
assert_not_nil expectation
24-
assert_equal [expectation], mock.__expectations__.to_a
24+
assert_equal expectation.expectations, mock.__expectations__.to_a
2525
end
2626

2727
def test_should_not_stub_everything_by_default
@@ -86,28 +86,14 @@ def test_should_create_and_add_expectations
8686
mock = build_mock
8787
expectation1 = mock.expects(:method1)
8888
expectation2 = mock.expects(:method2)
89-
assert_equal [expectation1, expectation2].to_set, mock.__expectations__.to_set
90-
end
91-
92-
def test_should_pass_backtrace_into_expectation
93-
mock = build_mock
94-
backtrace = Object.new
95-
expectation = mock.expects(:method1, backtrace)
96-
assert_equal backtrace, expectation.backtrace
97-
end
98-
99-
def test_should_pass_backtrace_into_stub
100-
mock = build_mock
101-
backtrace = Object.new
102-
stub = mock.stubs(:method1, backtrace)
103-
assert_equal backtrace, stub.backtrace
89+
assert_equal (expectation1.expectations + expectation2.expectations).to_set, mock.__expectations__.to_set
10490
end
10591

10692
def test_should_create_and_add_stubs
10793
mock = build_mock
10894
stub1 = mock.stubs(:method1)
10995
stub2 = mock.stubs(:method2)
110-
assert_equal [stub1, stub2].to_set, mock.__expectations__.to_set
96+
assert_equal (stub1.expectations + stub2.expectations).to_set, mock.__expectations__.to_set
11197
end
11298

11399
def test_should_invoke_expectation_and_return_result

0 commit comments

Comments
 (0)