Experimental mock functionality

This commit is contained in:
Michael Miller 2022-04-10 20:01:54 -06:00
parent b0e27c69e8
commit 3961662bf6
No known key found for this signature in database
GPG key ID: 32B47AE8F388A1FF
3 changed files with 143 additions and 0 deletions

View file

@ -0,0 +1,44 @@
require "../../spec_helper"
Spectator.describe Spectator::Mock do
describe "#define_subclass" do
class Thing
def method1
42
end
def method2
:original
end
def method3
"original"
end
end
Spectator::Mock.define_subclass(Thing, MockThing, :mock_name, method1: 123) do
stub def method2
:stubbed
end
end
let(thing) { MockThing.new }
it "defines a subclass of the mocked type" do
expect(MockThing).to be_lt(Thing)
end
it "overrides responses from methods with keyword arguments" do
expect(thing.method1).to eq(123)
end
it "overrides responses from methods defined in the block" do
expect(thing.method2).to eq(:stubbed)
end
it "allows methods to be stubbed" do
stub = Spectator::ValueStub.new(:method3, "stubbed")
expect { thing._spectator_define_stub(stub) }.to change { thing.method3 }.from("original").to("stubbed")
end
end
end

View file

@ -0,0 +1,47 @@
require "./method_call"
require "./mocked"
require "./stub"
require "./stubbed_name"
require "./value_stub"
module Spectator
module Mock
macro define_subclass(mocked_type, type_name, name = nil, **value_methods, &block)
{% if name %}@[::Spectator::StubbedName({{name}})]{% end %}
class {{type_name.id}} < {{mocked_type.id}}
include ::Spectator::Mocked
{% begin %}
@_spectator_stubs = [
{% for key, value in value_methods %}
::Spectator::ValueStub.new({{key.id.symbolize}}, {{value}}),
{% end %}
] of ::Spectator::Stub
{% end %}
def _spectator_define_stub(stub : ::Spectator::Stub) : Nil
@_spectator_stubs.unshift(stub)
end
def _spectator_find_stub(call : ::Spectator::MethodCall) : ::Spectator::Stub?
@_spectator_stubs.find &.===(call)
end
# Returns the mock's name formatted for user output.
private def _spectator_stubbed_name : String
\{% if anno = @type.annotation(::Spectator::StubbedName) %}
"#<Mock {{mocked_type.id}} \"" + \{{(anno[0] || :Anonymous.id).stringify}} + "\">"
\{% else %}
"#<Mock {{mocked_type.id}}>"
\{% end %}
end
macro finished
stub_all {{mocked_type.id}}
end
{% if block %}{{block.body}}{% end %}
end
end
end
end

View file

@ -0,0 +1,52 @@
require "./method_call"
require "./stubbable"
module Spectator
module Mocked
include Stubbable
# Method called when a stub isn't found.
#
# The received message is captured in *call*.
# Yield to call the original method's implementation.
# The stubbed method returns the value returned by this method.
# This method can also raise an error if it's impossible to return something.
def _spectator_stub_fallback(call : MethodCall, &)
raise "oof"
end
# Method called when a stub isn't found.
#
# The received message is captured in *call*.
# The expected return type is provided by *type*.
# Yield to call the original method's implementation.
# The stubbed method returns the value returned by this method.
# This method can also raise an error if it's impossible to return something.
def _spectator_stub_fallback(call : MethodCall, type, &)
raise "oof"
end
# Method called when a stub isn't found.
#
# This is similar to `#_spectator_stub_fallback`,
# but called when the original (un-stubbed) method isn't available.
# The received message is captured in *call*.
# The stubbed method returns the value returned by this method.
# This method can also raise an error if it's impossible to return something.
def _spectator_abstract_stub_fallback(call : MethodCall)
raise "oof"
end
# Method called when a stub isn't found.
#
# This is similar to `#_spectator_stub_fallback`,
# but called when the original (un-stubbed) method isn't available.
# The received message is captured in *call*.
# The expected return type is provided by *type*.
# The stubbed method returns the value returned by this method.
# This method can also raise an error if it's impossible to return something.
def _spectator_abstract_stub_fallback(call : MethodCall, type)
raise "oof"
end
end
end