Rework how expectations and matchers work together

This commit is contained in:
Michael Miller 2018-09-28 11:47:42 -06:00
parent 4948fe0c6f
commit 3035273e9a
12 changed files with 118 additions and 50 deletions

View file

@ -9,7 +9,7 @@ module Spectator::DSL
end
macro expect(actual)
::Spectator::Expectation.new({{actual.stringify}}, {{actual}})
::Spectator::Expectations::ValueExpectationPartial.new({{actual.stringify}}, {{actual}})
end
end
end

View file

@ -1,31 +0,0 @@
require "./matchers/matcher"
module Spectator
class Expectation(T)
getter actual : T
protected def label : String
@label.empty? ? @actual.to_s : @label
end
protected def initialize(@label : String, @actual : T)
end
def to(matcher : Matchers::Matcher)
unless matcher.match?(self)
raise ExpectationFailed.new(matcher.message(self))
end
end
def to_not(matcher : Matchers::Matcher)
if matcher.match?(self)
raise ExpectationFailed.new(matcher.negated_message(self))
end
end
@[AlwaysInline]
def not_to(matcher : Matchers::Matcher)
to_not(matcher)
end
end
end

View file

@ -0,0 +1,7 @@
require "./expectations/*"
module Spectator
# Namespace that contains all expectations, partials, and handling of them.
module Expectations
end
end

View file

@ -0,0 +1,4 @@
module Spectator::Expectations
abstract class Expectation
end
end

View file

@ -0,0 +1,22 @@
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.
# This can also cover a block's behavior.
# Sub-types of this class are returned by the `DSL::ExampleDSL.expect` call.
abstract class 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
# Creates the base of the partial.
private def initialize(@label)
end
end
end

View file

@ -0,0 +1,26 @@
module Spectator::Expectations
# Tracks the expectations and their outcomes in an example.
# A single instance of this class should be associated with one example.
class ExpectationRegistry
private getter? raise_on_failure : Bool
private def initialize(@raise_on_failure = true)
end
def report(expectation : Expectation) : Nil
raise NotImplementedError.new("ExpectationRegistry#report")
end
def self.current : ExpectationRegistry
raise NotImplementedError.new("ExpectationRegistry.current")
end
def self.start(example : Example) : ExpectationRegistry
raise NotImplementedError.new("ExpectationRegistry.start")
end
def self.finish : Nil # TODO: Define return type.
raise NotImplementedError.new("ExpectationRegistry.finish")
end
end
end

View file

@ -0,0 +1,6 @@
require "./expectation"
module Spectator::Expectations
class ValueExpectation < Expectation
end
end

View file

@ -0,0 +1,25 @@
require "./expectation_partial"
module Spectator::Expectations
class ValueExpectationPartial(ActualType) < ExpectationPartial
getter actual
protected def initialize(label : String, @actual : ActualType)
super(label)
end
def to(matcher : Matchers::ValueMatcher(ExpectedType)) : Nil forall ExpectedType
raise NotImplementedError.new("ValueExpectationPartial#to")
end
def to_not(matcher : Matchers::ValueMatcher(ExpectedType)) : Nil forall ExpectedType
raise NotImplementedError.new("ValueExpectationPartial#to_not")
end
# ditto
@[AlwaysInline]
def not_to(matcher : Matchers::ValueMatcher(ExpectedType)) : Nil forall ExpectedType
to_not(matcher)
end
end
end

View file

@ -7,6 +7,7 @@
# First the sub-modules.
require "./internals"
require "./dsl"
require "./expectations"
require "./matchers"
require "./formatters"
@ -18,7 +19,6 @@ require "./pending_example"
require "./example_hooks"
require "./example_group"
require "./expectation"
require "./expectation_failed"
require "./test_results"
require "./runner"

View file

@ -1,21 +1,17 @@
require "./matcher"
require "./value_matcher"
module Spectator::Matchers
class EqualityMatcher(T) < Matcher
def initialize(label, @expected : T)
super(label)
class EqualityMatcher(ExpectedType) < ValueMatcher(ExpectedType)
def match?(partial : ValueExpectationPartial(ActualType)) : Bool forall ActualType
partial.actual == expected
end
def match?(expectation : Expectation)
expectation.actual == @expected
def message(partial : ValueExpectationPartial(ActualType)) : String forall ActualType
"Expected #{partial.label} to equal #{label} (using ==)"
end
def message(expectation : Expectation) : String
"Expected #{expectation.label} to equal #{label} (using ==)"
end
def negated_message(expectation : Expectation) : String
"Expected #{expectation.label} to not equal #{label} (using ==)"
def negated_message(partial : ValueExpectationPartial(ActualType)) : String forall ActualType
"Expected #{partial.label} to not equal #{label} (using ==)"
end
end
end

View file

@ -2,11 +2,7 @@ module Spectator::Matchers
abstract class Matcher
private getter label : String
private def initialize(@label : String)
private def initialize(@label)
end
abstract def match?(expectation : Expectation)
abstract def message(expectation : Expectation) : String
abstract def negated_message(expectation : Expectation) : String
end
end

View file

@ -0,0 +1,17 @@
require "./matcher"
module Spectator::Matchers
abstract class ValueMatcher(ExpectedType) < Matcher
private getter expected
def initialize(label : String, @expected : ExpectedType)
super(label)
end
abstract def match?(partial : ValueExpectationPartial(ActualType)) : Bool forall ActualType
abstract def message(partial : ValueExpectationPartial(ActualType)) : String forall ActualType
abstract def negated_message(partial : ValueExpectationPartial(ActualType)) : String forall ActualType
end
end