mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Cleanup and docs
This commit is contained in:
parent
151926fd25
commit
7931847164
3 changed files with 110 additions and 9 deletions
|
@ -2,26 +2,45 @@ require "./arguments"
|
||||||
require "./method_call"
|
require "./method_call"
|
||||||
require "./stub"
|
require "./stub"
|
||||||
require "./stubable"
|
require "./stubable"
|
||||||
|
require "./stubbed_name"
|
||||||
require "./unexpected_message"
|
require "./unexpected_message"
|
||||||
require "./value_stub"
|
require "./value_stub"
|
||||||
|
|
||||||
module Spectator
|
module Spectator
|
||||||
# Defines the name of a double or mock.
|
|
||||||
#
|
|
||||||
# When present on a stubbed type, this annotation indicates its name in output such as exceptions.
|
|
||||||
# Must have one argument - the name of the double or mock.
|
|
||||||
# This can be a symbol, string literal, or type name.
|
|
||||||
annotation StubbedName; end
|
|
||||||
|
|
||||||
# Stands in for an object for testing that a SUT calls expected methods.
|
# Stands in for an object for testing that a SUT calls expected methods.
|
||||||
#
|
#
|
||||||
# Handles all messages (method calls), but only responds to those configured.
|
# Handles all messages (method calls), but only responds to those configured.
|
||||||
# Methods called that were not configured will raise `UnexpectedMessage`.
|
# Methods called that were not configured will raise `UnexpectedMessage`.
|
||||||
|
# Doubles should be defined with the `#define` macro.
|
||||||
|
#
|
||||||
|
# Use `#_spectator_define_stub` to override behavior of a method in the double.
|
||||||
|
# Only methods defined in the double's type can have stubs.
|
||||||
|
# New methods are not defines when a stub is added that doesn't have a matching method name.
|
||||||
abstract class Double
|
abstract class Double
|
||||||
include Stubable
|
include Stubable
|
||||||
|
|
||||||
Log = Spectator::Log.for(self)
|
Log = Spectator::Log.for(self)
|
||||||
|
|
||||||
|
# Defines a test double type.
|
||||||
|
#
|
||||||
|
# The *type_name* is the name to give the class.
|
||||||
|
# Instances of the double can be named by providing a *name*.
|
||||||
|
# This can be a symbol, string, or even a type.
|
||||||
|
# See `StubbedName` for details.
|
||||||
|
#
|
||||||
|
# After the names, a collection of key-value pairs can be given to quickly define methods.
|
||||||
|
# Each key is the method name, and the corresponding value is the value returned by the method.
|
||||||
|
# These methods accept any arguments.
|
||||||
|
# Additionally, these methods can be overridden later with stubs.
|
||||||
|
#
|
||||||
|
# Lastly, a block can be provided to define additional methods and stubs.
|
||||||
|
# The block is evaluated in the context of the double's class.
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# Double.define(SomeDouble, meth1: 42, meth2: "foobar") do
|
||||||
|
# stub abstract def meth3 : Symbol
|
||||||
|
# end
|
||||||
|
# ```
|
||||||
macro define(type_name, name = nil, **value_methods, &block)
|
macro define(type_name, name = nil, **value_methods, &block)
|
||||||
{% if name %}@[::Spectator::StubbedName({{name}})]{% end %}
|
{% if name %}@[::Spectator::StubbedName({{name}})]{% end %}
|
||||||
class {{type_name.id}} < {{@type.name}}
|
class {{type_name.id}} < {{@type.name}}
|
||||||
|
@ -34,21 +53,26 @@ module Spectator
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Creates the double.
|
||||||
|
#
|
||||||
|
# An initial set of *stubs* can be provided.
|
||||||
def initialize(@stubs : Array(Stub) = [] of Stub)
|
def initialize(@stubs : Array(Stub) = [] of Stub)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Defines a stub to change the behavior of a method in this double.
|
||||||
|
#
|
||||||
|
# NOTE: Defining a stub for a method not defined in the double's type has no effect.
|
||||||
protected def _spectator_define_stub(stub : Stub) : Nil
|
protected def _spectator_define_stub(stub : Stub) : Nil
|
||||||
@stubs.unshift(stub)
|
@stubs.unshift(stub)
|
||||||
end
|
end
|
||||||
|
|
||||||
private def _spectator_find_stub(call) : Stub?
|
private def _spectator_find_stub(call : MethodCall) : Stub?
|
||||||
Log.debug { "Finding stub for #{call}" }
|
Log.debug { "Finding stub for #{call}" }
|
||||||
stub = @stubs.find &.===(call)
|
stub = @stubs.find &.===(call)
|
||||||
Log.debug { stub ? "Found stub #{stub} for #{call}" : "Did not find stub for #{call}" }
|
Log.debug { stub ? "Found stub #{stub} for #{call}" : "Did not find stub for #{call}" }
|
||||||
stub
|
stub
|
||||||
end
|
end
|
||||||
|
|
||||||
# Utility returning the double's name as a string.
|
|
||||||
private def _spectator_stubbed_name : String
|
private def _spectator_stubbed_name : String
|
||||||
{% if anno = @type.annotation(StubbedName) %}
|
{% if anno = @type.annotation(StubbedName) %}
|
||||||
"#<Double " + {{(anno[0] || :Anonymous.id).stringify}} + ">"
|
"#<Double " + {{(anno[0] || :Anonymous.id).stringify}} + ">"
|
||||||
|
|
|
@ -1,5 +1,49 @@
|
||||||
module Spectator
|
module Spectator
|
||||||
|
# Mix-in for mocks and doubles providing method stubs.
|
||||||
|
#
|
||||||
|
# Macros in this module can override existing methods.
|
||||||
|
# Stubbed methods will look for stubs to evaluate in place of their original functionality.
|
||||||
|
# The primary macro of interest is `#stub`.
|
||||||
|
# The macros are intended to be called from within the type being stubbed.
|
||||||
|
#
|
||||||
|
# Types including this module must define `#_spectator_find_stub` and `#_spectator_stubbed_name`.
|
||||||
|
# These are internal, reserved method names by Spectator, hence the `_spectator` prefix.
|
||||||
|
# These methods can't (and shouldn't) be stubbed.
|
||||||
module Stubable
|
module Stubable
|
||||||
|
# Attempts to find a stub that satisfies a method call.
|
||||||
|
#
|
||||||
|
# Returns a stub that matches the method *call*
|
||||||
|
# or nil if no stubs satisfy it.
|
||||||
|
abstract def _spectator_find_stub(call : MethodCall) : Stub?
|
||||||
|
|
||||||
|
# Utility returning the mock or double's name as a string.
|
||||||
|
abstract def _spectator_stubbed_name : String
|
||||||
|
|
||||||
|
# Redefines a method to accept stubs.
|
||||||
|
#
|
||||||
|
# The *method* should be a `Def`.
|
||||||
|
# That is, a normal looking method definition should follow the `stub` keyword.
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# stub def stubbed_method
|
||||||
|
# "foobar"
|
||||||
|
# end
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# The method being stubbed must already exist in the type, parent, or included/extend module.
|
||||||
|
# If it doesn't exist, and a new stubable method is being added, use `#inject_stub` instead.
|
||||||
|
# The original's method is called if there are no applicable stubs for the invocation.
|
||||||
|
# The body of the method passed to this macro is ignored.
|
||||||
|
#
|
||||||
|
# The method can be abstract.
|
||||||
|
# If an abstract method is invoked that doesn't have a stub, an `UnexpectedMessage` error is raised.
|
||||||
|
# The abstract method should have a return type annotation, otherwise the compiled return type will probably end up as a giant union.
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# stub abstract def stubbed_method : String
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# Stubbed methods will call `#_spectator_find_stub` with the method call information.
|
||||||
private macro stub(method)
|
private macro stub(method)
|
||||||
{% raise "stub requires a method definition" if !method.is_a?(Def) %}
|
{% raise "stub requires a method definition" if !method.is_a?(Def) %}
|
||||||
{% raise "Cannot stub method with reserved keyword as name - #{method.name}" if ::Spectator::DSL::RESERVED_KEYWORDS.includes?(method.name.symbolize) %}
|
{% raise "Cannot stub method with reserved keyword as name - #{method.name}" if ::Spectator::DSL::RESERVED_KEYWORDS.includes?(method.name.symbolize) %}
|
||||||
|
@ -40,6 +84,29 @@ module Spectator
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Redefines a method to require stubs.
|
||||||
|
#
|
||||||
|
# This macro is similar to `#stub` but requires that a stub is defined for the method if it's called.
|
||||||
|
#
|
||||||
|
# The *method* should be a `Def`.
|
||||||
|
# That is, a normal looking method definition should follow the `stub` keyword.
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# abstract_stub def stubbed_method
|
||||||
|
# "foobar"
|
||||||
|
# end
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# The method being stubbed doesn't need to exist yet.
|
||||||
|
# Its body of the method passed to this macro is ignored.
|
||||||
|
# The method can be abstract.
|
||||||
|
# It should have a return type annotation, otherwise the compiled return type will probably end up as a giant union.
|
||||||
|
#
|
||||||
|
# ```
|
||||||
|
# abstract_stub abstract def stubbed_method : String
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# Stubbed methods will call `#_spectator_find_stub` with the method call information.
|
||||||
private macro abstract_stub(method)
|
private macro abstract_stub(method)
|
||||||
{% raise "abstract_stub requires a method definition" if !method.is_a?(Def) %}
|
{% raise "abstract_stub requires a method definition" if !method.is_a?(Def) %}
|
||||||
{% raise "Cannot stub method with reserved keyword as name - #{method.name}" if ::Spectator::DSL::RESERVED_KEYWORDS.includes?(method.name.symbolize) %}
|
{% raise "Cannot stub method with reserved keyword as name - #{method.name}" if ::Spectator::DSL::RESERVED_KEYWORDS.includes?(method.name.symbolize) %}
|
||||||
|
@ -72,6 +139,7 @@ module Spectator
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Utility for defining a stubbed method and a fallback.
|
||||||
private macro inject_stub(method)
|
private macro inject_stub(method)
|
||||||
{{method}}
|
{{method}}
|
||||||
stub {{method}}
|
stub {{method}}
|
||||||
|
|
9
src/spectator/mocks/stubbed_name.cr
Normal file
9
src/spectator/mocks/stubbed_name.cr
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
module Spectator
|
||||||
|
# Defines the name of a double or mock.
|
||||||
|
#
|
||||||
|
# When present on a stubbed type, this annotation indicates its name in output such as exceptions.
|
||||||
|
# Must have one argument - the name of the double or mock.
|
||||||
|
# This can be a symbol, string literal, or type name.
|
||||||
|
annotation StubbedName
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue