mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Initial work on response constraints
This commit is contained in:
parent
de7cd90d11
commit
2adc867843
7 changed files with 85 additions and 6 deletions
|
@ -1,7 +1,7 @@
|
|||
require "../../spec_helper"
|
||||
|
||||
Spectator.describe Spectator::Double do
|
||||
subject(dbl) { Spectator::Double.new("foobar", foo: 42, bar: "baz") }
|
||||
subject(dbl) { Spectator::Double({foo: Int32, bar: String}).new("foobar", foo: 42, bar: "baz") }
|
||||
|
||||
it "responds to defined messages" do
|
||||
aggregate_failures do
|
||||
|
|
5
src/spectator/mocks/abstract_arguments.cr
Normal file
5
src/spectator/mocks/abstract_arguments.cr
Normal file
|
@ -0,0 +1,5 @@
|
|||
module Spectator
|
||||
# Untyped arguments to a method call (message).
|
||||
abstract class AbstractArguments
|
||||
end
|
||||
end
|
11
src/spectator/mocks/abstract_response.cr
Normal file
11
src/spectator/mocks/abstract_response.cr
Normal file
|
@ -0,0 +1,11 @@
|
|||
module Spectator
|
||||
# Untyped response to a method call (message).
|
||||
abstract class AbstractResponse
|
||||
# Name of the method this response is for.
|
||||
getter method : Symbol
|
||||
|
||||
# Creates the base of the response.
|
||||
def initialize(@method : Symbol)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,10 +1,12 @@
|
|||
require "./abstract_arguments"
|
||||
|
||||
module Spectator
|
||||
# Arguments used in a method call.
|
||||
#
|
||||
# Can also be used to match arguments.
|
||||
# *T* must be a `Tuple` type representing the positional arguments.
|
||||
# *NT* must be a `NamedTuple` type representing the keyword arguments.
|
||||
class Arguments(T, NT)
|
||||
class Arguments(T, NT) < AbstractArguments
|
||||
# Positional arguments.
|
||||
getter args : T
|
||||
|
||||
|
@ -20,6 +22,11 @@ module Spectator
|
|||
new(args, kwargs)
|
||||
end
|
||||
|
||||
# Constructs an instance of empty arguments.
|
||||
macro empty
|
||||
{{@type.name(generic_args: false)}}.capture
|
||||
end
|
||||
|
||||
# Constructs a string representation of the arguments.
|
||||
def to_s(io : IO) : Nil
|
||||
io << '('
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
require "./abstract_response"
|
||||
require "./unexpected_message"
|
||||
|
||||
module Spectator
|
||||
|
@ -5,10 +6,20 @@ module Spectator
|
|||
#
|
||||
# Handles all messages (method calls), but only responds to those configured.
|
||||
# Methods called that were not configured will raise `UnexpectedMessage`.
|
||||
class Double(Messages)
|
||||
# `NT` must be a type of `NamedTuple` that maps method names to their return types.
|
||||
class Double(NT)
|
||||
# Stores responses to messages (method calls).
|
||||
@responses : Array(AbstractResponse)
|
||||
|
||||
# Creates a double with pre-configures responses.
|
||||
# A *name* can be provided, otherwise it is considered an anonymous double.
|
||||
def initialize(@name : String? = nil, **@messages : **Messages)
|
||||
def initialize(@responses : Array(AbstractResponse), @name : String? = nil)
|
||||
end
|
||||
|
||||
def initialize(@name : String? = nil, **methods : **NT)
|
||||
@responses = methods.map do |method, value|
|
||||
Response.new(method, value).as(AbstractResponse)
|
||||
end
|
||||
end
|
||||
|
||||
# Utility returning the double's name as a string.
|
||||
|
@ -37,7 +48,7 @@ module Spectator
|
|||
{% if meth.double_splat %}**{{meth.double_splat}}, {% 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 type = Messages[{{meth.name.symbolize}}] %}
|
||||
\{% if type = NT[{{meth.name.symbolize}}] %}
|
||||
{% if meth.return_type %}
|
||||
\{% if type <= {{meth.return_type}} %}
|
||||
# Return type appears to match configured type.
|
||||
|
@ -70,7 +81,10 @@ module Spectator
|
|||
# Handle all methods but only respond to configured messages.
|
||||
# Raises an `UnexpectedMessage` error for non-configures messages.
|
||||
macro method_missing(call)
|
||||
\{% if Messages.keys.includes?({{call.name.symbolize}}.id) %}
|
||||
\{% if NT.keys.includes?({{call.name.symbolize}}.id) %}
|
||||
# Find a suitable response.
|
||||
call = MethodCall.capture({{call.name.symbolize}}, {{call.args.splat}})
|
||||
response = @responses.find &.===(call)
|
||||
# Return configured response.
|
||||
@messages[{{call.name.symbolize}}]
|
||||
\{% else %}
|
||||
|
|
18
src/spectator/mocks/method_call.cr
Normal file
18
src/spectator/mocks/method_call.cr
Normal file
|
@ -0,0 +1,18 @@
|
|||
require "./abstract_arguments"
|
||||
require "./arguments"
|
||||
|
||||
module Spectator
|
||||
class MethodCall
|
||||
getter method : Symbol
|
||||
|
||||
getter arguments : AbstractArguments
|
||||
|
||||
def initialize(@method : Symbol, @arguments : Arguments = Arguments.empty)
|
||||
end
|
||||
|
||||
def self.capture(method : Symbol, *args, **kwargs)
|
||||
arguments = Arguments.new(args, kwargs)
|
||||
new(method, arguments)
|
||||
end
|
||||
end
|
||||
end
|
24
src/spectator/mocks/response.cr
Normal file
24
src/spectator/mocks/response.cr
Normal file
|
@ -0,0 +1,24 @@
|
|||
require "./abstract_arguments"
|
||||
require "./abstract_response"
|
||||
|
||||
module Spectator
|
||||
class Response(T) < AbstractResponse
|
||||
# Return value.
|
||||
getter value : T
|
||||
|
||||
# Arguments the method must have been called with to provide this response.
|
||||
# Is nil when there's no constraint - only the method name must match.
|
||||
getter constraint : AbstractArguments?
|
||||
|
||||
# Creates the response.
|
||||
def initialize(@method : Symbol, @value : T, @constraint : Arguments? = nil)
|
||||
end
|
||||
|
||||
def ===(call : MethodCall)
|
||||
return false if method != call.method
|
||||
return true unless constraint = @constraint
|
||||
|
||||
constraint === call.arguments
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue