Rewrite ExpectationPartial and remove sub-types

The partial now contains the actual and source.
It also calls the correct match method on the matcher and constructs an 
expectation (which needs to be updated).
This commit is contained in:
Michael Miller 2019-07-31 20:11:30 -06:00
parent 42b916bdf7
commit 3a7dc7299a
4 changed files with 61 additions and 86 deletions

View file

@ -1,25 +0,0 @@
require "./expectation_partial"
module Spectator::Expectations
# Expectation partial variation that operates on a block.
struct BlockExpectationPartial(ReturnType) < ExpectationPartial
# Actual value produced by calling the block.
def actual
@block.call
end
# Creates the expectation partial.
# The label should be a string representation of the block.
# The block is stored for later use.
def initialize(@block : Proc(ReturnType), label, source_file, source_line)
super(label, source_file, source_line)
end
# Creates the expectation partial.
# The label is generated by calling to_s on the block.
# The block is stored for later use.
def initialize(@block : Proc(ReturnType), source_file, source_line)
super("<Proc>", source_file, source_line)
end
end
end

View file

@ -1,39 +1,34 @@
require "../matchers/match_data"
require "../source"
require "./actual"
module Spectator::Expectations module Spectator::Expectations
# Base class for all expectation partials. # Stores part of an expectation (obviously).
# An "expectation partial" stores part of an expectation (obviously). # The part of the expectation this type covers is the actual value and source.
# The part of the expectation this class covers is the actual value.
# This can also cover a block's behavior. # This can also cover a block's behavior.
# Sub-types of this class are returned by the `DSL::ExampleDSL.expect` call. struct ExpectationPartial
abstract struct ExpectationPartial # The actual value being tested.
# User-friendly string displayed for the actual expression being tested. # This also contains its label.
# For instance, in the expectation: getter actual : Actual
# ```
# expect(foo).to eq(bar)
# ```
# This property will be "foo".
# It will be the literal string "foo",
# and not the actual value of the foo.
getter label : String
# Source file the expectation originated from. # Location where this expectation was defined.
getter source_file : String getter source : Source
# Line number in the source file the expectation originated from. # Creates the partial.
getter source_line : Int32 def initialize(@actual, @source)
# Creates the base of the partial.
private def initialize(@label, @source_file, @source_line)
end end
# Asserts that some criteria defined by the matcher is satisfied. # Asserts that some criteria defined by the matcher is satisfied.
def to(matcher) : Nil def to(matcher) : Nil
report(eval(matcher)) match_data = matcher.match(@actual)
report(match_data)
end end
# Asserts that some criteria defined by the matcher is not satisfied. # Asserts that some criteria defined by the matcher is not satisfied.
# This is effectively the opposite of `#to`. # This is effectively the opposite of `#to`.
def to_not(matcher) : Nil def to_not(matcher) : Nil
report(eval(matcher, true)) match_data = matcher.negated_match(@actual)
report(match_data)
end end
# ditto # ditto
@ -42,14 +37,9 @@ module Spectator::Expectations
to_not(matcher) to_not(matcher)
end end
# Evaluates the expectation and returns it.
private def eval(matcher, negated = false)
match_data = matcher.match(self)
Expectation.new(match_data, negated)
end
# Reports an expectation to the current harness. # Reports an expectation to the current harness.
private def report(expectation : Expectation) private def report(match_data : Matchers::MatchData)
expectation = Expectation.new(match_data, @source)
Internals::Harness.current.report_expectation(expectation) Internals::Harness.current.report_expectation(expectation)
end end
end end

View file

@ -1,24 +0,0 @@
require "./expectation_partial"
module Spectator::Expectations
# Expectation partial variation that operates on a value.
struct ValueExpectationPartial(ActualType) < ExpectationPartial
# Actual value produced by the test.
# This is the value passed to the `Spectator::DSL::ExampleDSL#expect` macro.
getter actual
# Creates the expectation partial.
# The label should be a string representation of the actual value.
# The actual value is stored for later use.
def initialize(@actual : ActualType, label, source_file, source_line)
super(label, source_file, source_line)
end
# Creates the expectation partial.
# The label is generated by calling `to_s` on the actual value.
# The actual value is stored for later use.
def initialize(@actual : ActualType, source_file, source_line)
super(@actual.to_s, source_file, source_line)
end
end
end

View file

@ -5,13 +5,47 @@ module Spectator::Matchers
# A matcher looks at something produced by the SUT # A matcher looks at something produced by the SUT
# and evaluates whether it is correct or not. # and evaluates whether it is correct or not.
abstract struct Matcher abstract struct Matcher
# Textual representation of what the matcher expects. # Short text about the matcher's purpose.
# This shouldn't be used in the conditional logic, # This explains what condition satisfies the matcher.
# but for verbose output to help the end-user. # The description is used when the one-liner syntax is used.
abstract def label : String # ```
# it { is_expected.to do_something }
# ```
# The phrasing should be such that it reads "it ___."
abstract def description : String
# Determines whether the matcher is satisfied with the value given to it. # Message displayed when the matcher isn't satisifed.
# True is returned if the match was successful, false otherwise. # This is only called when `#matches?` returns false.
abstract def match(partial) : MatchData abstract def failure_message : String
# Message displayed when the matcher isn't satisifed and is negated.
# This is only called when `#does_not_match?` returns false.
#
# A default implementation of this method is provided,
# which causes compilation to fail.
# If the matcher supports negation, it must override this method.
def failure_message_when_negated : String
{% raise "Negation with #{@type.name} is not supported."}
end
# Checks whether the matcher is satisifed.
private abstract def match?(actual) : Bool
# If the expectation is negated, then this method is called instead of `#match?`.
# The default implementation of this method is to invert the result of `#match?`.
# If the matcher requires custom handling of negated matches,
# then this method should be overriden.
# Remember to override `#failure_message_when_negated` as well.
private def does_not_match?(actual) : Bool
!matches?(actual)
end
def match(actual)
matched = match?(actual)
end
def negated_match(actual)
matched = does_not_match?(actual)
end
end end
end end