This commit is contained in:
Michael Miller 2022-03-31 18:51:28 -06:00
parent 7896a3496d
commit 914def05e1
No known key found for this signature in database
GPG Key ID: 32B47AE8F388A1FF
1 changed files with 50 additions and 0 deletions

View File

@ -8,6 +8,18 @@ module Spectator::DSL
# and double type name relative to its context.
DOUBLES = [] of {Symbol, Symbol, Symbol}
# Defines a new double type.
#
# This must be called from outside of a method (where classes can be defined).
# The *name* is the identifier used to reference the double, like when instantiating it.
# Simple stubbed methods returning a value can be defined by *value_methods*.
# More complex methods and stubs can be defined in a block passed to this macro.
#
# ```
# def_double(:dbl, foo: 42, bar: "baz") do
# stub abstract def deferred : String
# end
# ```
private macro def_double(name, **value_methods, &block)
{% # Construct a unique type name for the double by using the number of defined doubles.
index = ::Spectator::DSL::Mocks::DOUBLES.size
@ -18,7 +30,10 @@ module Spectator::DSL
# This is important for constructing an instance of the double later.
::Spectator::DSL::Mocks::DOUBLES << {name.id.symbolize, @type.name(generic_args: false).symbolize, double_type_name.symbolize} %}
# Define the plain double type.
::Spectator::Double.define({{double_type_name}}, {{name}}, {{**value_methods}}) do
# Returns a new double that responds to undefined methods with itself.
# See: `NullDouble`
def as_null_object
{{null_double_type_name}}.new(@stubs)
end
@ -27,12 +42,32 @@ module Spectator::DSL
end
{% begin %}
# Define a matching null double type.
::Spectator::NullDouble.define({{null_double_type_name}}, {{name}}, {{**value_methods}}){% if block %} do
{{block.body}}
end{% end %}
{% end %}
end
# Instantiates a double.
#
# The *name* is an optional identifier for the double.
# If *name* was previously used to define a double (with `#def_double`),
# then this macro returns a new instance of that previously defined double type.
# Otherwise, a `LazyDouble` is created and returned.
#
# Initial stubbed values for methods can be provided with *value_methods*.
#
# ```
# def_double(:dbl, foo: 42)
#
# specify do
# dbl = new_double(:dbl, foo: 7)
# expect(dbl.foo).to eq(7)
# lazy = new_double(:lazy, foo: 123)
# expect(lazy.foo).to eq(123)
# end
# ```
private macro new_double(name = nil, **value_methods)
{% # Find tuples with the same name.
found_tuples = ::Spectator::DSL::Mocks::DOUBLES.select { |tuple| tuple[0] == name.id.symbolize }
@ -66,6 +101,13 @@ module Spectator::DSL
{% end %}
end
# Defines or instantiates a double.
#
# When used inside of a method, instantiates a new double.
# See `#new_double`.
#
# When used outside of a method, defines a new double.
# See `#def_double`.
macro double(name, **value_methods, &block)
{% begin %}
{% if @def %}new_double{% else %}def_double{% end %}({{name}}, {{**value_methods}}){% if block %} do
@ -74,6 +116,14 @@ module Spectator::DSL
{% end %}
end
# Instantiates a new double with predefined responses.
#
# This constructs a `LazyDouble`.
#
# ```
# dbl = double(foo: 42)
# expect(dbl.foo).to eq(42)
# ```
macro double(**value_methods)
new_double({{**value_methods}})
end