Merge ReceiveArgumentsMatcher and ReceiveMatcher

Finally found the issue causing other matchers derived from
StandardMatcher to be "leaked" doubles and mocks indirectly.
The if-condition in ExpectationPartial#to and #to_not caused the matcher
to be given the StandardMatcher type instead of a union type.
This lead to really strange compilation errors and wasted a lot of
hours.
This commit is contained in:
Michael Miller 2019-11-16 15:14:38 -07:00
parent f0bfd8b6d4
commit d9d30c57d0
3 changed files with 15 additions and 125 deletions

View file

@ -27,11 +27,7 @@ module Spectator::Expectations
def to(stub : Mocks::MethodStub) : Nil def to(stub : Mocks::MethodStub) : Nil
Harness.current.mocks.expect(@actual.value, stub.name) Harness.current.mocks.expect(@actual.value, stub.name)
value = TestValue.new(stub.name, stub.to_s) value = TestValue.new(stub.name, stub.to_s)
matcher = if (arguments = stub.arguments?) matcher = Matchers::ReceiveMatcher.new(value, stub.arguments?)
Matchers::ReceiveArgumentsMatcher.new(value, arguments)
else
Matchers::ReceiveMatcher.new(value)
end
to_eventually(matcher) to_eventually(matcher)
end end
@ -44,11 +40,7 @@ module Spectator::Expectations
def to_not(stub : Mocks::MethodStub) : Nil def to_not(stub : Mocks::MethodStub) : Nil
value = TestValue.new(stub.name, stub.to_s) value = TestValue.new(stub.name, stub.to_s)
matcher = if (arguments = stub.arguments?) matcher = Matchers::ReceiveMatcher.new(value, stub.arguments?)
Matchers::ReceiveArgumentsMatcher.new(value, arguments)
else
Matchers::ReceiveMatcher.new(value)
end
to_never(matcher) to_never(matcher)
end end

View file

@ -1,104 +0,0 @@
require "../mocks"
require "./standard_matcher"
module Spectator::Matchers
struct ReceiveArgumentsMatcher < StandardMatcher
alias Range = ::Range(Int32, Int32) | ::Range(Nil, Int32) | ::Range(Int32, Nil)
def initialize(@expected : TestExpression(Symbol), @args : Mocks::Arguments, @range : Range? = nil)
end
def description : String
range = @range
"received message #{@expected.label} #{range ? "#{humanize_range(range)} time(s)" : "At least once"} with #{@args}"
end
def match?(actual : TestExpression(T)) : Bool forall T
calls = Harness.current.mocks.calls_for(actual.value, @expected.value).select { |call| @args === call.args }
if (range = @range)
range.includes?(calls.size)
else
!calls.empty?
end
end
def failure_message(actual : TestExpression(T)) : String forall T
range = @range
"#{actual.label} did not receive #{@expected.label} #{range ? "#{humanize_range(range)} time(s)" : "at least once"} with #{@args}"
end
def values(actual : TestExpression(T)) forall T
calls = Harness.current.mocks.calls_for(actual.value, @expected.value).select { |call| @args === call.args }
range = @range
{
expected: "#{range ? "#{humanize_range(range)} time(s)" : "At least once"} with #{@args}",
received: "#{calls.size} time(s)",
}
end
def once
ReceiveArgumentsMatcher.new(@expected, @args, (1..1))
end
def twice
ReceiveArgumentsMatcher.new(@expected, @args, (2..2))
end
def exactly(count)
Count.new(@expected, (count..count))
end
def at_least(count)
Count.new(@expected, (count..))
end
def at_most(count)
Count.new(@expected, (..count))
end
def at_least_once
at_least(1).times
end
def at_least_twice
at_least(2).times
end
def at_most_once
at_most(1).times
end
def at_most_twice
at_most(2).times
end
def humanize_range(range : Range)
if (min = range.begin)
if (max = range.end)
if min == max
min
else
"#{min} to #{max}"
end
else
"At least #{min}"
end
else
if (max = range.end)
"At most #{max}"
else
raise "Unexpected endless range"
end
end
end
private struct Count
def initialize(@expected : TestExpression(Symbol), @args : Mocks::Arguments, @range : Range)
end
def times
ReceiveArgumentsMatcher.new(@expected, @args, @range)
end
end
end
end

View file

@ -1,20 +1,21 @@
require "../mocks/double" require "../mocks"
require "./standard_matcher" require "./standard_matcher"
module Spectator::Matchers module Spectator::Matchers
struct ReceiveMatcher < StandardMatcher struct ReceiveMatcher < StandardMatcher
alias Range = ::Range(Int32, Int32) | ::Range(Nil, Int32) | ::Range(Int32, Nil) alias Range = ::Range(Int32, Int32) | ::Range(Nil, Int32) | ::Range(Int32, Nil)
def initialize(@expected : TestExpression(Symbol), @range : Range? = nil) def initialize(@expected : TestExpression(Symbol), @args : Mocks::Arguments? = nil, @range : Range? = nil)
end end
def description : String def description : String
range = @range range = @range
"received message #{@expected.label} #{range ? "#{humanize_range(range)} time(s)" : "at least once"} with any arguments" "received message #{@expected.label} #{range ? "#{humanize_range(range)} time(s)" : "At least once"} with #{@args || "any arguments"}"
end end
def match?(actual : TestExpression(T)) : Bool forall T def match?(actual : TestExpression(T)) : Bool forall T
calls = Harness.current.mocks.calls_for(actual.value, @expected.value) calls = Harness.current.mocks.calls_for(actual.value, @expected.value)
calls.select! { |call| @args === call.args } if @args
if (range = @range) if (range = @range)
range.includes?(calls.size) range.includes?(calls.size)
else else
@ -24,29 +25,30 @@ module Spectator::Matchers
def failure_message(actual : TestExpression(T)) : String forall T def failure_message(actual : TestExpression(T)) : String forall T
range = @range range = @range
"#{actual.label} did not receive #{@expected.label} #{range ? "#{humanize_range(range)} time(s)" : "at least once"}" "#{actual.label} did not receive #{@expected.label} #{range ? "#{humanize_range(range)} time(s)" : "at least once"} with #{@args || "any arguments"}"
end end
def values(actual : TestExpression(T)) forall T def values(actual : TestExpression(T)) forall T
calls = Harness.current.mocks.calls_for(actual.value, @expected.value) calls = Harness.current.mocks.calls_for(actual.value, @expected.value)
calls.select! { |call| @args === call.args } if @args
range = @range range = @range
{ {
expected: "#{range ? "#{humanize_range(range)} time(s)" : "At least once"} with any arguments", expected: "#{range ? "#{humanize_range(range)} time(s)" : "At least once"} with #{@args || "any arguments"}",
received: "#{calls.size} time(s) with any arguments", received: "#{calls.size} time(s)",
} }
end end
def with(*args, **opts) def with(*args, **opts)
args = Mocks::GenericArguments.new(args, opts) args = Mocks::GenericArguments.new(args, opts)
ReceiveArgumentsMatcher.new(@expected, args, @range) ReceiveMatcher.new(@expected, args, @range)
end end
def once def once
ReceiveMatcher.new(@expected, (1..1)) ReceiveMatcher.new(@expected, @args, (1..1))
end end
def twice def twice
ReceiveMatcher.new(@expected, (2..2)) ReceiveMatcher.new(@expected, @args, (2..2))
end end
def exactly(count) def exactly(count)
@ -98,11 +100,11 @@ module Spectator::Matchers
end end
private struct Count private struct Count
def initialize(@expected : TestExpression(Symbol), @range : Range) def initialize(@expected : TestExpression(Symbol), @args : Mocks::Arguments?, @range : Range)
end end
def times def times
ReceiveMatcher.new(@expected, @range) ReceiveMatcher.new(@expected, @args, @range)
end end
end end
end end