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
# Base class for all expectation partials.
# An "expectation partial" stores part of an expectation (obviously).
# The part of the expectation this class covers is the actual value.
# Stores part of an expectation (obviously).
# The part of the expectation this type covers is the actual value and source.
# This can also cover a block's behavior.
# Sub-types of this class are returned by the `DSL::ExampleDSL.expect` call.
abstract struct ExpectationPartial
# User-friendly string displayed for the actual expression being tested.
# For instance, in the expectation:
# ```
# 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
struct ExpectationPartial
# The actual value being tested.
# This also contains its label.
getter actual : Actual
# Source file the expectation originated from.
getter source_file : String
# Location where this expectation was defined.
getter source : Source
# Line number in the source file the expectation originated from.
getter source_line : Int32
# Creates the base of the partial.
private def initialize(@label, @source_file, @source_line)
# Creates the partial.
def initialize(@actual, @source)
end
# Asserts that some criteria defined by the matcher is satisfied.
def to(matcher) : Nil
report(eval(matcher))
match_data = matcher.match(@actual)
report(match_data)
end
# Asserts that some criteria defined by the matcher is not satisfied.
# This is effectively the opposite of `#to`.
def to_not(matcher) : Nil
report(eval(matcher, true))
match_data = matcher.negated_match(@actual)
report(match_data)
end
# ditto
@ -42,14 +37,9 @@ module Spectator::Expectations
to_not(matcher)
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.
private def report(expectation : Expectation)
private def report(match_data : Matchers::MatchData)
expectation = Expectation.new(match_data, @source)
Internals::Harness.current.report_expectation(expectation)
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
# and evaluates whether it is correct or not.
abstract struct Matcher
# Textual representation of what the matcher expects.
# This shouldn't be used in the conditional logic,
# but for verbose output to help the end-user.
abstract def label : String
# 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.
# ```
# 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.
# True is returned if the match was successful, false otherwise.
abstract def match(partial) : MatchData
# Message displayed when the matcher isn't satisifed.
# This is only called when `#matches?` returns false.
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