mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Change Assertions to Expectations
Start expectation rework.
This commit is contained in:
parent
a74957204b
commit
e09f5c960a
10 changed files with 120 additions and 299 deletions
|
@ -1,15 +0,0 @@
|
||||||
require "./block"
|
|
||||||
require "./expression"
|
|
||||||
|
|
||||||
module Spectator
|
|
||||||
class Assertion
|
|
||||||
struct Target(T)
|
|
||||||
@expression : Expression(T) | Block(T)
|
|
||||||
@source : Source?
|
|
||||||
|
|
||||||
def initialize(@expression : Expression(T) | Block(T), @source)
|
|
||||||
puts "TARGET: #{@expression} @ #{@source}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +1,7 @@
|
||||||
# require "./dsl/*"
|
# require "./dsl/*"
|
||||||
require "./dsl/assertions"
|
|
||||||
require "./dsl/builder"
|
require "./dsl/builder"
|
||||||
require "./dsl/examples"
|
require "./dsl/examples"
|
||||||
|
require "./dsl/expectations"
|
||||||
require "./dsl/groups"
|
require "./dsl/groups"
|
||||||
require "./dsl/hooks"
|
require "./dsl/hooks"
|
||||||
require "./dsl/top"
|
require "./dsl/top"
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
require "../assertion"
|
|
||||||
require "../assertion_failed"
|
require "../assertion_failed"
|
||||||
require "../block"
|
require "../block"
|
||||||
|
require "../expectation"
|
||||||
require "../source"
|
require "../source"
|
||||||
require "../value"
|
require "../value"
|
||||||
|
|
||||||
module Spectator::DSL
|
module Spectator::DSL
|
||||||
# Methods and macros for asserting that conditions are met.
|
# Methods and macros for asserting that conditions are met.
|
||||||
module Assertions
|
module Expectations
|
||||||
# Immediately fail the current test.
|
# Immediately fail the current test.
|
||||||
# A reason can be specified with *message*.
|
# A reason can be specified with *message*.
|
||||||
def fail(message = "Example failed", *, _file = __FILE__, _line = __LINE__)
|
def fail(message = "Example failed", *, _file = __FILE__, _line = __LINE__)
|
||||||
|
@ -53,7 +53,7 @@ module Spectator::DSL
|
||||||
|
|
||||||
%expression = ::Spectator::Value.new(%actual, {{actual.stringify}})
|
%expression = ::Spectator::Value.new(%actual, {{actual.stringify}})
|
||||||
%source = ::Spectator::Source.new({{actual.filename}}, {{actual.line_number}})
|
%source = ::Spectator::Source.new({{actual.filename}}, {{actual.line_number}})
|
||||||
::Spectator::Assertion::Target.new(%expression, %source)
|
::Spectator::Expectation::Target.new(%expression, %source)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Starts an expectation.
|
# Starts an expectation.
|
||||||
|
@ -105,7 +105,7 @@ module Spectator::DSL
|
||||||
{% end %}
|
{% end %}
|
||||||
|
|
||||||
%source = ::Spectator::Source.new({{block.filename}}, {{block.line_number}})
|
%source = ::Spectator::Source.new({{block.filename}}, {{block.line_number}})
|
||||||
::Spectator::Assertion::Target.new(%block, %source)
|
::Spectator::Expectation::Target.new(%block, %source)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Short-hand for expecting something of the subject.
|
# Short-hand for expecting something of the subject.
|
114
src/spectator/expectation.cr
Normal file
114
src/spectator/expectation.cr
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
require "./expression"
|
||||||
|
require "./source"
|
||||||
|
|
||||||
|
module Spectator
|
||||||
|
# Result of evaluating a matcher on a target.
|
||||||
|
# Contains information about the match,
|
||||||
|
# such as whether it was successful and a description of the operation.
|
||||||
|
struct Expectation
|
||||||
|
# Location of the expectation in source code.
|
||||||
|
# This can be nil if the source isn't capturable,
|
||||||
|
# for instance using the *should* syntax or dynamically created expectations.
|
||||||
|
getter source : Source?
|
||||||
|
|
||||||
|
# Creates the expectation.
|
||||||
|
# The *match_data* comes from the result of calling `Matcher#match`.
|
||||||
|
# The *source* is the location of the expectation in source code, if available.
|
||||||
|
def initialize(@match_data : Matchers::MatchData, @source : Source? = nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Stores part of an expectation.
|
||||||
|
# This covers the actual value (or block) being inspected and its source.
|
||||||
|
# This is the type returned by an `expect` block in the DSL.
|
||||||
|
# It is not intended to be used directly, but instead by chaining methods.
|
||||||
|
# Typically `#to` and `#not_to` are used.
|
||||||
|
struct Target(T)
|
||||||
|
# Creates the expectation target.
|
||||||
|
# The *expression* is the actual value being tested and its label.
|
||||||
|
# The *source* is the location of where this expectation was defined.
|
||||||
|
def initialize(@expression : Expression(T), @source : Source)
|
||||||
|
puts "TARGET: #{@expression} @ #{@source}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Asserts that some criteria defined by the matcher is satisfied.
|
||||||
|
def to(matcher) : Nil
|
||||||
|
match_data = matcher.match(@expression)
|
||||||
|
report(match_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to(stub : Mocks::MethodStub) : Nil
|
||||||
|
Harness.current.mocks.expect(@expression.value, stub)
|
||||||
|
value = TestValue.new(stub.name, stub.to_s)
|
||||||
|
matcher = Matchers::ReceiveMatcher.new(value, stub.arguments?)
|
||||||
|
to_eventually(matcher)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to(stubs : Enumerable(Mocks::MethodStub)) : Nil
|
||||||
|
stubs.each { |stub| to(stub) }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Asserts that some criteria defined by the matcher is not satisfied.
|
||||||
|
# This is effectively the opposite of `#to`.
|
||||||
|
def to_not(matcher) : Nil
|
||||||
|
match_data = matcher.negated_match(@expression)
|
||||||
|
report(match_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_not(stub : Mocks::MethodStub) : Nil
|
||||||
|
value = TestValue.new(stub.name, stub.to_s)
|
||||||
|
matcher = Matchers::ReceiveMatcher.new(value, stub.arguments?)
|
||||||
|
to_never(matcher)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_not(stubs : Enumerable(Mocks::MethodStub)) : Nil
|
||||||
|
stubs.each { |stub| to_not(stub) }
|
||||||
|
end
|
||||||
|
|
||||||
|
# :ditto:
|
||||||
|
@[AlwaysInline]
|
||||||
|
def not_to(matcher) : Nil
|
||||||
|
to_not(matcher)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Asserts that some criteria defined by the matcher is eventually satisfied.
|
||||||
|
# The expectation is checked after the example finishes and all hooks have run.
|
||||||
|
def to_eventually(matcher) : Nil
|
||||||
|
Harness.current.defer { to(matcher) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_eventually(stub : Mocks::MethodStub) : Nil
|
||||||
|
to(stub)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_eventually(stubs : Enumerable(Mocks::MethodStub)) : Nil
|
||||||
|
to(stub)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Asserts that some criteria defined by the matcher is never satisfied.
|
||||||
|
# The expectation is checked after the example finishes and all hooks have run.
|
||||||
|
def to_never(matcher) : Nil
|
||||||
|
Harness.current.defer { to_not(matcher) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_never(stub : Mocks::MethodStub) : Nil
|
||||||
|
to_not(stub)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_never(stub : Enumerable(Mocks::MethodStub)) : Nil
|
||||||
|
to_not(stub)
|
||||||
|
end
|
||||||
|
|
||||||
|
# :ditto:
|
||||||
|
@[AlwaysInline]
|
||||||
|
def never_to(matcher) : Nil
|
||||||
|
to_never(matcher)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Reports an expectation to the current harness.
|
||||||
|
private def report(match_data : Matchers::MatchData)
|
||||||
|
expectation = Expectation.new(match_data, @source)
|
||||||
|
Harness.current.report_expectation(expectation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,7 +0,0 @@
|
||||||
require "./expectations/*"
|
|
||||||
|
|
||||||
module Spectator
|
|
||||||
# Namespace that contains all expectations, partials, and handling of them.
|
|
||||||
module Expectations
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,62 +0,0 @@
|
||||||
require "./expectation"
|
|
||||||
|
|
||||||
module Spectator::Expectations
|
|
||||||
# Collection of expectations from an example.
|
|
||||||
class ExampleExpectations
|
|
||||||
include Enumerable(Expectation)
|
|
||||||
|
|
||||||
# Creates the collection.
|
|
||||||
def initialize(@expectations : Array(Expectation))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Iterates through all expectations.
|
|
||||||
def each
|
|
||||||
@expectations.each do |expectation|
|
|
||||||
yield expectation
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a collection of only the satisfied expectations.
|
|
||||||
def satisfied : Enumerable(Expectation)
|
|
||||||
@expectations.select(&.satisfied?)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Iterates over only the satisfied expectations.
|
|
||||||
def each_satisfied
|
|
||||||
@expectations.each do |expectation|
|
|
||||||
yield expectation if expectation.satisfied?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a collection of only the unsatisfied expectations.
|
|
||||||
def unsatisfied : Enumerable(Expectation)
|
|
||||||
@expectations.reject(&.satisfied?)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Iterates over only the unsatisfied expectations.
|
|
||||||
def each_unsatisfied
|
|
||||||
@expectations.each do |expectation|
|
|
||||||
yield expectation unless expectation.satisfied?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Determines whether the example was successful
|
|
||||||
# based on if all expectations were satisfied.
|
|
||||||
def successful?
|
|
||||||
@expectations.all?(&.satisfied?)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Determines whether the example failed
|
|
||||||
# based on if any expectations were not satisfied.
|
|
||||||
def failed?
|
|
||||||
!successful?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates the JSON representation of the expectations.
|
|
||||||
def to_json(json : ::JSON::Builder)
|
|
||||||
json.array do
|
|
||||||
each &.to_json(json)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,74 +0,0 @@
|
||||||
require "../matchers/failed_match_data"
|
|
||||||
require "../matchers/match_data"
|
|
||||||
require "../source"
|
|
||||||
|
|
||||||
module Spectator::Expectations
|
|
||||||
# Result of evaluating a matcher on an expectation partial.
|
|
||||||
struct Expectation
|
|
||||||
# Location where this expectation was defined.
|
|
||||||
getter source : Source
|
|
||||||
|
|
||||||
# Creates the expectation.
|
|
||||||
def initialize(@match_data : Matchers::MatchData, @source : Source)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Indicates whether the matcher was satisified.
|
|
||||||
def satisfied?
|
|
||||||
@match_data.matched?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Indicates that the expectation was not satisified.
|
|
||||||
def failure?
|
|
||||||
!satisfied?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Description of why the match failed.
|
|
||||||
# If nil, then the match was successful.
|
|
||||||
def failure_message?
|
|
||||||
@match_data.as?(Matchers::FailedMatchData).try(&.failure_message)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Description of why the match failed.
|
|
||||||
def failure_message
|
|
||||||
failure_message?.not_nil!
|
|
||||||
end
|
|
||||||
|
|
||||||
# Additional information about the match, useful for debug.
|
|
||||||
# If nil, then the match was successful.
|
|
||||||
def values?
|
|
||||||
@match_data.as?(Matchers::FailedMatchData).try(&.values)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Additional information about the match, useful for debug.
|
|
||||||
def values
|
|
||||||
values?.not_nil!
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
|
||||||
@match_data.description
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates the JSON representation of the expectation.
|
|
||||||
def to_json(json : ::JSON::Builder)
|
|
||||||
json.object do
|
|
||||||
json.field("source") { @source.to_json(json) }
|
|
||||||
json.field("satisfied", satisfied?)
|
|
||||||
if (failed = @match_data.as?(Matchers::FailedMatchData))
|
|
||||||
failed_to_json(failed, json)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Adds failure information to a JSON structure.
|
|
||||||
private def failed_to_json(failed : Matchers::FailedMatchData, json : ::JSON::Builder)
|
|
||||||
json.field("failure", failed.failure_message)
|
|
||||||
json.field("values") do
|
|
||||||
json.object do
|
|
||||||
failed.values.each do |pair|
|
|
||||||
json.field(pair.first, pair.last)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,101 +0,0 @@
|
||||||
require "../matchers/match_data"
|
|
||||||
require "../source"
|
|
||||||
require "../test_expression"
|
|
||||||
|
|
||||||
module Spectator::Expectations
|
|
||||||
# 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.
|
|
||||||
struct ExpectationPartial(T)
|
|
||||||
# The actual value being tested.
|
|
||||||
# This also contains its label.
|
|
||||||
getter actual : TestExpression(T)
|
|
||||||
|
|
||||||
# Location where this expectation was defined.
|
|
||||||
getter source : Source
|
|
||||||
|
|
||||||
# Creates the partial.
|
|
||||||
def initialize(@actual : TestExpression(T), @source : Source)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Asserts that some criteria defined by the matcher is satisfied.
|
|
||||||
def to(matcher) : Nil
|
|
||||||
match_data = matcher.match(@actual)
|
|
||||||
report(match_data)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to(stub : Mocks::MethodStub) : Nil
|
|
||||||
Harness.current.mocks.expect(@actual.value, stub)
|
|
||||||
value = TestValue.new(stub.name, stub.to_s)
|
|
||||||
matcher = Matchers::ReceiveMatcher.new(value, stub.arguments?)
|
|
||||||
to_eventually(matcher)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to(stubs : Enumerable(Mocks::MethodStub)) : Nil
|
|
||||||
stubs.each { |stub| to(stub) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Asserts that some criteria defined by the matcher is not satisfied.
|
|
||||||
# This is effectively the opposite of `#to`.
|
|
||||||
def to_not(matcher) : Nil
|
|
||||||
match_data = matcher.negated_match(@actual)
|
|
||||||
report(match_data)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_not(stub : Mocks::MethodStub) : Nil
|
|
||||||
value = TestValue.new(stub.name, stub.to_s)
|
|
||||||
matcher = Matchers::ReceiveMatcher.new(value, stub.arguments?)
|
|
||||||
to_never(matcher)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_not(stubs : Enumerable(Mocks::MethodStub)) : Nil
|
|
||||||
stubs.each { |stub| to_not(stub) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# :ditto:
|
|
||||||
@[AlwaysInline]
|
|
||||||
def not_to(matcher) : Nil
|
|
||||||
to_not(matcher)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Asserts that some criteria defined by the matcher is eventually satisfied.
|
|
||||||
# The expectation is checked after the example finishes and all hooks have run.
|
|
||||||
def to_eventually(matcher) : Nil
|
|
||||||
Harness.current.defer { to(matcher) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_eventually(stub : Mocks::MethodStub) : Nil
|
|
||||||
to(stub)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_eventually(stubs : Enumerable(Mocks::MethodStub)) : Nil
|
|
||||||
to(stub)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Asserts that some criteria defined by the matcher is never satisfied.
|
|
||||||
# The expectation is checked after the example finishes and all hooks have run.
|
|
||||||
def to_never(matcher) : Nil
|
|
||||||
Harness.current.defer { to_not(matcher) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_never(stub : Mocks::MethodStub) : Nil
|
|
||||||
to_not(stub)
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_never(stub : Enumerable(Mocks::MethodStub)) : Nil
|
|
||||||
to_not(stub)
|
|
||||||
end
|
|
||||||
|
|
||||||
# :ditto:
|
|
||||||
@[AlwaysInline]
|
|
||||||
def never_to(matcher) : Nil
|
|
||||||
to_never(matcher)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Reports an expectation to the current harness.
|
|
||||||
private def report(match_data : Matchers::MatchData)
|
|
||||||
expectation = Expectation.new(match_data, @source)
|
|
||||||
Harness.current.report_expectation(expectation)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,34 +0,0 @@
|
||||||
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 ExpectationReporter
|
|
||||||
# All expectations are stored in this array.
|
|
||||||
# The initial capacity is set to one,
|
|
||||||
# as that is the typical (and recommended)
|
|
||||||
# number of expectations per example.
|
|
||||||
@expectations = Array(Expectation).new(1)
|
|
||||||
|
|
||||||
# Creates the reporter.
|
|
||||||
# When the *raise_on_failure* flag is set to true,
|
|
||||||
# which is the default, an exception will be raised
|
|
||||||
# on the first failure that is reported.
|
|
||||||
# To store failures and continue, set the flag to false.
|
|
||||||
def initialize(@raise_on_failure = true)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Stores the outcome of an expectation.
|
|
||||||
# If the raise on failure flag is set to true,
|
|
||||||
# then this method will raise an exception
|
|
||||||
# when a failing result is given.
|
|
||||||
def report(expectation : Expectation) : Nil
|
|
||||||
@expectations << expectation
|
|
||||||
raise ExpectationFailed.new(expectation) if !expectation.satisfied? && @raise_on_failure
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the reported expectations from the example.
|
|
||||||
# This should be run after the example has finished.
|
|
||||||
def expectations : ExampleExpectations
|
|
||||||
ExampleExpectations.new(@expectations)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -6,8 +6,8 @@ require "./dsl"
|
||||||
# This type is intentionally outside the `Spectator` module.
|
# This type is intentionally outside the `Spectator` module.
|
||||||
# The reason for this is to prevent name collision when using the DSL to define a spec.
|
# The reason for this is to prevent name collision when using the DSL to define a spec.
|
||||||
class SpectatorTestContext < SpectatorContext
|
class SpectatorTestContext < SpectatorContext
|
||||||
include ::Spectator::DSL::Assertions
|
|
||||||
include ::Spectator::DSL::Examples
|
include ::Spectator::DSL::Examples
|
||||||
|
include ::Spectator::DSL::Expectations
|
||||||
include ::Spectator::DSL::Groups
|
include ::Spectator::DSL::Groups
|
||||||
include ::Spectator::DSL::Hooks
|
include ::Spectator::DSL::Hooks
|
||||||
include ::Spectator::DSL::Values
|
include ::Spectator::DSL::Values
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue