Skip to content

Commit 94ea6cc

Browse files
committed
feat: implement explicit block support
1 parent c5c6c73 commit 94ea6cc

18 files changed

+115
-131
lines changed

flexmock.gemspec

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ spec = Gem::Specification.new do |s|
1010
interface is simple, it is very flexible.
1111
}
1212

13-
s.required_ruby_version = ">= 2.2"
13+
s.required_ruby_version = ">= 3.0"
1414

1515
s.license = 'MIT'
1616

@@ -19,7 +19,6 @@ spec = Gem::Specification.new do |s|
1919
s.add_development_dependency 'minitest', ">= 5.0"
2020
s.add_development_dependency 'rake'
2121
s.add_development_dependency 'simplecov', '>= 0.11.0'
22-
s.add_development_dependency 'coveralls'
2322

2423
#### Which files are to be included in this gem? Everything! (Except CVS directories.)
2524

lib/flexmock/argument_matchers.rb

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,19 +83,4 @@ def inspect
8383
"ducktype(#{@methods.map{|m| m.inspect}.join(',')})"
8484
end
8585
end
86-
87-
####################################################################
88-
# Match objects that implement all the methods in +methods+.
89-
class OptionalProcMatcher
90-
def initialize
91-
end
92-
def ===(target)
93-
ArgumentMatching.missing?(target) || Proc === target
94-
end
95-
def inspect
96-
"optional_proc"
97-
end
98-
end
99-
OPTIONAL_PROC_MATCHER = OptionalProcMatcher.new
100-
10186
end

lib/flexmock/argument_matching.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ module ArgumentMatching
44

55
MISSING_ARG = Object.new
66

7-
def all_match?(expected_args, expected_kw, actual_args, actual_kw)
7+
def all_match?(expected_args, expected_kw, expected_block, actual_args, actual_kw, actual_block)
88
all_match_args?(expected_args, actual_args) &&
9-
all_match_kw?(expected_kw, actual_kw)
9+
all_match_kw?(expected_kw, actual_kw) &&
10+
all_match_block?(expected_block, actual_block)
1011
end
1112

1213
def all_match_args?(expected_args, actual_args)
@@ -42,6 +43,12 @@ def all_match_kw?(expected_kw, actual_kw)
4243
true
4344
end
4445

46+
def all_match_block?(expected_block, actual_block)
47+
return true if expected_block.nil?
48+
49+
!(expected_block ^ actual_block)
50+
end
51+
4552
# Does the expected argument match the corresponding actual value.
4653
def match?(expected, actual)
4754
expected === actual ||

lib/flexmock/argument_types.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@ def hsh(hash)
4747
def ducktype(*methods)
4848
DuckMatcher.new(methods)
4949
end
50-
51-
def optional_proc
52-
OPTIONAL_PROC_MATCHER
53-
end
5450
end
5551
extend ArgumentTypes
5652

lib/flexmock/call_record.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,24 @@
1111

1212
class FlexMock
1313

14-
CallRecord = Struct.new(:method_name, :args, :kw, :block_given, :expectation) do
14+
CallRecord = Struct.new(:method_name, :args, :kw, :block, :expectation) do
1515
def matches?(sym, expected_args, expected_kw, options)
1616
method_name == sym &&
17-
ArgumentMatching.all_match?(expected_args, expected_kw, args, kw) &&
17+
ArgumentMatching.all_match_args?(expected_args, args) &&
18+
ArgumentMatching.all_match_kw?(expected_kw, kw) &&
1819
matches_block?(options[:with_block])
1920
end
2021

2122
private
2223

2324
def matches_block?(block_option)
2425
block_option.nil? ||
25-
(block_option && block_given) ||
26-
(!block_option && !block_given)
26+
(block_option && block) ||
27+
(!block_option && !block)
28+
end
29+
30+
def block_given
31+
block
2732
end
2833
end
2934

lib/flexmock/core.rb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,13 @@ def by_default
140140
def method_missing(sym, *args, **kw, &block)
141141
FlexMock.verify_mocking_allowed!
142142

143-
enhanced_args = block_given? ? args + [block] : args
144-
call_record = CallRecord.new(sym, enhanced_args, kw, block_given?)
143+
call_record = CallRecord.new(sym, args, kw, block)
145144
@calls << call_record
146145
flexmock_wrap do
147146
if flexmock_closed?
148147
FlexMock.undefined
149148
elsif exp = flexmock_expectations_for(sym)
150-
exp.call(enhanced_args, kw, call_record)
149+
exp.call(args, kw, block, call_record)
151150
elsif @base_class && @base_class.flexmock_defined?(sym)
152151
FlexMock.undefined
153152
elsif @ignore_missing
@@ -167,9 +166,9 @@ def respond_to?(sym, *args)
167166
end
168167

169168
# Find the mock expectation for method sym and arguments.
170-
def flexmock_find_expectation(method_name, *args, **kw) # :nodoc:
169+
def flexmock_find_expectation(method_name, *args, **kw, &block) # :nodoc:
171170
if exp = flexmock_expectations_for(method_name)
172-
exp.find_expectation(args, kw)
171+
exp.find_expectation(args, kw, block)
173172
end
174173
end
175174

@@ -214,7 +213,7 @@ def flexmock_invoke_original(method_name, args, kw = {})
214213
# Override the built-in +method+ to include the mocked methods.
215214
def method(method_name)
216215
if (expectations = flexmock_expectations_for(method_name))
217-
->(*args, **kw) { expectations.call(args, kw) }
216+
->(*args, **kw, &block) { expectations.call(args, kw, block) }
218217
else
219218
super
220219
end

lib/flexmock/expectation.rb

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def initialize(mock, sym, location)
4141
@count_validators = []
4242
@signature_validator = SignatureValidator.new(self)
4343
@count_validator_class = ExactCountValidator
44+
@with_block = nil
4445
@actual_count = 0
4546
@return_value = nil
4647
@return_queue = []
@@ -86,48 +87,47 @@ def validate_eligible
8687
FlexMock.framework_adapter.check(e.message) { false }
8788
end
8889

89-
def validate_signature(args, kw)
90-
@signature_validator.validate(args, kw)
90+
def validate_signature(args, kw, block)
91+
@signature_validator.validate(args, kw, block)
9192
rescue SignatureValidator::ValidationFailed => e
9293
FlexMock.framework_adapter.check(e.message) { false }
9394
end
9495

9596
# Verify the current call with the given arguments matches the
9697
# expectations recorded in this object.
97-
def verify_call(args, kw)
98+
def verify_call(args, kw, block)
9899
validate_eligible
99100
validate_order
100-
validate_signature(args, kw)
101+
validate_signature(args, kw, block)
101102
@actual_count += 1
102-
perform_yielding(args)
103-
return_value(args, kw)
103+
perform_yielding(block)
104+
return_value(args, kw, block)
104105
end
105106

106107
# Public return value (odd name to avoid accidental use as a
107108
# constraint).
108-
def _return_value(args, kw) # :nodoc:
109-
return_value(args, kw)
109+
def _return_value(args, kw, block) # :nodoc:
110+
return_value(args, kw, block)
110111
end
111112

112113
# Find the return value for this expectation. (private version)
113-
def return_value(args, kw)
114+
def return_value(args, kw, block)
114115
case @return_queue.size
115116
when 0
116-
block = lambda { |*a| @return_value }
117+
ret_block = lambda { |*, **| @return_value }
117118
when 1
118-
block = @return_queue.first
119+
ret_block = @return_queue.first
119120
else
120-
block = @return_queue.shift
121+
ret_block = @return_queue.shift
121122
end
122-
block.call(*args, **kw)
123+
ret_block.call(*args, **kw, &block)
123124
end
124125
private :return_value
125126

126127
# Yield stored values to any blocks given.
127-
def perform_yielding(args)
128+
def perform_yielding(block)
128129
@return_value = nil
129130
unless @yield_queue.empty?
130-
block = args.last
131131
values = (@yield_queue.size == 1) ? @yield_queue.first : @yield_queue.shift
132132
if block && block.respond_to?(:call)
133133
values.each do |v|
@@ -173,8 +173,8 @@ def flexmock_verify
173173

174174
# Does the argument list match this expectation's argument
175175
# specification.
176-
def match_args(args, kw)
177-
ArgumentMatching.all_match?(@expected_args, @expected_kw, args, kw)
176+
def match_args(args, kw, block)
177+
ArgumentMatching.all_match?(@expected_args, @expected_kw, @expected_block, args, kw, block)
178178
end
179179

180180
# Declare that the method should expect the given argument list.
@@ -218,6 +218,23 @@ def with_kw_args(kw)
218218
self
219219
end
220220

221+
# Declare that the call should have a block
222+
def with_block
223+
@expected_block = true
224+
self
225+
end
226+
227+
# Declare that the call should have a block
228+
def with_no_block
229+
@expected_block = false
230+
self
231+
end
232+
233+
def with_optional_block
234+
@expected_block = nil
235+
self
236+
end
237+
221238
# Validate general parameters on the call signature
222239
def with_signature(
223240
required_arguments: 0, optional_arguments: 0, splat: false,

lib/flexmock/expectation_builder.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def create_demeter_chain(mock, names)
9292
names.each do |name|
9393
exp = mock.flexmock_find_expectation(name)
9494
if exp
95-
next_mock = exp._return_value([], {})
95+
next_mock = exp._return_value([], {}, nil)
9696
check_proper_mock(next_mock, name)
9797
else
9898
next_mock = container.flexmock("demeter_#{name}")

lib/flexmock/expectation_director.rb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ def initialize(sym)
3535
# but at least we will get a good failure message). Finally,
3636
# check for expectations that don't have any argument matching
3737
# criteria.
38-
def call(args, kw, call_record=nil)
39-
exp = find_expectation(args, kw)
38+
def call(args, kw, block, call_record=nil)
39+
exp = find_expectation(args, kw, block)
4040
call_record.expectation = exp if call_record
4141
FlexMock.check(
4242
proc { "no matching handler found for " +
4343
FlexMock.format_call(@sym, args, kw) +
4444
"\nDefined expectations:\n " +
4545
@expectations.map(&:description).join("\n ") }
4646
) { !exp.nil? }
47-
returned_value = exp.verify_call(args, kw)
47+
returned_value = exp.verify_call(args, kw, block)
4848
returned_value
4949
end
5050

@@ -54,11 +54,11 @@ def <<(expectation)
5454
end
5555

5656
# Find an expectation matching the given arguments.
57-
def find_expectation(args, kw) # :nodoc:
57+
def find_expectation(args, kw, block) # :nodoc:
5858
if @expectations.empty?
59-
find_expectation_in(@defaults, args, kw)
59+
find_expectation_in(@defaults, args, kw, block)
6060
else
61-
find_expectation_in(@expectations, args, kw)
61+
find_expectation_in(@expectations, args, kw, block)
6262
end
6363
end
6464

@@ -84,9 +84,9 @@ def defaultify_expectation(exp) # :nodoc:
8484

8585
private
8686

87-
def find_expectation_in(expectations, args, kw)
88-
expectations.find { |e| e.match_args(args, kw) && e.eligible? } ||
89-
expectations.find { |e| e.match_args(args, kw) }
87+
def find_expectation_in(expectations, args, kw, block)
88+
expectations.find { |e| e.match_args(args, kw, block) && e.eligible? } ||
89+
expectations.find { |e| e.match_args(args, kw, block) }
9090
end
9191
end
9292

lib/flexmock/partial_mock.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,8 @@ def flexmock_define_expectation(location, *args, **kw)
234234
end
235235
end
236236

237-
def flexmock_find_expectation(*args, **kw)
238-
@mock.flexmock_find_expectation(*args, **kw)
237+
def flexmock_find_expectation(*args, **kw, &block)
238+
@mock.flexmock_find_expectation(*args, **kw, &block)
239239
end
240240

241241
def add_mock_method(method_name)

0 commit comments

Comments
 (0)