diff --git a/spec/spectator/mocks/double_spec.cr b/spec/spectator/mocks/double_spec.cr index 9a316b8..f6db87d 100644 --- a/spec/spectator/mocks/double_spec.cr +++ b/spec/spectator/mocks/double_spec.cr @@ -5,7 +5,7 @@ Spectator.describe Spectator::Double do Spectator::Double.define(FooBarDouble, "dbl-name", foo: 42, bar: "baz") # The subject `dbl` must be carefully used in sub-contexts, otherwise it pollutes parent scopes. - # This changes the type of `dbl` to `Double`, which produces a union of methods and their return types. + # This changes the type of `dbl` to `Double+`, which produces a union of methods and their return types. context "plain double" do subject(dbl) { FooBarDouble.new } @@ -38,6 +38,10 @@ Spectator.describe Spectator::Double do it "uses nil for undefined messages" do expect { dbl.baz }.to compile_as(Nil) end + + it "supports blocks" do + dbl.bar + end end context "without a double name" do diff --git a/src/spectator/mocks/stubbable.cr b/src/spectator/mocks/stubbable.cr index f523160..31e6c6f 100644 --- a/src/spectator/mocks/stubbable.cr +++ b/src/spectator/mocks/stubbable.cr @@ -269,12 +269,33 @@ module Spectator end # Utility for defining a stubbed method and a fallback. + # + # NOTE: The method definition is exploded and redefined by its parts because using `{{method}}` omits the block argument. private macro inject_stub(method) {% if method.abstract? %} - abstract_stub {{method}} + abstract_stub {% if method.visibility != :public %}{{method.visibility.id}}{% end %} abstract def {{method.receiver}}{{method.name}}( + {% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %} + {% if method.double_splat %}**{{method.double_splat}}, {% end %} + {% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %} + ){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %} {% else %} - {{method}} - stub {{method}} + {% if method.visibility != :public %}{{method.visibility.id}}{% end %} def {{method.receiver}}{{method.name}}( + {% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %} + {% if method.double_splat %}**{{method.double_splat}}, {% end %} + {% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %} + ){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %} + {{method.body}} + end + + stub {% if method.visibility != :public %}{{method.visibility.id}}{% end %} def {{method.receiver}}{{method.name}}( + {% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg}}, {% end %} + {% if method.double_splat %}**{{method.double_splat}}, {% end %} + {% if method.block_arg %}&{{method.block_arg}}{% elsif method.accepts_block? %}&{% end %} + ){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %} + # Content of this method is discarded, + # but this will compile successfully even if it's used. + previous_def{% if method.accepts_block? %} { |*%yargs| yield *%yargs }{% end %} + end {% end %} end