diff --git a/CHANGELOG.md b/CHANGELOG.md index 248f805..46800cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fix macro logic to support free variables, 'self', and variants on stubbed methods. [#48](https://github.com/icy-arctic-fox/spectator/issues/48) - Fix method stubs used on methods that capture blocks. +- Fix type name resolution for when using custom types in a mocked typed. ### Changed - Simplify string representation of mock-related types. diff --git a/spec/issues/gitlab_issue_51_spec.cr b/spec/issues/gitlab_issue_51_spec.cr index c809607..996af80 100644 --- a/spec/issues/gitlab_issue_51_spec.cr +++ b/spec/issues/gitlab_issue_51_spec.cr @@ -1,31 +1,33 @@ require "../spec_helper" -private class Foo - def call(str : String) : String? - "" +module GitLabIssue51 + class Foo + def call(str : String) : String? + "" + end + + def alt1_call(str : String) : String? + nil + end + + def alt2_call(str : String) : String? + [str, nil].sample + end end - def alt1_call(str : String) : String? - nil - end - - def alt2_call(str : String) : String? - [str, nil].sample + class Bar + def call(a_foo) : Nil # Must add nil restriction here, otherwise a segfault occurs from returning the result of #alt2_call. + a_foo.call("") + a_foo.alt1_call("") + a_foo.alt2_call("") + end end end -private class Bar - def call(a_foo) : Nil # Must add nil restriction here, otherwise a segfault occurs from returning the result of #alt2_call. - a_foo.call("") - a_foo.alt1_call("") - a_foo.alt2_call("") - end -end +Spectator.describe GitLabIssue51::Bar do + mock GitLabIssue51::Foo, call: "", alt1_call: "", alt2_call: "" -Spectator.describe Bar do - mock Foo, call: "", alt1_call: "", alt2_call: "" - - let(:foo) { mock(Foo) } + let(:foo) { mock(GitLabIssue51::Foo) } subject(:call) { described_class.new.call(foo) } describe "#call" do diff --git a/spec/spectator/mocks/mock_spec.cr b/spec/spectator/mocks/mock_spec.cr index 40b4bf0..8835183 100644 --- a/spec/spectator/mocks/mock_spec.cr +++ b/spec/spectator/mocks/mock_spec.cr @@ -29,8 +29,18 @@ Spectator.describe Spectator::Mock do @_spectator_invocations << :method3 "original" end + + def method4 : Thing + self + end + + def method5 : OtherThing + OtherThing.new + end end + class OtherThing; end + Spectator::Mock.define_subtype(:class, Thing, MockThing, :mock_name, method1: 123) do stub def method2 :stubbed @@ -104,6 +114,20 @@ Spectator.describe Spectator::Mock do mock.method3 expect(mock._spectator_invocations).to contain_exactly(:method3) end + + it "can reference its own type" do + new_mock = MockThing.new + stub = Spectator::ValueStub.new(:method4, new_mock) + mock._spectator_define_stub(stub) + expect(mock.method4).to be(new_mock) + end + + it "can reference other types in the original namespace" do + other = OtherThing.new + stub = Spectator::ValueStub.new(:method5, other) + mock._spectator_define_stub(stub) + expect(mock.method5).to be(other) + end end context "with an abstract class" do @@ -120,8 +144,14 @@ Spectator.describe Spectator::Mock do end abstract def method4 + + abstract def method4 : Thing + + abstract def method5 : OtherThing end + class OtherThing; end + Spectator::Mock.define_subtype(:class, Thing, MockThing, :mock_name, method2: :stubbed) do stub def method1 : Int32 # NOTE: Return type is required since one wasn't provided in the parent. 123 @@ -199,6 +229,20 @@ Spectator.describe Spectator::Mock do mock.method3 expect(mock._spectator_invocations).to contain_exactly(:method3) end + + it "can reference its own type" do + new_mock = MockThing.new + stub = Spectator::ValueStub.new(:method4, new_mock) + mock._spectator_define_stub(stub) + expect(mock.method4).to be(new_mock) + end + + it "can reference other types in the original namespace" do + other = OtherThing.new + stub = Spectator::ValueStub.new(:method5, other) + mock._spectator_define_stub(stub) + expect(mock.method5).to be(other) + end end context "with an abstract struct" do @@ -215,8 +259,14 @@ Spectator.describe Spectator::Mock do end abstract def method4 + + abstract def method4 : Thing + + abstract def method5 : OtherThing end + class OtherThing; end + Spectator::Mock.define_subtype(:struct, Thing, MockThing, :mock_name, method2: :stubbed) do stub def method1 : Int32 # NOTE: Return type is required since one wasn't provided in the parent. 123 @@ -286,6 +336,22 @@ Spectator.describe Spectator::Mock do mock.method3 expect(mock._spectator_invocations).to contain_exactly(:method3) end + + it "can reference its own type" do + mock = self.mock # FIXME: Workaround for passing by value messing with stubs. + new_mock = MockThing.new + stub = Spectator::ValueStub.new(:method4, new_mock) + mock._spectator_define_stub(stub) + expect(mock.method4).to be_a(Thing) + end + + it "can reference other types in the original namespace" do + mock = self.mock # FIXME: Workaround for passing by value messing with stubs. + other = OtherThing.new + stub = Spectator::ValueStub.new(:method5, other) + mock._spectator_define_stub(stub) + expect(mock.method5).to be(other) + end end context "class method stubs" do @@ -301,8 +367,18 @@ Spectator.describe Spectator::Mock do def self.baz(arg) yield end + + def self.thing : Thing + new + end + + def self.other : OtherThing + OtherThing.new + end end + class OtherThing; end + Spectator::Mock.define_subtype(:class, Thing, MockThing) do stub def self.foo :stub @@ -367,6 +443,20 @@ Spectator.describe Spectator::Mock do expect(restricted(mock)).to eq(:stub) end + it "can reference its own type" do + new_mock = MockThing.new + stub = Spectator::ValueStub.new(:thing, new_mock) + mock._spectator_define_stub(stub) + expect(mock.thing).to be(new_mock) + end + + it "can reference other types in the original namespace" do + other = OtherThing.new + stub = Spectator::ValueStub.new(:other, other) + mock._spectator_define_stub(stub) + expect(mock.other).to be(other) + end + describe "._spectator_clear_stubs" do before { mock._spectator_define_stub(foo_stub) } diff --git a/src/spectator/dsl/mocks.cr b/src/spectator/dsl/mocks.cr index 94a41f9..ab05636 100644 --- a/src/spectator/dsl/mocks.cr +++ b/src/spectator/dsl/mocks.cr @@ -218,24 +218,29 @@ module Spectator::DSL # end # ``` private macro def_mock(type, name = nil, **value_methods, &block) - {% # Construct a unique type name for the mock by using the number of defined types. - index = ::Spectator::DSL::Mocks::TYPES.size - mock_type_name = "Mock#{index}".id + {% resolved = type.resolve + # Construct a unique type name for the mock by using the number of defined types. + index = ::Spectator::DSL::Mocks::TYPES.size + # The type is nested under the original so that any type names from the original can be resolved. + mock_type_name = "Mock#{index}".id - # Store information about how the mock is defined and its context. - # This is important for constructing an instance of the mock later. - ::Spectator::DSL::Mocks::TYPES << {type.id.symbolize, @type.name(generic_args: false).symbolize, mock_type_name.symbolize} + # Store information about how the mock is defined and its context. + # This is important for constructing an instance of the mock later. + ::Spectator::DSL::Mocks::TYPES << {type.id.symbolize, @type.name(generic_args: false).symbolize, "::#{resolved.name}::#{mock_type_name}".id.symbolize} - resolved = type.resolve - base = if resolved.class? - :class - elsif resolved.struct? - :struct - else - :module - end %} + base = if resolved.class? + :class + elsif resolved.struct? + :struct + else + :module + end %} - ::Spectator::Mock.define_subtype({{base}}, {{type.id}}, {{mock_type_name}}, {{name}}, {{**value_methods}}) {{block}} + {% begin %} + {{base.id}} ::{{resolved.name}} + ::Spectator::Mock.define_subtype({{base}}, {{type.id}}, {{mock_type_name}}, {{name}}, {{**value_methods}}) {{block}} + end + {% end %} end # Instantiates a mock.