diff --git a/src/spectator/assertion.cr b/src/spectator/assertion.cr deleted file mode 100644 index d2813ce..0000000 --- a/src/spectator/assertion.cr +++ /dev/null @@ -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 diff --git a/src/spectator/dsl.cr b/src/spectator/dsl.cr index e206d1b..4fe0e43 100644 --- a/src/spectator/dsl.cr +++ b/src/spectator/dsl.cr @@ -1,7 +1,7 @@ # require "./dsl/*" -require "./dsl/assertions" require "./dsl/builder" require "./dsl/examples" +require "./dsl/expectations" require "./dsl/groups" require "./dsl/hooks" require "./dsl/top" diff --git a/src/spectator/dsl/assertions.cr b/src/spectator/dsl/expectations.cr similarity index 96% rename from src/spectator/dsl/assertions.cr rename to src/spectator/dsl/expectations.cr index d868d16..851b4f3 100644 --- a/src/spectator/dsl/assertions.cr +++ b/src/spectator/dsl/expectations.cr @@ -1,12 +1,12 @@ -require "../assertion" require "../assertion_failed" require "../block" +require "../expectation" require "../source" require "../value" module Spectator::DSL # Methods and macros for asserting that conditions are met. - module Assertions + module Expectations # Immediately fail the current test. # A reason can be specified with *message*. def fail(message = "Example failed", *, _file = __FILE__, _line = __LINE__) @@ -53,7 +53,7 @@ module Spectator::DSL %expression = ::Spectator::Value.new(%actual, {{actual.stringify}}) %source = ::Spectator::Source.new({{actual.filename}}, {{actual.line_number}}) - ::Spectator::Assertion::Target.new(%expression, %source) + ::Spectator::Expectation::Target.new(%expression, %source) end # Starts an expectation. @@ -105,7 +105,7 @@ module Spectator::DSL {% end %} %source = ::Spectator::Source.new({{block.filename}}, {{block.line_number}}) - ::Spectator::Assertion::Target.new(%block, %source) + ::Spectator::Expectation::Target.new(%block, %source) end # Short-hand for expecting something of the subject. diff --git a/src/spectator/expectation.cr b/src/spectator/expectation.cr new file mode 100644 index 0000000..fcb2961 --- /dev/null +++ b/src/spectator/expectation.cr @@ -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 diff --git a/src/spectator/expectations.cr b/src/spectator/expectations.cr deleted file mode 100644 index b835f10..0000000 --- a/src/spectator/expectations.cr +++ /dev/null @@ -1,7 +0,0 @@ -require "./expectations/*" - -module Spectator - # Namespace that contains all expectations, partials, and handling of them. - module Expectations - end -end diff --git a/src/spectator/expectations/example_expectations.cr b/src/spectator/expectations/example_expectations.cr deleted file mode 100644 index 31f1f98..0000000 --- a/src/spectator/expectations/example_expectations.cr +++ /dev/null @@ -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 diff --git a/src/spectator/expectations/expectation.cr b/src/spectator/expectations/expectation.cr deleted file mode 100644 index 72d3dbd..0000000 --- a/src/spectator/expectations/expectation.cr +++ /dev/null @@ -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 diff --git a/src/spectator/expectations/expectation_partial.cr b/src/spectator/expectations/expectation_partial.cr deleted file mode 100644 index c837c86..0000000 --- a/src/spectator/expectations/expectation_partial.cr +++ /dev/null @@ -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 diff --git a/src/spectator/expectations/expectation_reporter.cr b/src/spectator/expectations/expectation_reporter.cr deleted file mode 100644 index 2830b8e..0000000 --- a/src/spectator/expectations/expectation_reporter.cr +++ /dev/null @@ -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 diff --git a/src/spectator/test_context.cr b/src/spectator/test_context.cr index c6ca933..0595ee8 100644 --- a/src/spectator/test_context.cr +++ b/src/spectator/test_context.cr @@ -6,8 +6,8 @@ require "./dsl" # 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. class SpectatorTestContext < SpectatorContext - include ::Spectator::DSL::Assertions include ::Spectator::DSL::Examples + include ::Spectator::DSL::Expectations include ::Spectator::DSL::Groups include ::Spectator::DSL::Hooks include ::Spectator::DSL::Values