diff --git a/src/spectator/dsl/mocks.cr b/src/spectator/dsl/mocks.cr index 4e8858f..b5b99ee 100644 --- a/src/spectator/dsl/mocks.cr +++ b/src/spectator/dsl/mocks.cr @@ -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