diff --git a/src/spectator/mocks/stubbable.cr b/src/spectator/mocks/stubbable.cr index 21d3d49..55e180a 100644 --- a/src/spectator/mocks/stubbable.cr +++ b/src/spectator/mocks/stubbable.cr @@ -130,7 +130,7 @@ module Spectator if %stub = _spectator_find_stub(%call) # Cast the stub or return value to the expected type. # This is necessary to match the expected return type of the original method. - _spectator_cast_stub_value(%stub, %call, typeof({{original}})) + _spectator_cast_stub_value(%stub, %call, typeof({{original}}), {{method.return_type && method.return_type.resolve < Nil || false}}) else # Delegate missing stub behavior to concrete type. _spectator_stub_fallback(%call, typeof({{original}})) do @@ -221,7 +221,7 @@ module Spectator # This is necessary to match the expected return type of the original method. {% if method.return_type %} # Return type restriction takes priority since it can be a superset of the original implementation. - _spectator_cast_stub_value(%stub, %call, {{method.return_type}}) + _spectator_cast_stub_value(%stub, %call, {{method.return_type}}, {{method.return_type.resolve < Nil}}) {% elsif !method.abstract? %} # The method isn't abstract, infer the type it returns without calling it. _spectator_cast_stub_value(%stub, %call, typeof({{original}})) @@ -328,7 +328,8 @@ module Spectator # *stub* is the variable holding the stub. # *call* is the variable holding the captured method call. # *type* is the expected type to cast the value to. - private macro _spectator_cast_stub_value(stub, call, type) + # *nullable* indicates whether *type* can be nil or not. + private macro _spectator_cast_stub_value(stub, call, type, nullable = true) # Attempt to cast the stub to the method's return type. # If successful, return the value of the stub. # This is a common usage where the return type is simple and matches the stub type exactly. @@ -348,9 +349,12 @@ module Spectator # Attempt to cast the value to the method's return type. # If successful, which it will be in most cases, return it. # The caller will receive a properly typed value without unions or other side-effects. - begin - %value.as({{type}}) - rescue TypeCastError + if %cast = %value.as?({{type}}) + %cast + else + {% if nullable %} + nil + {% else %} # The stubbed value was something else entirely and cannot be cast to the return type. # There's something weird going on (compiler bug?) that sometimes causes this class lookup to fail. %type = begin @@ -359,6 +363,7 @@ module Spectator "" end raise TypeCastError.new("#{_spectator_stubbed_name} received message #{ {{call}} } and is attempting to return a `#{%type}`, but returned type must be `#{ {{type}} }`.") + {% end %} end end end