Test type restrictions and ensure original methods aren't called

This commit is contained in:
Michael Miller 2022-05-28 09:18:03 -06:00
parent fa89fa5550
commit 6060b3cd10
No known key found for this signature in database
GPG key ID: AC78B32D30CE34A2

View file

@ -8,15 +8,20 @@ Spectator.describe Spectator::Mock do
describe "#define_subtype" do describe "#define_subtype" do
context "with a concrete class" do context "with a concrete class" do
class Thing class Thing
getter _spectator_calls = [] of Symbol
def method1 def method1
@_spectator_calls << :method1
42 42
end end
def method2 def method2
@_spectator_calls << :method2
:original :original
end end
def method3 def method3
@_spectator_calls << :method3
"original" "original"
end end
end end
@ -68,15 +73,33 @@ Spectator.describe Spectator::Mock do
mock._spectator_define_stub(stub) mock._spectator_define_stub(stub)
expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name." expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name."
end end
def restricted(thing : Thing)
thing.method1
end
it "can be used in type restricted methods" do
expect(restricted(mock)).to eq(123)
end
it "does not call the original method when stubbed" do
mock.method1
mock.method2
mock.method3
expect(mock._spectator_calls).to contain_exactly(:method3)
end
end end
context "with an abstract class" do context "with an abstract class" do
abstract class Thing abstract class Thing
getter _spectator_calls = [] of Symbol
abstract def method1 abstract def method1
abstract def method2 : Symbol abstract def method2 : Symbol
def method3 def method3
@_spectator_calls << :method3
"original" "original"
end end
@ -134,15 +157,33 @@ Spectator.describe Spectator::Mock do
mock._spectator_define_stub(stub) mock._spectator_define_stub(stub)
expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name." expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name."
end end
def restricted(thing : Thing)
thing.method1
end
it "can be used in type restricted methods" do
expect(restricted(mock)).to eq(123)
end
it "does not call the original method when stubbed" do
mock.method1
mock.method2
mock.method3
expect(mock._spectator_calls).to contain_exactly(:method3)
end
end end
context "with an abstract struct" do context "with an abstract struct" do
abstract struct Thing abstract struct Thing
getter _spectator_calls = [] of Symbol
abstract def method1 abstract def method1
abstract def method2 : Symbol abstract def method2 : Symbol
def method3 def method3
@_spectator_calls << :method3
"original" "original"
end end
@ -202,24 +243,47 @@ Spectator.describe Spectator::Mock do
mock._spectator_define_stub(stub) mock._spectator_define_stub(stub)
expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name." expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name."
end end
def restricted(thing : Thing)
thing.method1
end
it "can be used in type restricted methods" do
expect(restricted(mock)).to eq(123)
end
it "does not call the original method when stubbed" do
mock = self.mock # FIXME: Workaround for passing by value messing with stubs.
mock.method1
mock.method2
mock.method3
expect(mock._spectator_calls).to contain_exactly(:method3)
end
end end
end end
describe "#inject" do describe "#inject" do
context "with a class" do context "with a class" do
class MockedClass class MockedClass
getter method1 = 42 getter _spectator_calls = [] of Symbol
getter method1 do
@_spectator_calls << :method1
42
end
def method2 def method2
@_spectator_calls << :method2
:original :original
end end
def method3 def method3
@_spectator_calls << :method3
"original" "original"
end end
def instance_variables def instance_variables
[{{@type.instance_vars.map(&.name.symbolize).splat}}] {% begin %}[{{@type.instance_vars.map(&.name.symbolize).splat}}]{% end %}
end end
end end
@ -264,11 +328,12 @@ Spectator.describe Spectator::Mock do
end end
it "doesn't change the size of an instance" do it "doesn't change the size of an instance" do
expect(instance_sizeof(MockedClass)).to eq(8) # sizeof(Int32) + sizeof(TypeID) size = sizeof(Int64) + sizeof(Int32?) + sizeof(Array(Symbol)) # TypeID + Int32? + _spectator_calls
expect(instance_sizeof(MockedClass)).to eq(size)
end end
it "doesn't affect instance variables" do it "doesn't affect instance variables" do
expect(mock.instance_variables).to eq([:method1]) expect(mock.instance_variables).to contain_exactly(:method1, :_spectator_calls)
end end
it "sets the mock name" do it "sets the mock name" do
@ -277,22 +342,47 @@ Spectator.describe Spectator::Mock do
mock._spectator_define_stub(stub) mock._spectator_define_stub(stub)
expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name." expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name."
end end
def restricted(thing : MockedClass)
thing.method1
end
it "can be used in type restricted methods" do
expect(restricted(mock)).to eq(123)
end
it "does not call the original method when stubbed" do
mock.method1
mock.method2
mock.method3
expect(mock._spectator_calls).to contain_exactly(:method3)
end
end end
context "with a struct" do context "with a struct" do
struct MockedStruct struct MockedStruct
getter method1 = 42 # Using a class variable instead of an instance variable to prevent mutability problems with stub lookup.
class_getter _spectator_calls = [] of Symbol
@method1 = 42
def method1
@@_spectator_calls << :method1
@method1
end
def method2 def method2
@@_spectator_calls << :method2
:original :original
end end
def method3 def method3
@@_spectator_calls << :method3
"original" "original"
end end
def instance_variables def instance_variables
[{{@type.instance_vars.map(&.name.symbolize).splat}}] {% begin %}[{{@type.instance_vars.map(&.name.symbolize).splat}}]{% end %}
end end
end end
@ -306,6 +396,7 @@ Spectator.describe Spectator::Mock do
# Necessary to clear stubs to prevent leakages between tests. # Necessary to clear stubs to prevent leakages between tests.
after_each { mock._spectator_clear_stubs } after_each { mock._spectator_clear_stubs }
after_each { MockedStruct._spectator_calls.clear }
it "overrides responses from methods with keyword arguments" do it "overrides responses from methods with keyword arguments" do
expect(mock.method1).to eq(123) expect(mock.method1).to eq(123)
@ -324,11 +415,11 @@ Spectator.describe Spectator::Mock do
end end
it "doesn't change the size of an instance" do it "doesn't change the size of an instance" do
expect(sizeof(MockedStruct)).to eq(4) # sizeof(Int32) expect(sizeof(MockedStruct)).to eq(sizeof(Int32))
end end
it "doesn't affect instance variables" do it "doesn't affect instance variables" do
expect(mock.instance_variables).to eq([:method1]) expect(mock.instance_variables).to contain_exactly(:method1)
end end
it "sets the mock name" do it "sets the mock name" do
@ -337,6 +428,21 @@ Spectator.describe Spectator::Mock do
mock._spectator_define_stub(stub) mock._spectator_define_stub(stub)
expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name." expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name."
end end
def restricted(thing : MockedStruct)
thing.method1
end
it "can be used in type restricted methods" do
expect(restricted(mock)).to eq(123)
end
it "does not call the original method when stubbed" do
mock.method1
mock.method2
mock.method3
expect(MockedStruct._spectator_calls).to contain_exactly(:method3)
end
end end
end end
end end