Compare commits
35 Commits
Author | SHA1 | Date |
---|---|---|
|
287758e6af | |
|
f39ceb8eba | |
|
9b1d400ee1 | |
|
edb20e5b2f | |
|
526a998e41 | |
|
556d4783bf | |
|
b5fbc96195 | |
|
5520999b6d | |
|
4a630b1ebf | |
|
d72895fe10 | |
|
04f151fddf | |
|
9cbb5d2cf7 | |
|
3852606b28 | |
|
726a2e1515 | |
|
5c08427ca0 | |
|
735122a94b | |
|
9ea5c261b1 | |
|
24a860ea11 | |
|
528ad7257d | |
|
7149ef7df5 | |
|
cb89589155 | |
|
a5e8f11e11 | |
|
abbd6ffd71 | |
|
fd372226ab | |
|
6a5e5b8f7a | |
|
4a0bfc1cb2 | |
|
d46698d81a | |
|
8c3900adcb | |
|
30602663fe | |
|
b8901f522a | |
|
c4bcf54b98 | |
|
acf810553a | |
|
faff2933e6 | |
|
0f8c46d6ef | |
|
7620f58fb8 |
|
@ -10,3 +10,5 @@
|
|||
|
||||
# Ignore JUnit output
|
||||
output.xml
|
||||
|
||||
/test.cr
|
||||
|
|
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.12.0] - 2024-02-03
|
||||
### Added
|
||||
- Added ability to use matchers for case equality. [#55](https://github.com/icy-arctic-fox/spectator/issues/55)
|
||||
- Added support for nested case equality when checking arguments with Array, Tuple, Hash, and NamedTuple.
|
||||
|
||||
### Fixed
|
||||
- Fixed some issues with the `be_within` matcher when used with expected and union types.
|
||||
|
||||
## [0.11.7] - 2023-10-16
|
||||
### Fixed
|
||||
- Fix memoized value (`let`) with a union type causing segfault. [#81](https://gitlab.com/arctic-fox/spectator/-/issues/81)
|
||||
|
||||
## [0.11.6] - 2023-01-26
|
||||
### Added
|
||||
- Added ability to cast types using the return value from expect/should statements with a type matcher.
|
||||
- Added support for string interpolation in context names/labels.
|
||||
|
||||
### Fixed
|
||||
- Fix invalid syntax (unterminated call) when recording calls to stubs with an un-named splat. [#51](https://github.com/icy-arctic-fox/spectator/issues/51)
|
||||
- Fix malformed method signature when using named splat with keyword arguments in mocked type. [#49](https://github.com/icy-arctic-fox/spectator/issues/49)
|
||||
|
||||
### Changed
|
||||
- Expectations using 'should' syntax report file and line where the 'should' keyword is instead of the test start.
|
||||
- Add non-captured block argument in preparation for Crystal 1.8.0.
|
||||
|
||||
## [0.11.5] - 2022-12-18
|
||||
### Added
|
||||
- Added support for mock modules and types that include mocked modules.
|
||||
|
@ -433,7 +458,10 @@ This has been changed so that it compiles and raises an error at runtime with a
|
|||
First version ready for public use.
|
||||
|
||||
|
||||
[Unreleased]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.5...master
|
||||
[Unreleased]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.12.0...master
|
||||
[0.12.0]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.7...v0.12.0
|
||||
[0.11.7]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.6...v0.11.7
|
||||
[0.11.6]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.5...v0.11.6
|
||||
[0.11.5]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.4...v0.11.5
|
||||
[0.11.4]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.3...v0.11.4
|
||||
[0.11.3]: https://gitlab.com/arctic-fox/spectator/-/compare/v0.11.2...v0.11.3
|
||||
|
|
|
@ -25,7 +25,7 @@ Add this to your application's `shard.yml`:
|
|||
development_dependencies:
|
||||
spectator:
|
||||
gitlab: arctic-fox/spectator
|
||||
version: ~> 0.11.0
|
||||
version: ~> 0.12.0
|
||||
```
|
||||
|
||||
Usage
|
||||
|
@ -287,7 +287,7 @@ Spectator.describe Driver do
|
|||
# Call the mock method.
|
||||
subject.do_something(interface, dbl)
|
||||
# Verify everything went okay.
|
||||
expect(interface).to have_received(:invoke).with(thing)
|
||||
expect(interface).to have_received(:invoke).with(dbl)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: spectator
|
||||
version: 0.11.5
|
||||
version: 0.12.0
|
||||
description: |
|
||||
Feature-rich testing framework for Crystal inspired by RSpec.
|
||||
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
require "../spec_helper"
|
||||
|
||||
Spectator.describe "Expect Type", :smoke do
|
||||
context "with expect syntax" do
|
||||
it "ensures a type is cast" do
|
||||
value = 42.as(String | Int32)
|
||||
expect(value).to be_a(String | Int32)
|
||||
expect(value).to compile_as(String | Int32)
|
||||
value = expect(value).to be_a(Int32)
|
||||
expect(value).to eq(42)
|
||||
expect(value).to be_a(Int32)
|
||||
expect(value).to compile_as(Int32)
|
||||
expect(value).to_not respond_to(:downcase)
|
||||
end
|
||||
|
||||
it "ensures a type is not nil" do
|
||||
value = 42.as(Int32?)
|
||||
expect(value).to be_a(Int32?)
|
||||
expect(value).to compile_as(Int32?)
|
||||
value = expect(value).to_not be_nil
|
||||
expect(value).to eq(42)
|
||||
expect(value).to be_a(Int32)
|
||||
expect(value).to compile_as(Int32)
|
||||
expect { value.not_nil! }.to_not raise_error(NilAssertionError)
|
||||
end
|
||||
|
||||
it "removes types from a union" do
|
||||
value = 42.as(String | Int32)
|
||||
expect(value).to be_a(String | Int32)
|
||||
expect(value).to compile_as(String | Int32)
|
||||
value = expect(value).to_not be_a(String)
|
||||
expect(value).to eq(42)
|
||||
expect(value).to be_a(Int32)
|
||||
expect(value).to compile_as(Int32)
|
||||
expect(value).to_not respond_to(:downcase)
|
||||
end
|
||||
end
|
||||
|
||||
context "with should syntax" do
|
||||
it "ensures a type is cast" do
|
||||
value = 42.as(String | Int32)
|
||||
value.should be_a(String | Int32)
|
||||
value = value.should be_a(Int32)
|
||||
value.should eq(42)
|
||||
value.should be_a(Int32)
|
||||
value.should compile_as(Int32)
|
||||
value.should_not respond_to(:downcase)
|
||||
end
|
||||
|
||||
it "ensures a type is not nil" do
|
||||
value = 42.as(Int32?)
|
||||
value.should be_a(Int32?)
|
||||
value = value.should_not be_nil
|
||||
value.should eq(42)
|
||||
value.should be_a(Int32)
|
||||
value.should compile_as(Int32)
|
||||
expect { value.not_nil! }.to_not raise_error(NilAssertionError)
|
||||
end
|
||||
|
||||
it "removes types from a union" do
|
||||
value = 42.as(String | Int32)
|
||||
value.should be_a(String | Int32)
|
||||
value = value.should_not be_a(String)
|
||||
value.should eq(42)
|
||||
value.should be_a(Int32)
|
||||
value.should compile_as(Int32)
|
||||
value.should_not respond_to(:downcase)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
require "../spec_helper"
|
||||
|
||||
Spectator.describe "Interpolated Label", :smoke do
|
||||
let(foo) { "example" }
|
||||
let(bar) { "context" }
|
||||
|
||||
it "interpolates #{foo} labels" do |example|
|
||||
expect(example.name).to eq("interpolates example labels")
|
||||
end
|
||||
|
||||
context "within a #{bar}" do
|
||||
let(foo) { "multiple" }
|
||||
|
||||
it "interpolates context labels" do |example|
|
||||
expect(example.group.name).to eq("within a context")
|
||||
end
|
||||
|
||||
it "interpolates #{foo} levels" do |example|
|
||||
expect(example.name).to eq("interpolates multiple levels")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
require "../spec_helper"
|
||||
|
||||
# https://github.com/icy-arctic-fox/spectator/issues/49
|
||||
Spectator.describe "GitHub Issue #49" do
|
||||
# mock File
|
||||
end
|
|
@ -0,0 +1,48 @@
|
|||
require "../spec_helper"
|
||||
|
||||
Spectator.describe "GitHub Issue #55" do
|
||||
GROUP_NAME = "CallCenter"
|
||||
|
||||
let(name) { "TimeTravel" }
|
||||
let(source) { "my.time.travel.experiment" }
|
||||
|
||||
class Analytics(T)
|
||||
property start_time = Time.local
|
||||
property end_time = Time.local
|
||||
|
||||
def initialize(@brain_talker : T)
|
||||
end
|
||||
|
||||
def instrument(*, name, source, &)
|
||||
@brain_talker.send(payload: {
|
||||
:group => GROUP_NAME,
|
||||
:name => name,
|
||||
:source => source,
|
||||
:start => start_time,
|
||||
:end => end_time,
|
||||
}, action: "analytics")
|
||||
end
|
||||
end
|
||||
|
||||
double(:brain_talker, send: nil)
|
||||
|
||||
let(brain_talker) { double(:brain_talker) }
|
||||
let(analytics) { Analytics.new(brain_talker) }
|
||||
|
||||
it "tracks the time it takes to run the block" do
|
||||
analytics.start_time = expected_start_time = Time.local
|
||||
expected_end_time = expected_start_time + 10.seconds
|
||||
analytics.end_time = expected_end_time + 0.5.seconds # Offset to ensure non-exact match.
|
||||
|
||||
analytics.instrument(name: name, source: source) do
|
||||
end
|
||||
|
||||
expect(brain_talker).to have_received(:send).with(payload: {
|
||||
:group => GROUP_NAME,
|
||||
:name => name,
|
||||
:source => source,
|
||||
:start => expected_start_time,
|
||||
:end => be_within(1.second).of(expected_end_time),
|
||||
}, action: "analytics")
|
||||
end
|
||||
end
|
|
@ -168,7 +168,7 @@ Spectator.describe "Double DSL", :smoke do
|
|||
|
||||
context "methods accepting blocks" do
|
||||
double(:test7) do
|
||||
stub def foo
|
||||
stub def foo(&)
|
||||
yield
|
||||
end
|
||||
|
||||
|
|
|
@ -40,17 +40,17 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
arg
|
||||
end
|
||||
|
||||
def method4 : Symbol
|
||||
def method4(&) : Symbol
|
||||
@_spectator_invocations << :method4
|
||||
yield
|
||||
end
|
||||
|
||||
def method5
|
||||
def method5(&)
|
||||
@_spectator_invocations << :method5
|
||||
yield.to_i
|
||||
end
|
||||
|
||||
def method6
|
||||
def method6(&)
|
||||
@_spectator_invocations << :method6
|
||||
yield
|
||||
end
|
||||
|
@ -60,7 +60,7 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
{arg, args, kwarg, kwargs}
|
||||
end
|
||||
|
||||
def method8(arg, *args, kwarg, **kwargs)
|
||||
def method8(arg, *args, kwarg, **kwargs, &)
|
||||
@_spectator_invocations << :method8
|
||||
yield
|
||||
{arg, args, kwarg, kwargs}
|
||||
|
@ -80,7 +80,7 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
"stubbed"
|
||||
end
|
||||
|
||||
stub def method4 : Symbol
|
||||
stub def method4(&) : Symbol
|
||||
yield
|
||||
:block
|
||||
end
|
||||
|
@ -258,12 +258,12 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
# NOTE: Abstract methods that yield must have yield functionality defined in the method.
|
||||
# This requires that yielding methods have a default implementation.
|
||||
# Just providing `&` in the arguments gets dropped by the compiler unless `yield` is in the method definition.
|
||||
stub def method5
|
||||
stub def method5(&)
|
||||
yield
|
||||
end
|
||||
|
||||
# NOTE: Another quirk where a default implementation must be provided because `&` is dropped.
|
||||
stub def method6 : Symbol
|
||||
stub def method6(&) : Symbol
|
||||
yield
|
||||
end
|
||||
|
||||
|
@ -381,12 +381,12 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
# NOTE: Abstract methods that yield must have yield functionality defined in the method.
|
||||
# This requires that yielding methods have a default implementation.
|
||||
# Just providing `&` in the arguments gets dropped by the compiler unless `yield` is in the method definition.
|
||||
stub def method5
|
||||
stub def method5(&)
|
||||
yield
|
||||
end
|
||||
|
||||
# NOTE: Another quirk where a default implementation must be provided because `&` is dropped.
|
||||
stub def method6 : Symbol
|
||||
stub def method6(&) : Symbol
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
@ -454,12 +454,12 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
# NOTE: Abstract methods that yield must have yield functionality defined in the method.
|
||||
# This requires that yielding methods have a default implementation.
|
||||
# Just providing `&` in the arguments gets dropped by the compiler unless `yield` is in the method definition.
|
||||
stub def method5
|
||||
stub def method5(&)
|
||||
yield
|
||||
end
|
||||
|
||||
# NOTE: Another quirk where a default implementation must be provided because `&` is dropped.
|
||||
stub def method6 : Symbol
|
||||
stub def method6(&) : Symbol
|
||||
yield
|
||||
end
|
||||
|
||||
|
@ -577,12 +577,12 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
# NOTE: Abstract methods that yield must have yield functionality defined in the method.
|
||||
# This requires that yielding methods have a default implementation.
|
||||
# Just providing `&` in the arguments gets dropped by the compiler unless `yield` is in the method definition.
|
||||
stub def method5
|
||||
stub def method5(&)
|
||||
yield
|
||||
end
|
||||
|
||||
# NOTE: Another quirk where a default implementation must be provided because `&` is dropped.
|
||||
stub def method6 : Symbol
|
||||
stub def method6(&) : Symbol
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
@ -620,11 +620,11 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
:original
|
||||
end
|
||||
|
||||
def method3
|
||||
def method3(&)
|
||||
yield
|
||||
end
|
||||
|
||||
def method4 : Int32
|
||||
def method4(&) : Int32
|
||||
yield.to_i
|
||||
end
|
||||
|
||||
|
@ -749,11 +749,11 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
:original
|
||||
end
|
||||
|
||||
def method3
|
||||
def method3(&)
|
||||
yield
|
||||
end
|
||||
|
||||
def method4 : Int32
|
||||
def method4(&) : Int32
|
||||
yield.to_i
|
||||
end
|
||||
|
||||
|
@ -1108,17 +1108,17 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
arg
|
||||
end
|
||||
|
||||
def method4 : Symbol
|
||||
def method4(&) : Symbol
|
||||
@_spectator_invocations << :method4
|
||||
yield
|
||||
end
|
||||
|
||||
def method5
|
||||
def method5(&)
|
||||
@_spectator_invocations << :method5
|
||||
yield.to_i
|
||||
end
|
||||
|
||||
def method6
|
||||
def method6(&)
|
||||
@_spectator_invocations << :method6
|
||||
yield
|
||||
end
|
||||
|
@ -1128,7 +1128,7 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
{arg, args, kwarg, kwargs}
|
||||
end
|
||||
|
||||
def method8(arg, *args, kwarg, **kwargs)
|
||||
def method8(arg, *args, kwarg, **kwargs, &)
|
||||
@_spectator_invocations << :method8
|
||||
yield
|
||||
{arg, args, kwarg, kwargs}
|
||||
|
@ -1148,7 +1148,7 @@ Spectator.describe "Mock DSL", :smoke do
|
|||
"stubbed"
|
||||
end
|
||||
|
||||
stub def method4 : Symbol
|
||||
stub def method4(&) : Symbol
|
||||
yield
|
||||
:block
|
||||
end
|
||||
|
|
|
@ -156,7 +156,7 @@ Spectator.describe "Null double DSL" do
|
|||
|
||||
context "methods accepting blocks" do
|
||||
double(:test7) do
|
||||
stub def foo
|
||||
stub def foo(&)
|
||||
yield
|
||||
end
|
||||
|
||||
|
|
|
@ -297,7 +297,7 @@ Spectator.describe Spectator::Double do
|
|||
arg
|
||||
end
|
||||
|
||||
stub def self.baz(arg)
|
||||
stub def self.baz(arg, &)
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
|
|
@ -364,7 +364,7 @@ Spectator.describe Spectator::Mock do
|
|||
arg
|
||||
end
|
||||
|
||||
def self.baz(arg)
|
||||
def self.baz(arg, &)
|
||||
yield
|
||||
end
|
||||
|
||||
|
@ -929,7 +929,7 @@ Spectator.describe Spectator::Mock do
|
|||
arg
|
||||
end
|
||||
|
||||
def self.baz(arg)
|
||||
def self.baz(arg, &)
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
|
|
@ -259,7 +259,7 @@ Spectator.describe Spectator::NullDouble do
|
|||
arg
|
||||
end
|
||||
|
||||
stub def self.baz(arg)
|
||||
stub def self.baz(arg, &)
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
# 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.
|
||||
abstract class SpectatorContext
|
||||
# Evaluates the contents of a block within the scope of the context.
|
||||
def eval(&)
|
||||
with self yield
|
||||
end
|
||||
|
||||
# Produces a dummy string to represent the context as a string.
|
||||
# This prevents the default behavior, which normally stringifies instance variables.
|
||||
# Due to the sheer amount of types Spectator can create
|
||||
|
|
|
@ -182,7 +182,7 @@ module Spectator::DSL
|
|||
# expect(false).to be_true
|
||||
# end
|
||||
# ```
|
||||
def aggregate_failures(label = nil)
|
||||
def aggregate_failures(label = nil, &)
|
||||
::Spectator::Harness.current.aggregate_failures(label) do
|
||||
yield
|
||||
end
|
||||
|
|
|
@ -137,7 +137,11 @@ module Spectator::DSL
|
|||
what.is_a?(NilLiteral) %}
|
||||
{{what}}
|
||||
{% elsif what.is_a?(StringInterpolation) %}
|
||||
{% raise "String interpolation isn't supported for example group names" %}
|
||||
{{@type.name}}.new.eval do
|
||||
{{what}}
|
||||
rescue e
|
||||
"<Failed to evaluate context label - #{e.class}: #{e}>"
|
||||
end
|
||||
{% else %}
|
||||
{{what.stringify}}
|
||||
{% end %}
|
||||
|
|
|
@ -11,7 +11,7 @@ module Spectator
|
|||
end
|
||||
|
||||
# Calls the `error` method on *visitor*.
|
||||
def accept(visitor)
|
||||
def accept(visitor, &)
|
||||
visitor.error(yield self)
|
||||
end
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ module Spectator
|
|||
|
||||
begin
|
||||
@result = Harness.run do
|
||||
if proc = @name_proc.as?(Proc(Example, String))
|
||||
if proc = @name_proc
|
||||
self.name = proc.call(self)
|
||||
end
|
||||
|
||||
|
@ -164,7 +164,7 @@ module Spectator
|
|||
# The context casted to an instance of *klass* is provided as a block argument.
|
||||
#
|
||||
# TODO: Benchmark compiler performance using this method versus client-side casting in a proc.
|
||||
protected def with_context(klass)
|
||||
protected def with_context(klass, &)
|
||||
context = klass.cast(@context)
|
||||
with context yield
|
||||
end
|
||||
|
@ -184,7 +184,7 @@ module Spectator
|
|||
end
|
||||
|
||||
# Yields this example and all parent groups.
|
||||
def ascend
|
||||
def ascend(&)
|
||||
node = self
|
||||
while node
|
||||
yield node
|
||||
|
@ -279,7 +279,7 @@ module Spectator
|
|||
# The block given to this method will be executed within the test context.
|
||||
#
|
||||
# TODO: Benchmark compiler performance using this method versus client-side casting in a proc.
|
||||
protected def with_context(klass)
|
||||
protected def with_context(klass, &)
|
||||
context = @example.cast_context(klass)
|
||||
with context yield
|
||||
end
|
||||
|
|
|
@ -87,7 +87,7 @@ module Spectator
|
|||
delegate size, unsafe_fetch, to: @nodes
|
||||
|
||||
# Yields this group and all parent groups.
|
||||
def ascend
|
||||
def ascend(&)
|
||||
group = self
|
||||
while group
|
||||
yield group
|
||||
|
|
|
@ -114,6 +114,21 @@ module Spectator
|
|||
report(match_data, message)
|
||||
end
|
||||
|
||||
# Asserts that some criteria defined by the matcher is satisfied.
|
||||
# Allows a custom message to be used.
|
||||
# Returns the expected value cast as the expected type, if the matcher is satisfied.
|
||||
def to(matcher : Matchers::TypeMatcher(U), message = nil) forall U
|
||||
match_data = matcher.match(@expression)
|
||||
value = @expression.value
|
||||
if report(match_data, message)
|
||||
return value if value.is_a?(U)
|
||||
|
||||
raise "Spectator bug: expected value should have cast to #{U}"
|
||||
else
|
||||
raise TypeCastError.new("#{@expression.label} is expected to be a #{U}, but was actually #{value.class}")
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that a method is not called before the example completes.
|
||||
@[AlwaysInline]
|
||||
def to_not(stub : Stub, message = nil) : Nil
|
||||
|
@ -136,6 +151,36 @@ module Spectator
|
|||
report(match_data, message)
|
||||
end
|
||||
|
||||
# Asserts that some criteria defined by the matcher is not satisfied.
|
||||
# Allows a custom message to be used.
|
||||
# Returns the expected value cast without the unexpected type, if the matcher is satisfied.
|
||||
def to_not(matcher : Matchers::TypeMatcher(U), message = nil) forall U
|
||||
match_data = matcher.negated_match(@expression)
|
||||
value = @expression.value
|
||||
if report(match_data, message)
|
||||
return value unless value.is_a?(U)
|
||||
|
||||
raise "Spectator bug: expected value should not be #{U}"
|
||||
else
|
||||
raise TypeCastError.new("#{@expression.label} is not expected to be a #{U}, but was actually #{value.class}")
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that some criteria defined by the matcher is not satisfied.
|
||||
# Allows a custom message to be used.
|
||||
# Returns the expected value cast as a non-nillable type, if the matcher is satisfied.
|
||||
def to_not(matcher : Matchers::NilMatcher, message = nil)
|
||||
match_data = matcher.negated_match(@expression)
|
||||
if report(match_data, message)
|
||||
value = @expression.value
|
||||
return value unless value.nil?
|
||||
|
||||
raise "Spectator bug: expected value should not be nil"
|
||||
else
|
||||
raise NilAssertionError.new("#{@expression.label} is not expected to be nil.")
|
||||
end
|
||||
end
|
||||
|
||||
# :ditto:
|
||||
@[AlwaysInline]
|
||||
def not_to(matcher, message = nil) : Nil
|
||||
|
|
|
@ -24,7 +24,7 @@ module Spectator
|
|||
end
|
||||
|
||||
# Calls the `failure` method on *visitor*.
|
||||
def accept(visitor)
|
||||
def accept(visitor, &)
|
||||
visitor.fail(yield self)
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ module Spectator::Formatting::Components
|
|||
end
|
||||
|
||||
# Increases the indent by the a specific *amount* for the duration of the block.
|
||||
private def indent(amount = INDENT)
|
||||
private def indent(amount = INDENT, &)
|
||||
@indent += amount
|
||||
yield
|
||||
@indent -= amount
|
||||
|
@ -23,7 +23,7 @@ module Spectator::Formatting::Components
|
|||
# The contents of the line should be generated by a block provided to this method.
|
||||
# Ensure that _only_ one line is produced by the block,
|
||||
# otherwise the indent will be lost.
|
||||
private def line(io)
|
||||
private def line(io, &)
|
||||
@indent.times { io << ' ' }
|
||||
yield
|
||||
io.puts
|
||||
|
|
|
@ -43,7 +43,7 @@ module Spectator
|
|||
# The value of `.current` is set to the harness for the duration of the test.
|
||||
# It will be reset after the test regardless of the outcome.
|
||||
# The result of running the test code will be returned.
|
||||
def self.run : Result
|
||||
def self.run(&) : Result
|
||||
with_harness do |harness|
|
||||
harness.run { yield }
|
||||
end
|
||||
|
@ -53,7 +53,7 @@ module Spectator
|
|||
# The `.current` harness is set to the new harness for the duration of the block.
|
||||
# `.current` is reset to the previous value (probably nil) afterwards, even if the block raises.
|
||||
# The result of the block is returned.
|
||||
private def self.with_harness
|
||||
private def self.with_harness(&)
|
||||
previous = @@current
|
||||
begin
|
||||
@@current = harness = new
|
||||
|
@ -70,7 +70,7 @@ module Spectator
|
|||
|
||||
# Runs test code and produces a result based on the outcome.
|
||||
# The test code should be called from within the block given to this method.
|
||||
def run : Result
|
||||
def run(&) : Result
|
||||
elapsed, error = capture { yield }
|
||||
elapsed2, error2 = capture { run_deferred }
|
||||
run_cleanup
|
||||
|
@ -106,7 +106,7 @@ module Spectator
|
|||
@cleanup << block
|
||||
end
|
||||
|
||||
def aggregate_failures(label = nil)
|
||||
def aggregate_failures(label = nil, &)
|
||||
previous = @aggregate
|
||||
@aggregate = aggregate = [] of Expectation
|
||||
begin
|
||||
|
@ -135,7 +135,7 @@ module Spectator
|
|||
|
||||
# Yields to run the test code and returns information about the outcome.
|
||||
# Returns a tuple with the elapsed time and an error if one occurred (otherwise nil).
|
||||
private def capture : Tuple(Time::Span, Exception?)
|
||||
private def capture(&) : Tuple(Time::Span, Exception?)
|
||||
error = nil
|
||||
elapsed = Time.measure do
|
||||
error = catch { yield }
|
||||
|
@ -146,7 +146,7 @@ module Spectator
|
|||
# Yields to run a block of code and captures exceptions.
|
||||
# If the block of code raises an error, the error is caught and returned.
|
||||
# If the block doesn't raise an error, then nil is returned.
|
||||
private def catch : Exception?
|
||||
private def catch(&) : Exception?
|
||||
yield
|
||||
rescue e
|
||||
e
|
||||
|
|
|
@ -97,7 +97,7 @@ module Spectator::Matchers
|
|||
|
||||
# Runs a block of code and returns the exception it threw.
|
||||
# If no exception was thrown, *nil* is returned.
|
||||
private def capture_exception
|
||||
private def capture_exception(&)
|
||||
exception = nil
|
||||
begin
|
||||
yield
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
require "../value"
|
||||
require "./match_data"
|
||||
|
||||
module Spectator::Matchers
|
||||
|
@ -22,6 +23,19 @@ module Spectator::Matchers
|
|||
# A successful match with `#match` should normally fail for this method, and vice-versa.
|
||||
abstract def negated_match(actual : Expression(T)) : MatchData forall T
|
||||
|
||||
# Compares a matcher against a value.
|
||||
# Enables composable matchers.
|
||||
def ===(actual : Expression(T)) : Bool
|
||||
match(actual).matched?
|
||||
end
|
||||
|
||||
# Compares a matcher against a value.
|
||||
# Enables composable matchers.
|
||||
def ===(other) : Bool
|
||||
expression = Value.new(other)
|
||||
match(expression).matched?
|
||||
end
|
||||
|
||||
private def match_data_description(actual : Expression(T)) : String forall T
|
||||
match_data_description(actual.label)
|
||||
end
|
||||
|
|
|
@ -29,7 +29,26 @@ module Spectator::Matchers
|
|||
|
||||
# Checks whether the matcher is satisfied with the expression given to it.
|
||||
private def match?(actual : Expression(T)) : Bool forall T
|
||||
expected.value.includes?(actual.value)
|
||||
actual_value = actual.value
|
||||
expected_value = expected.value
|
||||
if expected_value.is_a?(Range) && actual_value.is_a?(Comparable)
|
||||
return match_impl?(expected_value, actual_value)
|
||||
end
|
||||
return false unless actual_value.is_a?(Comparable(typeof(expected_value.begin)))
|
||||
expected_value.includes?(actual_value)
|
||||
end
|
||||
|
||||
private def match_impl?(expected_value : Range(B, E), actual_value : Comparable(B)) : Bool forall B, E
|
||||
expected_value.includes?(actual_value)
|
||||
end
|
||||
|
||||
private def match_impl?(expected_value : Range(B, E), actual_value : T) : Bool forall B, E, T
|
||||
return false unless actual_value.is_a?(B) || actual_value.is_a?(Comparable(B))
|
||||
expected_value.includes?(actual_value)
|
||||
end
|
||||
|
||||
private def match_impl?(expected_value : Range(Number, Number), actual_value : Number) : Bool
|
||||
expected_value.includes?(actual_value)
|
||||
end
|
||||
|
||||
# Message displayed when the matcher isn't satisfied.
|
||||
|
|
|
@ -7,7 +7,7 @@ module Spectator
|
|||
end
|
||||
|
||||
# Utility method for comparing two tuples considering special types.
|
||||
private def compare_tuples(a : Tuple, b : Tuple)
|
||||
private def compare_tuples(a : Tuple | Array, b : Tuple | Array)
|
||||
return false if a.size != b.size
|
||||
|
||||
a.zip(b) do |a_value, b_value|
|
||||
|
@ -18,14 +18,14 @@ module Spectator
|
|||
|
||||
# Utility method for comparing two tuples considering special types.
|
||||
# Supports nilable tuples (ideal for splats).
|
||||
private def compare_tuples(a : Tuple?, b : Tuple?)
|
||||
private def compare_tuples(a : Tuple? | Array?, b : Tuple? | Array?)
|
||||
return false if a.nil? ^ b.nil?
|
||||
|
||||
compare_tuples(a.not_nil!, b.not_nil!)
|
||||
end
|
||||
|
||||
# Utility method for comparing two named tuples ignoring order.
|
||||
private def compare_named_tuples(a : NamedTuple, b : NamedTuple)
|
||||
private def compare_named_tuples(a : NamedTuple | Hash, b : NamedTuple | Hash)
|
||||
a.each do |k, v1|
|
||||
v2 = b.fetch(k) { return false }
|
||||
return false unless compare_values(v1, v2)
|
||||
|
@ -45,11 +45,14 @@ module Spectator
|
|||
when Range
|
||||
# Ranges can only be matched against if their right side is comparable.
|
||||
# Ensure the right side is comparable, otherwise compare directly.
|
||||
if b.is_a?(Comparable(typeof(b)))
|
||||
a === b
|
||||
else
|
||||
a == b
|
||||
end
|
||||
return a === b if b.is_a?(Comparable(typeof(b)))
|
||||
a == b
|
||||
when Tuple, Array
|
||||
return compare_tuples(a, b) if b.is_a?(Tuple) || b.is_a?(Array)
|
||||
a === b
|
||||
when NamedTuple, Hash
|
||||
return compare_named_tuples(a, b) if b.is_a?(NamedTuple) || b.is_a?(Hash)
|
||||
a === b
|
||||
else
|
||||
a === b
|
||||
end
|
||||
|
|
|
@ -133,13 +133,12 @@ module Spectator
|
|||
if method.splat_index
|
||||
method.args.each_with_index do |arg, i|
|
||||
if i == method.splat_index
|
||||
original += '*'
|
||||
if arg.internal_name && arg.internal_name.size > 0
|
||||
original += "#{arg.internal_name}, "
|
||||
original += "*#{arg.internal_name}, "
|
||||
end
|
||||
original += "**#{method.double_splat}, " if method.double_splat
|
||||
elsif i > method.splat_index
|
||||
original += "#{arg.name}: #{arg.internal_name}"
|
||||
original += "#{arg.name}: #{arg.internal_name}, "
|
||||
else
|
||||
original += "#{arg.internal_name}, "
|
||||
end
|
||||
|
@ -180,7 +179,7 @@ module Spectator
|
|||
::NamedTuple.new(
|
||||
{% for arg, i in method.args %}{% if !method.splat_index || i < method.splat_index %}{{arg.internal_name.stringify}}: {{arg.internal_name}}, {% end %}{% end %}
|
||||
),
|
||||
{% if method.splat_index && (splat = method.args[method.splat_index].internal_name) %}{{splat.symbolize}}, {{splat}},{% end %}
|
||||
{% if method.splat_index && !(splat = method.args[method.splat_index].internal_name).empty? %}{{splat.symbolize}}, {{splat}},{% end %}
|
||||
::NamedTuple.new(
|
||||
{% for arg, i in method.args %}{% if method.splat_index && i > method.splat_index %}{{arg.internal_name.stringify}}: {{arg.internal_name}}, {% end %}{% end %}
|
||||
).merge({{method.double_splat}})
|
||||
|
@ -283,9 +282,8 @@ module Spectator
|
|||
if method.splat_index
|
||||
method.args.each_with_index do |arg, i|
|
||||
if i == method.splat_index
|
||||
original += '*'
|
||||
if arg.internal_name && arg.internal_name.size > 0
|
||||
original += "#{arg.internal_name}, "
|
||||
original += "*#{arg.internal_name}, "
|
||||
end
|
||||
original += "**#{method.double_splat}, " if method.double_splat
|
||||
elsif i > method.splat_index
|
||||
|
@ -332,7 +330,7 @@ module Spectator
|
|||
::NamedTuple.new(
|
||||
{% for arg, i in method.args %}{% if !method.splat_index || i < method.splat_index %}{{arg.internal_name.stringify}}: {{arg.internal_name}}, {% end %}{% end %}
|
||||
),
|
||||
{% if method.splat_index && (splat = method.args[method.splat_index].internal_name) %}{{splat.symbolize}}, {{splat}},{% end %}
|
||||
{% if method.splat_index && !(splat = method.args[method.splat_index].internal_name).empty? %}{{splat.symbolize}}, {{splat}},{% end %}
|
||||
::NamedTuple.new(
|
||||
{% for arg, i in method.args %}{% if method.splat_index && i > method.splat_index %}{{arg.internal_name.stringify}}: {{arg.internal_name}}, {% end %}{% end %}
|
||||
).merge({{method.double_splat}})
|
||||
|
@ -539,6 +537,7 @@ module Spectator
|
|||
# Get the value as-is from the stub.
|
||||
# This will be compiled as a union of all known stubbed value types.
|
||||
%value = {{stub}}.call({{call}})
|
||||
%type = {{type}}
|
||||
|
||||
# Attempt to cast the value to the method's return type.
|
||||
# If successful, which it will be in most cases, return it.
|
||||
|
@ -549,12 +548,12 @@ module Spectator
|
|||
%cast
|
||||
{% elsif fail_cast == :raise %}
|
||||
# Check if nil was returned by the stub and if its okay to return it.
|
||||
if %value.nil? && {{type}}.nilable?
|
||||
if %value.nil? && %type.nilable?
|
||||
# Value was nil and nil is allowed to be returned.
|
||||
%cast.as({{type}})
|
||||
%type.cast(%cast)
|
||||
elsif %cast.nil?
|
||||
# The stubbed value was something else entirely and cannot be cast to the return type.
|
||||
raise TypeCastError.new("#{_spectator_stubbed_name} received message #{ {{call}} } and is attempting to return a `#{%value.class}`, but returned type must be `#{ {{type}} }`.")
|
||||
raise TypeCastError.new("#{_spectator_stubbed_name} received message #{ {{call}} } and is attempting to return a `#{%value.class}`, but returned type must be `#{%type}`.")
|
||||
else
|
||||
# Types match and value can be returned as cast type.
|
||||
%cast
|
||||
|
|
|
@ -9,7 +9,7 @@ module Spectator
|
|||
end
|
||||
|
||||
# Calls the `pass` method on *visitor*.
|
||||
def accept(visitor)
|
||||
def accept(visitor, &)
|
||||
visitor.pass(yield self)
|
||||
end
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ module Spectator
|
|||
end
|
||||
|
||||
# Calls the `pending` method on the *visitor*.
|
||||
def accept(visitor)
|
||||
def accept(visitor, &)
|
||||
visitor.pending(yield self)
|
||||
end
|
||||
|
||||
|
|
|
@ -22,51 +22,106 @@ class Object
|
|||
# ```
|
||||
# require "spectator/should"
|
||||
# ```
|
||||
def should(matcher, message = nil)
|
||||
def should(matcher, message = nil, *, _file = __FILE__, _line = __LINE__)
|
||||
actual = ::Spectator::Value.new(self)
|
||||
location = ::Spectator::Location.new(_file, _line)
|
||||
match_data = matcher.match(actual)
|
||||
expectation = ::Spectator::Expectation.new(match_data, message: message)
|
||||
expectation = ::Spectator::Expectation.new(match_data, location, message)
|
||||
::Spectator::Harness.current.report(expectation)
|
||||
end
|
||||
|
||||
# Asserts that some criteria defined by the matcher is satisfied.
|
||||
# Allows a custom message to be used.
|
||||
# Returns the expected value cast as the expected type, if the matcher is satisfied.
|
||||
def should(matcher : ::Spectator::Matchers::TypeMatcher(U), message = nil, *, _file = __FILE__, _line = __LINE__) forall U
|
||||
actual = ::Spectator::Value.new(self)
|
||||
location = ::Spectator::Location.new(_file, _line)
|
||||
match_data = matcher.match(actual)
|
||||
expectation = ::Spectator::Expectation.new(match_data, location, message)
|
||||
if ::Spectator::Harness.current.report(expectation)
|
||||
return self if self.is_a?(U)
|
||||
|
||||
raise "Spectator bug: expected value should have cast to #{U}"
|
||||
else
|
||||
raise TypeCastError.new("Expected value should be a #{U}, but was actually #{self.class}")
|
||||
end
|
||||
end
|
||||
|
||||
# Works the same as `#should` except the condition is inverted.
|
||||
# When `#should` succeeds, this method will fail, and vice-versa.
|
||||
def should_not(matcher, message = nil)
|
||||
def should_not(matcher, message = nil, *, _file = __FILE__, _line = __LINE__)
|
||||
actual = ::Spectator::Value.new(self)
|
||||
location = ::Spectator::Location.new(_file, _line)
|
||||
match_data = matcher.negated_match(actual)
|
||||
expectation = ::Spectator::Expectation.new(match_data, message: message)
|
||||
expectation = ::Spectator::Expectation.new(match_data, location, message)
|
||||
::Spectator::Harness.current.report(expectation)
|
||||
end
|
||||
|
||||
# Asserts that some criteria defined by the matcher is not satisfied.
|
||||
# Allows a custom message to be used.
|
||||
# Returns the expected value cast without the unexpected type, if the matcher is satisfied.
|
||||
def should_not(matcher : ::Spectator::Matchers::TypeMatcher(U), message = nil, *, _file = __FILE__, _line = __LINE__) forall U
|
||||
actual = ::Spectator::Value.new(self)
|
||||
location = ::Spectator::Location.new(_file, _line)
|
||||
match_data = matcher.negated_match(actual)
|
||||
expectation = ::Spectator::Expectation.new(match_data, location, message)
|
||||
if ::Spectator::Harness.current.report(expectation)
|
||||
return self unless self.is_a?(U)
|
||||
|
||||
raise "Spectator bug: expected value should not be #{U}"
|
||||
else
|
||||
raise TypeCastError.new("Expected value is not expected to be a #{U}, but was actually #{self.class}")
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that some criteria defined by the matcher is not satisfied.
|
||||
# Allows a custom message to be used.
|
||||
# Returns the expected value cast as a non-nillable type, if the matcher is satisfied.
|
||||
def should_not(matcher : ::Spectator::Matchers::NilMatcher, message = nil, *, _file = __FILE__, _line = __LINE__)
|
||||
actual = ::Spectator::Value.new(self)
|
||||
location = ::Spectator::Location.new(_file, _line)
|
||||
match_data = matcher.negated_match(actual)
|
||||
expectation = ::Spectator::Expectation.new(match_data, location, message)
|
||||
if ::Spectator::Harness.current.report(expectation)
|
||||
return self unless self.nil?
|
||||
|
||||
raise "Spectator bug: expected value should not be nil"
|
||||
else
|
||||
raise NilAssertionError.new("Expected value should not be nil.")
|
||||
end
|
||||
end
|
||||
|
||||
# Works the same as `#should` except that the condition check is postponed.
|
||||
# The expectation is checked after the example finishes and all hooks have run.
|
||||
def should_eventually(matcher, message = nil)
|
||||
::Spectator::Harness.current.defer { should(matcher, message) }
|
||||
def should_eventually(matcher, message = nil, *, _file = __FILE__, _line = __LINE__)
|
||||
::Spectator::Harness.current.defer { should(matcher, message, _file: _file, _line: _line) }
|
||||
end
|
||||
|
||||
# Works the same as `#should_not` except that the condition check is postponed.
|
||||
# The expectation is checked after the example finishes and all hooks have run.
|
||||
def should_never(matcher, message = nil)
|
||||
::Spectator::Harness.current.defer { should_not(matcher, message) }
|
||||
def should_never(matcher, message = nil, *, _file = __FILE__, _line = __LINE__)
|
||||
::Spectator::Harness.current.defer { should_not(matcher, message, _file: _file, _line: _line) }
|
||||
end
|
||||
end
|
||||
|
||||
struct Proc(*T, R)
|
||||
# Extension method to create an expectation for a block of code (proc).
|
||||
# Depending on the matcher, the proc may be executed multiple times.
|
||||
def should(matcher, message = nil)
|
||||
def should(matcher, message = nil, *, _file = __FILE__, _line = __LINE__)
|
||||
actual = ::Spectator::Block.new(self)
|
||||
location = ::Spectator::Location.new(_file, _line)
|
||||
match_data = matcher.match(actual)
|
||||
expectation = ::Spectator::Expectation.new(match_data, message: message)
|
||||
expectation = ::Spectator::Expectation.new(match_data, location, message)
|
||||
::Spectator::Harness.current.report(expectation)
|
||||
end
|
||||
|
||||
# Works the same as `#should` except the condition is inverted.
|
||||
# When `#should` succeeds, this method will fail, and vice-versa.
|
||||
def should_not(matcher, message = nil)
|
||||
def should_not(matcher, message = nil, *, _file = __FILE__, _line = __LINE__)
|
||||
actual = ::Spectator::Block.new(self)
|
||||
location = ::Spectator::Location.new(_file, _line)
|
||||
match_data = matcher.negated_match(actual)
|
||||
expectation = ::Spectator::Expectation.new(match_data, message: message)
|
||||
expectation = ::Spectator::Expectation.new(match_data, location, message)
|
||||
::Spectator::Harness.current.report(expectation)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,18 +13,13 @@ module Spectator
|
|||
|
||||
# Creates a wrapper for the specified value.
|
||||
def initialize(value)
|
||||
@pointer = Box.box(value)
|
||||
@pointer = Value.new(value).as(Void*)
|
||||
end
|
||||
|
||||
# Retrieves the previously wrapped value.
|
||||
# The *type* of the wrapped value must match otherwise an error will be raised.
|
||||
def get(type : T.class) : T forall T
|
||||
{% begin %}
|
||||
{% if T.nilable? %}
|
||||
@pointer.null? ? nil :
|
||||
{% end %}
|
||||
Box(T).unbox(@pointer)
|
||||
{% end %}
|
||||
@pointer.unsafe_as(Value(T)).get
|
||||
end
|
||||
|
||||
# Retrieves the previously wrapped value.
|
||||
|
@ -39,12 +34,20 @@ module Spectator
|
|||
# type = wrapper.get { Int32 } # Returns Int32
|
||||
# ```
|
||||
def get(& : -> T) : T forall T
|
||||
{% begin %}
|
||||
{% if T.nilable? %}
|
||||
@pointer.null? ? nil :
|
||||
{% end %}
|
||||
Box(T).unbox(@pointer)
|
||||
{% end %}
|
||||
@pointer.unsafe_as(Value(T)).get
|
||||
end
|
||||
|
||||
# Wrapper for a value.
|
||||
# Similar to `Box`, but doesn't segfault on nil and unions.
|
||||
private class Value(T)
|
||||
# Creates the wrapper.
|
||||
def initialize(@value : T)
|
||||
end
|
||||
|
||||
# Retrieves the value.
|
||||
def get : T
|
||||
@value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
104
test.cr
104
test.cr
|
@ -1,104 +0,0 @@
|
|||
require "./src/spectator"
|
||||
|
||||
module Thing
|
||||
def self.original_method
|
||||
:original
|
||||
end
|
||||
|
||||
def self.default_method
|
||||
:original
|
||||
end
|
||||
|
||||
def self.stubbed_method(_value = 42)
|
||||
:original
|
||||
end
|
||||
|
||||
macro finished
|
||||
def self.debug
|
||||
{% begin %}puts "Methods: ", {{@type.methods.map &.name.stringify}} of String{% end %}
|
||||
{% begin %}puts "Class Methods: ", {{@type.class.methods.map &.name.stringify}} of String{% end %}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Thing.debug
|
||||
|
||||
# Spectator::Mock.define_subtype(:module, Thing, MockThing, default_method: :default) do
|
||||
# stub def stubbed_method(_value = 42)
|
||||
# :stubbed
|
||||
# end
|
||||
# end
|
||||
|
||||
# Spectator.describe "Mock modules" do
|
||||
# let(mock) { MockThing }
|
||||
|
||||
# after { mock._spectator_clear_stubs }
|
||||
|
||||
# it "overrides an existing method" do
|
||||
# stub = Spectator::ValueStub.new(:original_method, :override)
|
||||
# expect { mock._spectator_define_stub(stub) }.to change { mock.original_method }.from(:original).to(:override)
|
||||
# end
|
||||
|
||||
# it "doesn't affect other methods" do
|
||||
# stub = Spectator::ValueStub.new(:stubbed_method, :override)
|
||||
# expect { mock._spectator_define_stub(stub) }.to_not change { mock.original_method }
|
||||
# end
|
||||
|
||||
# it "replaces an existing default stub" do
|
||||
# stub = Spectator::ValueStub.new(:default_method, :override)
|
||||
# expect { mock._spectator_define_stub(stub) }.to change { mock.default_method }.to(:override)
|
||||
# end
|
||||
|
||||
# it "replaces an existing stubbed method" do
|
||||
# stub = Spectator::ValueStub.new(:stubbed_method, :override)
|
||||
# expect { mock._spectator_define_stub(stub) }.to change { mock.stubbed_method }.to(:override)
|
||||
# end
|
||||
|
||||
# def restricted(thing : Thing.class)
|
||||
# thing.default_method
|
||||
# end
|
||||
|
||||
# describe "._spectator_clear_stubs" do
|
||||
# before do
|
||||
# stub = Spectator::ValueStub.new(:original_method, :override)
|
||||
# mock._spectator_define_stub(stub)
|
||||
# end
|
||||
|
||||
# it "removes previously defined stubs" do
|
||||
# expect { mock._spectator_clear_stubs }.to change { mock.original_method }.from(:override).to(:original)
|
||||
# end
|
||||
# end
|
||||
|
||||
# describe "._spectator_calls" do
|
||||
# before { mock._spectator_clear_calls }
|
||||
|
||||
# # Retrieves symbolic names of methods called on a mock.
|
||||
# def called_method_names(mock)
|
||||
# mock._spectator_calls.map(&.method)
|
||||
# end
|
||||
|
||||
# it "stores calls to original methods" do
|
||||
# expect { mock.original_method }.to change { called_method_names(mock) }.from(%i[]).to(%i[original_method])
|
||||
# end
|
||||
|
||||
# it "stores calls to default methods" do
|
||||
# expect { mock.default_method }.to change { called_method_names(mock) }.from(%i[]).to(%i[default_method])
|
||||
# end
|
||||
|
||||
# it "stores calls to stubbed methods" do
|
||||
# expect { mock.stubbed_method }.to change { called_method_names(mock) }.from(%i[]).to(%i[stubbed_method])
|
||||
# end
|
||||
|
||||
# it "stores multiple calls to the same stub" do
|
||||
# mock.stubbed_method
|
||||
# expect { mock.stubbed_method }.to change { called_method_names(mock) }.from(%i[stubbed_method]).to(%i[stubbed_method stubbed_method])
|
||||
# end
|
||||
|
||||
# it "stores arguments for a call" do
|
||||
# mock.stubbed_method(5)
|
||||
# args = Spectator::Arguments.capture(5)
|
||||
# call = mock._spectator_calls.first
|
||||
# expect(call.arguments).to eq(args)
|
||||
# end
|
||||
# end
|
||||
# end
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
readonly image=crystallang/crystal:nightly
|
||||
readonly code=/project
|
||||
|
||||
docker run -it -v "$PWD:${code}" -w "${code}" "${image}" crystal spec "$@"
|
Loading…
Reference in New Issue