Capture and report arguments in exception message

This commit is contained in:
Michael Miller 2022-03-05 16:06:01 -07:00
parent 75ccda0a86
commit 13f185b801
No known key found for this signature in database
GPG key ID: 32B47AE8F388A1FF
2 changed files with 27 additions and 13 deletions

View file

@ -1,7 +1,7 @@
require "../../spec_helper" require "../../spec_helper"
Spectator.describe Spectator::Double do Spectator.describe Spectator::Double do
subject(dbl) { Spectator::Double({foo: Int32, bar: String}).new("foobar", foo: 42, bar: "baz") } subject(dbl) { Spectator::Double({foo: Int32, bar: String}).new("dbl-name", foo: 42, bar: "baz") }
it "responds to defined messages" do it "responds to defined messages" do
aggregate_failures do aggregate_failures do
@ -11,11 +11,15 @@ Spectator.describe Spectator::Double do
end end
it "fails on undefined messages" do it "fails on undefined messages" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage) expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/)
end end
it "reports the name in errors" do it "reports the name in errors" do
expect { dbl.baz }.to raise_error(/foobar/) expect { dbl.baz }.to raise_error(/dbl-name/)
end
it "reports arguments" do
expect { dbl.baz(123, "qux", field: :value) }.to raise_error(Spectator::UnexpectedMessage, /\(123, "qux", field: :value\)/)
end end
context "without a double name" do context "without a double name" do
@ -122,6 +126,10 @@ Spectator.describe Spectator::Double do
expect { dbl.same?(nil) }.to raise_error(Spectator::UnexpectedMessage, /mask/) expect { dbl.same?(nil) }.to raise_error(Spectator::UnexpectedMessage, /mask/)
end end
end end
it "reports arguments" do
expect { dbl.same?(123) }.to raise_error(Spectator::UnexpectedMessage, /\(123\)/)
end
end end
context "with arguments constraints" do context "with arguments constraints" do

View file

@ -52,18 +52,23 @@ module Spectator
{% for meth in type.methods %} {% for meth in type.methods %}
{% if !meth.abstract? && !DSL::RESERVED_KEYWORDS.includes?(meth.name.symbolize) %} {% if !meth.abstract? && !DSL::RESERVED_KEYWORDS.includes?(meth.name.symbolize) %}
{% if meth.visibility != :public %}{{meth.visibility.id}}{% end %} def {{meth.receiver}}{{meth.name}}( {% if meth.visibility != :public %}{{meth.visibility.id}}{% end %} def {{meth.receiver}}{{meth.name}}(
{{meth.args.splat}}{% if !meth.args.empty? %}, {% end %} {{meth.args.splat(",")}}
{% if meth.double_splat %}**{{meth.double_splat}}, {% end %} {% if meth.double_splat %}**{{meth.double_splat}}, {% end %}
{% if meth.block_arg %}&{{meth.block_arg}}{% elsif meth.accepts_block? %}&{% end %} {% if meth.block_arg %}&{{meth.block_arg}}{% elsif meth.accepts_block? %}&{% end %}
){% if meth.return_type %} : {{meth.return_type}}{% end %}{% if !meth.free_vars.empty? %} forall {{meth.free_vars.splat}}{% end %} ){% if meth.return_type %} : {{meth.return_type}}{% end %}{% if !meth.free_vars.empty? %} forall {{meth.free_vars.splat}}{% end %}
# Capture call information.
arguments = Arguments.capture(
{{meth.args.map(&.internal_name).splat}}{% if !meth.args.empty? %}, {% end %}
{% if meth.double_splat %}**{{meth.double_splat}}, {% end %}
)
call = MethodCall.new({{meth.name.symbolize}}, arguments)
\{% if type = NT[{{meth.name.symbolize}}] %} \{% if type = NT[{{meth.name.symbolize}}] %}
{% if meth.return_type %} {% if meth.return_type %}
\{% if type <= {{meth.return_type}} %} \{% if type <= {{meth.return_type}} %}
# Return type appears to match configured type. # Return type appears to match configured type.
# Respond with configured value.
# Find a suitable response. # Find a suitable response.
call = MethodCall.capture({{meth.name.symbolize}}, {{meth.args.map(&.internal_name).splat}})
response = @responses.find &.===(call) response = @responses.find &.===(call)
if response if response
@ -71,7 +76,7 @@ module Spectator
response.as(Response(\{{type}})).value response.as(Response(\{{type}})).value
else else
# Response not configured for this method/message. # Response not configured for this method/message.
raise UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{meth.name}} (masking ancestor) with (<TODO: ARGS>).") raise UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{meth.name}} (masking ancestor) with #{arguments}")
end end
\{% else %} \{% else %}
# Return type doesn't match configured type. # Return type doesn't match configured type.
@ -83,7 +88,6 @@ module Spectator
# No return type restriction, return configured response. # No return type restriction, return configured response.
# Find a suitable response. # Find a suitable response.
call = MethodCall.capture({{meth.name.symbolize}}, {{meth.args.map(&.internal_name).splat}})
response = @responses.find &.===(call) response = @responses.find &.===(call)
if response if response
@ -91,12 +95,12 @@ module Spectator
response.as(Response(\{{type}})).value response.as(Response(\{{type}})).value
else else
# Response not configured for this method/message. # Response not configured for this method/message.
raise UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{meth.name}} (masking ancestor) with (<TODO: ARGS>).") raise UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{meth.name}} (masking ancestor) with #{arguments}")
end end
{% end %} {% end %}
\{% else %} \{% else %}
# Response not configured for this method/message. # Response not configured for this method/message.
raise UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{meth.name}} (masking ancestor) with (<TODO: ARGS>).") raise UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{meth.name}} (masking ancestor) with #{arguments}")
\{% end %} \{% end %}
end end
{% end %} {% end %}
@ -111,9 +115,11 @@ module Spectator
# Handle all methods but only respond to configured messages. # Handle all methods but only respond to configured messages.
# Raises an `UnexpectedMessage` error for non-configures messages. # Raises an `UnexpectedMessage` error for non-configures messages.
macro method_missing(call) macro method_missing(call)
arguments = Arguments.capture({{call.args.splat(", ")}}{% if call.named_args %}{{call.named_args.splat}}{% end %})
call = MethodCall.new({{call.name.symbolize}}, arguments)
\{% if type = NT[{{call.name.symbolize}}] %} \{% if type = NT[{{call.name.symbolize}}] %}
# Find a suitable response. # Find a suitable response.
call = MethodCall.capture({{call.name.symbolize}}, {{call.args.splat}})
response = @responses.find &.===(call) response = @responses.find &.===(call)
if response if response
@ -121,11 +127,11 @@ module Spectator
response.as(Response(\{{type}})).value response.as(Response(\{{type}})).value
else else
# Response not configured for this method/message. # Response not configured for this method/message.
raise UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{call.name}} with (<TODO: ARGS>).") raise UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{call.name}} with #{arguments}")
end end
\{% else %} \{% else %}
# Response not configured for this method/message. # Response not configured for this method/message.
raise UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{call.name}} with (<TODO: ARGS>).") raise UnexpectedMessage.new("#{_spectator_double_name} received unexpected message :{{call.name}} with #{arguments}")
\{% end %} \{% end %}
end end
end end