Add relative change matcher

This commit is contained in:
Michael Miller 2019-08-11 23:08:45 -06:00
parent c19f442e6c
commit 4e15487a0f
2 changed files with 73 additions and 0 deletions

View file

@ -7,6 +7,7 @@ require "./successful_match_data"
module Spectator::Matchers module Spectator::Matchers
# Matcher that tests whether an expression changed. # Matcher that tests whether an expression changed.
struct ChangeMatcher(ExpressionType) < Matcher struct ChangeMatcher(ExpressionType) < Matcher
# The expression that is expected to (not) change.
private getter expression private getter expression
# Creates a new change matcher. # Creates a new change matcher.
@ -57,6 +58,21 @@ module Spectator::Matchers
ChangeToMatcher.new(@expression, value) ChangeToMatcher.new(@expression, value)
end end
# Specifies that t he resulting value must be some amount different.
def by(amount : T) forall T
ChangeRelativeMatcher.new(@expression, "by #{amount}") { |before, after| amount == after - before }
end
# Specifies that the resulting value must be at least some amount different.
def by_at_least(minimum : T) forall T
ChangeRelativeMatcher.new(@expression, "by at least #{minimum}") { |before, after| minimum <= after - before }
end
# Specifies that the resulting value must be at most some amount different.
def by_at_most(maximum : T) forall T
ChangeRelativeMatcher.new(@expression, "by at most #{maximum}") { |before, after| maximum >= after - before }
end
# Performs the change and reports the before and after values. # Performs the change and reports the before and after values.
private def change(actual) private def change(actual)
before = expression.value # Retrieve the expression's initial value. before = expression.value # Retrieve the expression's initial value.

View file

@ -0,0 +1,57 @@
require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
module Spectator::Matchers
# Matcher that tests whether an expression changed by an amount.
struct ChangeRelativeMatcher(ExpressionType) < Matcher
# The expression that is expected to (not) change.
private getter expression
# Creates a new change matcher.
def initialize(@expression : TestBlock(ExpressionType), @relativity : String,
&evaluator : ExpressionType, ExpressionType -> Bool)
@evaluator = evaluator
end
# Short text about the matcher's purpose.
# This explains what condition satisfies the matcher.
# The description is used when the one-liner syntax is used.
def description
"changes #{expression.label} #{@relativity}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
before, after = change(actual)
if before == after
FailedMatchData.new("#{actual.label} did not change #{expression.label}",
before: before.inspect,
after: after.inspect
)
elsif @evaluator.call(before, after)
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual.label} did not change #{expression.label} #{@relativity}",
before: before.inspect,
after: after.inspect
)
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
{% raise "The `expect { }.to_not change { }.by_...()` syntax is not supported (ambiguous)." %}
end
# Performs the change and reports the before and after values.
private def change(actual)
before = expression.value # Retrieve the expression's initial value.
actual.value # Invoke action that might change the expression's value.
after = expression.value # Retrieve the expression's value again.
{before, after}
end
end
end