Store calls to mocks and doubles

This commit is contained in:
Michael Miller 2022-06-28 22:54:08 -06:00
parent c98edcec5d
commit 3589f23475
No known key found for this signature in database
GPG key ID: AC78B32D30CE34A2
9 changed files with 158 additions and 1 deletions

View file

@ -326,4 +326,37 @@ Spectator.describe Spectator::Double do
expect { dbl._spectator_clear_stubs }.to change { dbl.foo }.from(5).to(42)
end
end
describe "#_spectator_calls" do
subject(dbl) { FooBarDouble.new }
let(stub) { Spectator::ValueStub.new(:foo, 5) }
before_each { dbl._spectator_define_stub(stub) }
# Retrieves symbolic names of methods called on a double.
def called_method_names(dbl)
dbl._spectator_calls.map(&.method)
end
it "stores calls to stubbed methods" do
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo])
end
it "stores multiple calls to the same stub" do
dbl.foo
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo])
end
it "stores calls to non-stubbed methods" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/)
expect(called_method_names(dbl)).to eq(%i[baz])
end
it "stores arguments for a call" do
dbl.foo(42)
args = Spectator::Arguments.capture(42)
call = dbl._spectator_calls(:foo).first
expect(call.arguments).to eq(args)
end
end
end

View file

@ -268,4 +268,37 @@ Spectator.describe Spectator::LazyDouble do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/)
end
end
describe "#_spectator_calls" do
subject(dbl) { Spectator::LazyDouble.new(foo: 42, bar: "baz") }
let(stub) { Spectator::ValueStub.new(:foo, 5) }
before_each { dbl._spectator_define_stub(stub) }
# Retrieves symbolic names of methods called on a double.
def called_method_names(dbl)
dbl._spectator_calls.map(&.method)
end
it "stores calls to stubbed methods" do
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo])
end
it "stores multiple calls to the same stub" do
dbl.foo
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo])
end
it "stores calls to non-stubbed methods" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/)
expect(called_method_names(dbl)).to eq(%i[baz])
end
it "stores arguments for a call" do
dbl.foo(42)
args = Spectator::Arguments.capture(42)
call = dbl._spectator_calls(:foo).first
expect(call.arguments).to eq(args)
end
end
end

View file

@ -5,6 +5,11 @@ Spectator.describe Spectator::Mock do
let(stub2) { Spectator::ValueStub.new(:method2, :override) }
let(stub3) { Spectator::ValueStub.new(:method3, "stubbed") }
# Retrieves symbolic names of methods called on a mock.
def called_method_names(mock)
mock._spectator_calls.map(&.method)
end
describe "#define_subtype" do
context "with a concrete class" do
class Thing
@ -74,6 +79,17 @@ Spectator.describe Spectator::Mock do
expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name."
end
it "records invoked stubs" do
expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[]).to(%i[method2])
expect { mock.method1 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method1])
expect { mock.method3 }.to change { called_method_names(mock) }.from(%i[method2 method1]).to(%i[method2 method1 method3])
end
it "records multiple invocations of the same stub" do
mock.method2
expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method2])
end
def restricted(thing : Thing)
thing.method1
end
@ -158,6 +174,17 @@ Spectator.describe Spectator::Mock do
expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name."
end
it "records invoked stubs" do
expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[]).to(%i[method2])
expect { mock.method1 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method1])
expect { mock.method3 }.to change { called_method_names(mock) }.from(%i[method2 method1]).to(%i[method2 method1 method3])
end
it "records multiple invocations of the same stub" do
mock.method2
expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method2])
end
def restricted(thing : Thing)
thing.method1
end
@ -343,6 +370,17 @@ Spectator.describe Spectator::Mock do
expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name."
end
it "records invoked stubs" do
expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[]).to(%i[method2])
expect { mock.method1 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method1])
expect { mock.method3 }.to change { called_method_names(mock) }.from(%i[method2 method1]).to(%i[method2 method1 method3])
end
it "records multiple invocations of the same stub" do
mock.method2
expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method2])
end
def restricted(thing : MockedClass)
thing.method1
end
@ -429,6 +467,17 @@ Spectator.describe Spectator::Mock do
expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name."
end
it "records invoked stubs" do
expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[]).to(%i[method2])
expect { mock.method1 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method1])
expect { mock.method3 }.to change { called_method_names(mock) }.from(%i[method2 method1]).to(%i[method2 method1 method3])
end
it "records multiple invocations of the same stub" do
mock.method2
expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method2])
end
def restricted(thing : MockedStruct)
thing.method1
end

View file

@ -296,4 +296,36 @@ Spectator.describe Spectator::NullDouble do
expect(dbl.baz).to be(dbl)
end
end
describe "#_spectator_calls" do
subject(dbl) { FooBarDouble.new }
let(stub) { Spectator::ValueStub.new(:foo, 5) }
before_each { dbl._spectator_define_stub(stub) }
# Retrieves symbolic names of methods called on a double.
def called_method_names(dbl)
dbl._spectator_calls.map(&.method)
end
it "stores calls to stubbed methods" do
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo])
end
it "stores multiple calls to the same stub" do
dbl.foo
expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo])
end
it "stores calls to non-stubbed methods" do
expect { dbl.baz }.to change { called_method_names(dbl) }.from(%i[]).to(%i[baz])
end
it "stores arguments for a call" do
dbl.foo(42)
args = Spectator::Arguments.capture(42)
call = dbl._spectator_calls(:foo).first
expect(call.arguments).to eq(args)
end
end
end

View file

@ -117,6 +117,10 @@ module Spectator
@calls << call
end
def _spectator_calls
@calls
end
def _spectator_calls(method : Symbol) : Enumerable(MethodCall)
@calls.select { |call| call.method == method }
end
@ -164,6 +168,8 @@ module Spectator
Log.trace { "Got undefined method `{{call.name}}({{*call.args}}{% if call.named_args %}{% unless call.args.empty? %}, {% end %}{{*call.named_args}}{% end %}){% if call.block %} { ... }{% end %}`" }
args = ::Spectator::Arguments.capture({{call.args.splat(", ")}}{% if call.named_args %}{{*call.named_args}}{% end %})
call = ::Spectator::MethodCall.new({{call.name.symbolize}}, args)
_spectator_record_call(call)
raise ::Spectator::UnexpectedMessage.new("#{_spectator_stubbed_name} received unexpected message #{call}")
nil # Necessary for compiler to infer return type as nil. Avoids runtime "can't execute ... `x` has no type errors".
end

View file

@ -48,6 +48,7 @@ module Spectator
# Capture information about the call.
%args = ::Spectator::Arguments.capture({{call.args.splat(", ")}}{% if call.named_args %}{{*call.named_args}}{% end %})
%call = ::Spectator::MethodCall.new({{call.name.symbolize}}, %args)
_spectator_record_call(%call)
# Attempt to find a stub that satisfies the method call and arguments.
if %stub = _spectator_find_stub(%call)

View file

@ -52,7 +52,7 @@ module Spectator
@_spectator_stubs = nil
end
private getter _spectator_calls = [] of ::Spectator::MethodCall
getter _spectator_calls = [] of ::Spectator::MethodCall
# Returns the mock's name formatted for user output.
private def _spectator_stubbed_name : String

View file

@ -56,6 +56,7 @@ module Spectator
# Capture information about the call.
%args = ::Spectator::Arguments.capture({{call.args.splat(", ")}}{% if call.named_args %}{{*call.named_args}}{% end %})
%call = ::Spectator::MethodCall.new({{call.name.symbolize}}, %args)
_spectator_record_call(%call)
# Attempt to find a stub that satisfies the method call and arguments.
if %stub = _spectator_find_stub(%call)

View file

@ -133,6 +133,7 @@ module Spectator
{% if method.double_splat %}**{{method.double_splat}}{% end %}
)
%call = ::Spectator::MethodCall.new({{method.name.symbolize}}, %args)
_spectator_record_call(%call)
# Attempt to find a stub that satisfies the method call and arguments.
# Finding a suitable stub is delegated to the type including the `Stubbable` module.
@ -222,6 +223,7 @@ module Spectator
{% if method.double_splat %}**{{method.double_splat}}{% end %}
)
%call = ::Spectator::MethodCall.new({{method.name.symbolize}}, %args)
_spectator_record_call(%call)
# Attempt to find a stub that satisfies the method call and arguments.
# Finding a suitable stub is delegated to the type including the `Stubbable` module.