From 42aaae7908b3f077fd76fa2ebf5cbed0610ece06 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Sat, 2 Nov 2019 19:58:47 -0600 Subject: [PATCH] Some initial work on mocks --- src/spectator/dsl/mocks.cr | 25 ++++++++++++ src/spectator/stubs.cr | 82 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/spectator/stubs.cr diff --git a/src/spectator/dsl/mocks.cr b/src/spectator/dsl/mocks.cr index afcb681..d45ec2d 100644 --- a/src/spectator/dsl/mocks.cr +++ b/src/spectator/dsl/mocks.cr @@ -1,6 +1,7 @@ require "../double" require "../generic_method_stub" require "../open_mock" +require "../stubs" module Spectator::DSL macro double(name, &block) @@ -19,6 +20,30 @@ module Spectator::DSL {% end %} end + macro mock(name, &block) + {% if block.is_a?(Nop) %} + {{name}}.new.tap do |%inst| + %inst.spectator_test = self + end + {% else %} + {% resolved = name.resolve + type = if resolved < Reference + :class + elsif resolved < Value + :struct + else + :module + end + %} + {{type.id}} ::{{resolved.id}} + include ::Spectator::Stubs + + {{block.body}} + end + {% end %} + {% debug %} + end + def allow(double : ::Spectator::Double) OpenMock.new(double) end diff --git a/src/spectator/stubs.cr b/src/spectator/stubs.cr new file mode 100644 index 0000000..acb68ff --- /dev/null +++ b/src/spectator/stubs.cr @@ -0,0 +1,82 @@ +module Spectator + module Stubs + private macro stub(definition, &block) + {% + name = nil + params = nil + args = nil + body = nil + if definition.is_a?(Call) # stub foo { :bar } + name = definition.name.id + params = definition.args + args = params.map { |p| p.is_a?(TypeDeclaration) ? p.var : p.id } + body = definition.block.is_a?(Nop) ? block : definition.block + elsif definition.is_a?(TypeDeclaration) # stub foo : Symbol + name = definition.var + params = [] of MacroId + args = [] of MacroId + body = block + else + raise "Unrecognized stub format" + end + %} + def {{name}}{% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + %method + end + + {% if name.ends_with?('=') && name.id != "[]=" %} + def {{name}}(arg) + call = ::Spectator::GenericMethodCall.new({{name.symbolize}}, {arg}, NamedTuple.new) + @spectator_stub_calls << call + stub = @spectator_stubs.find(&.callable?(call)) + if stub + stub.as(::Spectator::GenericMethodStub(typeof(%method(arg)))).call(call) + else + %method(arg) + end + end + {% else %} + def {{name}}(*args, **options){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + call = ::Spectator::GenericMethodCall.new({{name.symbolize}}, args, options) + @spectator_stub_calls << call + stub = @spectator_stubs.find(&.callable?(call)) + if stub + stub.as(::Spectator::GenericMethodStub(typeof(%method(*args, **options)))).call(call) + else + %method(*args, **options) + end + end + + {% if name != "[]=" %} + def {{name}}(*args, **options){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + call = ::Spectator::GenericMethodCall.new({{name.symbolize}}, args, options) + @spectator_stub_calls << call + stub = @spectator_stubs.find(&.callable?(call)) + if stub + stub.as(::Spectator::GenericMethodStub(typeof(%method(*args, **options) { |*yield_args| yield *yield_args }))).call(call) + else + %method(*args, **options) do |*yield_args| + yield *yield_args + end + end + end + {% end %} + {% end %} + + def %method({{params.splat}}){% if definition.is_a?(TypeDeclaration) %} : {{definition.type}}{% end %} + {% if body && !body.is_a?(Nop) %} + {{body.body}} + {% else %} + raise "Stubbed method called without being allowed" + # This code shouldn't be reached, but makes the compiler happy to have a matching type. + {% if definition.is_a?(TypeDeclaration) %} + %x = uninitialized {{definition.type}} + {% else %} + nil + {% end %} + {% end %} + end + {% debug %} + end + end +end