Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 70 additions & 2 deletions spec/mocks_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class Example
"hey, #{name}"
end

def say_hello_type(name : String)
"hey, #{name} String"
end

def say_nothing
nil
end
Expand Down Expand Up @@ -51,6 +55,7 @@ Mocks.create_mock Example do
mock self.hello_world(greeting)
mock instance.say_hello
mock instance.say_hello(name)
mock instance.say_hello_type(name : String)
mock instance.greeting = value
mock instance == value
end
Expand All @@ -72,6 +77,7 @@ Mocks.create_double "OtherExample" do
mock self.hello_world(greeting).as(String)
mock instance.say_hello.as(String)
mock instance.say_hello(name).as(String)
mock instance.say_hello_type(name : String).as(String)
mock (instance.greeting = value), String
end

Expand Down Expand Up @@ -108,6 +114,7 @@ describe Mocks do
it "has original value when there is no mocking" do
Example.new.say_hello.should eq("hey!")
Example.new.say_hello("john").should eq("hey, john")
Example.new.say_hello_type("mike").should eq("hey, mike String")
end

it "has mocked value when there was some mocking" do
Expand All @@ -119,6 +126,10 @@ describe Mocks do

allow(example).to receive(say_hello).and_return("aloha!")
example.say_hello.should eq("aloha!")

allow(example).to receive(say_hello_type("world")).and_return("hello, world!")
example.say_hello_type("world").should eq("hello, world!")
example.say_hello_type("james").should eq("hey, james String")
end

it "affects only the same instance" do
Expand Down Expand Up @@ -161,12 +172,17 @@ describe Mocks do
it "returns value of valid type when not mocked" do
example = Example.new
typeof(example.say_hello("world")).should eq(String)

typeof(example.say_hello_type("world")).should eq(String)
end

it "returns value of valid type when mocked" do
example = Example.new
allow(example).to receive(say_hello("world")).and_return("hello, test")
typeof(example.say_hello("world")).should eq(String)

allow(example).to receive(say_hello_type("world")).and_return("hello, test")
typeof(example.say_hello_type("world")).should eq(String)
end

it "does not fail if stubbed value is nil" do
Expand All @@ -177,6 +193,12 @@ describe Mocks do
expect_raises Mocks::UnexpectedMethodCall, expected_message do
example.say_hello("world")
end

expected_message = "#{example.inspect} attempted to return stubbed value of wrong type, while calling say_hello_type[\"world\"]. Expected type: String. Actual type: Nil"
allow(example).to receive(say_hello_type("world")).and_return(nil)
expect_raises Mocks::UnexpectedMethodCall, expected_message do
example.say_hello_type("world")
end
end

it "does not fail if stubbed value is nil and the type of method is nil" do
Expand All @@ -195,6 +217,9 @@ describe Mocks do

allow(example).to receive(say_hello).and_return("halo")
example.say_hello.should eq("halo")

allow(example).to receive(say_hello_type("john")).and_return("halo, john string")
example.say_hello_type("john").should eq("halo, john string")
end

it "defines good default #==" do
Expand Down Expand Up @@ -229,13 +254,21 @@ describe Mocks do
it "allows to define stubs as an argument" do
example = Mocks.double("OtherExample", returns(say_hello("world"), "hello, world!"))
example.say_hello("world").should eq("hello, world!")

example = Mocks.double("OtherExample", returns(say_hello_type("world"), "hello, world!!"))
example.say_hello_type("world").should eq("hello, world!!")
end

it "allows for allow syntax" do
example = Mocks.double("OtherExample", returns(say_hello("world"), "hello, world!"))
allow(example).to receive(say_hello("john")).and_return("hi, john")
example.say_hello("world").should eq("hello, world!")
example.say_hello("john").should eq("hi, john")

example = Mocks.double("OtherExample", returns(say_hello_type("world"), "hello, world! Stging!"))
allow(example).to receive(say_hello_type("john")).and_return("hi, john!!")
example.say_hello_type("world").should eq("hello, world! Stging!")
example.say_hello_type("john").should eq("hi, john!!")
end

it "allows to stub class methods" do
Expand All @@ -253,26 +286,35 @@ describe Mocks do
it "allows to define multiple stubs as an argument list" do
example = Mocks.double("OtherExample",
returns(say_hello("world"), "hello, world!"),
returns(say_hello_type("world"), "hello, world!!"),
returns(instance.greeting=("hi"), "yes, it is hi"))

example.say_hello("world").should eq("hello, world!")
example.say_hello_type("world").should eq("hello, world!!")
(example.greeting = "hi").should eq("yes, it is hi")
end

it "raises UnexpectedMethodCall when there is no such stub" do
example = Mocks.double("OtherExample",
returns(say_hello("world"), "hello, world!"),
returns(say_hello_type("world"), "hello, world!!"),
returns(instance.greeting=("hi"), "yes, it is hi"))

expected_message = %{#{example.inspect} received unexpected method call say_hello["john"]}
expect_raises Mocks::UnexpectedMethodCall, expected_message do
example.say_hello("john")
end

expected_message = %{#{example.inspect} received unexpected method call say_hello_type["john"]}
expect_raises Mocks::UnexpectedMethodCall, expected_message do
example.say_hello_type("john")
end
end

it "returns value of correct type" do
example = Mocks.double("OtherExample")
typeof(example.say_hello("world")).should eq(String)
typeof(example.say_hello_type("world!")).should eq(String)
end
end

Expand All @@ -281,36 +323,54 @@ describe Mocks do
example = Mocks.instance_double(Example)
allow(example).to receive(say_hello("jonny")).and_return("ah, jonny, there you are")
example.say_hello("jonny").should eq("ah, jonny, there you are")

allow(example).to receive(say_hello_type("jonny")).and_return("ah, jonny, there you are!")
example.say_hello_type("jonny").should eq("ah, jonny, there you are!")
end

it "can be created with stub" do
example = Mocks.instance_double(Example, returns(say_hello("james"), "Hi, James!"))
example.say_hello("james").should eq("Hi, James!")

example = Mocks.instance_double(Example, returns(say_hello_type("james"), "Hi, James! string"))
example.say_hello_type("james").should eq("Hi, James! string")
end

it "can be created with a list of stubs" do
example = Mocks.instance_double(Example,
returns(say_hello("james"), "Hi, James!"),
returns(say_hello("john"), "Oh, hey, John."))
returns(say_hello("john"), "Oh, hey, John."),
returns(say_hello_type("james"), "Hi, James! string!"),
returns(say_hello_type("john"), "Oh, hey, John. string!"))

example.say_hello("john").should eq("Oh, hey, John.")
example.say_hello("james").should eq("Hi, James!")
example.say_hello_type("john").should eq("Oh, hey, John. string!")
example.say_hello_type("james").should eq("Hi, James! string!")
end

it "raises UnexpectedMethodCall when there is no such stub" do
example = Mocks.instance_double(Example,
returns(say_hello("james"), "Hi, James!"),
returns(say_hello("john"), "Oh, hey, John."))
returns(say_hello("john"), "Oh, hey, John."),
returns(say_hello_type("james"), "Hi, James! string!"),
returns(say_hello_type("john"), "Oh, hey, John. string!"))

expected_message = "#{example.inspect} received unexpected method call say_hello[\"sarah\"]"
expect_raises Mocks::UnexpectedMethodCall, expected_message do
example.say_hello("sarah")
end

expected_message = "#{example.inspect} received unexpected method call say_hello_type[\"sarah\"]"
expect_raises Mocks::UnexpectedMethodCall, expected_message do
example.say_hello_type("sarah")
end
end

it "returns value of correct type" do
example = Mocks.instance_double(Example)
typeof(example.say_hello("world")).should eq(String)
typeof(example.say_hello_type("world")).should eq(String)
end
end

Expand Down Expand Up @@ -352,6 +412,14 @@ describe Mocks do
expect_raises Mocks::UnexpectedMethodCall, expected_message do
example.say_hello("james")
end

allow(example).to receive(say_hello_type("john")).and_return("hello, john! string!")
example.say_hello_type("john").should eq("hello, john! string!")

expected_message = "#{example.inspect} received unexpected method call say_hello_type[\"james\"]"
expect_raises Mocks::UnexpectedMethodCall, expected_message do
example.say_hello_type("james")
end
end

it "returns value of correct type" do
Expand Down
32 changes: 26 additions & 6 deletions src/macro/base_double.cr
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
module Mocks
class BaseDouble
macro _mock(method_spec, return_type = nil, sample = nil)

{% if sample %}
{% method = method_spec %}

{% if method.args.empty? %}
{% args_splat_notype = method.args.splat %}
{% elsif method.args.splat.stringify.includes?(",") %}
{% args_splat_notype = method.args.splat.stringify.split(",").map {|arg| arg.split(":")[0].strip}.join(", ") %}
{% else %}
{% args_splat_notype = method.args.splat.stringify.split(":")[0].strip %}
{% end %}

{% method_name = method.name.stringify %}
{% method_name = "self.#{method_name.id}" if method.receiver.stringify == "self" %}
{% method_name = method_name.id %}

{% if method.receiver.stringify == "self" %}
{% return_type = "typeof(typeof(#{sample}).#{method.name}(#{method.args.splat}))" %}
{% return_type = "typeof(typeof(#{sample}).#{method.name}(#{args_splat_notype.id}))" %}
{% else %}
{% return_type = "typeof(#{sample}.#{method.name}(#{method.args.splat}))".id %}
{% return_type = "typeof(#{sample}.#{method.name}(#{args_splat_notype.id}))".id %}
{% end %}
{% else %}

Expand All @@ -30,6 +39,14 @@ module Mocks
{% method = method.expressions[0] %}
{% end %}

{% if method.args.empty? %}
{% args_splat_notype = method.args.splat %}
{% elsif method.args.splat.stringify.includes?(",") %}
{% args_splat_notype = method.args.splat.stringify.split(",").map {|arg| arg.split(":")[0].strip}.join(", ") %}
{% else %}
{% args_splat_notype = method.args.splat.stringify.split(":")[0].strip %}
{% end %}

{% method_name = method.name.stringify %}
{% method_name = "self.#{method_name.id}" if method.receiver.stringify == "self" %}
{% method_name = method_name.id %}
Expand All @@ -38,17 +55,20 @@ module Mocks
def {{method_name}}({{method.args.splat}})
{% if method.args.empty? %}
{% args_tuple = "nil".id %}
{% args_tuple_notype = "nil".id %}
{% else %}
{% args_tuple = "{#{method.args.splat}}".id %}
{% args_tuple_notype = "{#{args_splat_notype.id}}".id %}
{% end %}

{% args_types = "typeof(#{args_tuple})".id %}
{% args_notype_types = "typeof(#{args_tuple_notype})".id %}

::Mocks::Registry.remember({{args_types}})

%method = ::Mocks::Registry({{args_types}}).for(@@name).fetch_method("{{method_name}}")
%method = ::Mocks::Registry({{args_notype_types}}).for(@@name).fetch_method("{{method_name}}")

%result = %method.call(::Mocks::Registry::ObjectId.build(self), {{args_tuple}})
%result = %method.call(::Mocks::Registry::ObjectId.build(self), {{args_tuple_notype}})

if %result.call_original

Expand All @@ -60,7 +80,7 @@ module Mocks
{% if method.args.empty? %}
"#{self.inspect} received unexpected method call {{method_name}}[]"
{% else %}
"#{self.inspect} received unexpected method call {{method_name}}#{[{{method.args.splat}}]}"
"#{self.inspect} received unexpected method call {{method_name}}#{[{{args_splat_notype.id}}]}"
{% end %}
)

Expand All @@ -74,7 +94,7 @@ module Mocks
{% if method.args.empty? %}
"#{self.inspect} received unexpected method call {{method_name}}[]"
{% else %}
"#{self.inspect} received unexpected method call {{method_name}}#{[{{method.args.splat}}]}"
"#{self.inspect} received unexpected method call {{method_name}}#{[{{args_splat_notype.id}}]}"
{% end %}
)
end
Expand Down
15 changes: 12 additions & 3 deletions src/macro/base_mock.cr
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,26 @@ module Mocks
end

{% if method.args.empty? %}
{% args_splat_notype = method.args.splat %}
{% args_tuple = "nil".id %}
{% args_tuple_notype = "nil".id %}
{% else %}
{% if method.args.splat.stringify.includes?(",") %}
{% args_splat_notype = method.args.splat.stringify.split(",").map {|arg| arg.split(":")[0].strip}.join(", ") %}
{% else %}
{% args_splat_notype = method.args.splat.stringify.split(":")[0].strip %}
{% end %}
{% args_tuple = "{#{method.args.splat}}".id %}
{% args_tuple_notype = "{#{args_splat_notype.id}}".id %}
{% end %}

{% args_types = "typeof(#{args_tuple})".id %}
{% args_notype_types = "typeof(#{args_tuple_notype})".id %}

::Mocks::Registry.remember({{args_types}})

%method = ::Mocks::Registry({{args_types}}).for(%mock_name).fetch_method({{method_name.stringify}})
%result = %method.call(::Mocks::Registry::ObjectId.build(self), {{args_tuple}})
%method = ::Mocks::Registry({{args_notype_types}}).for(%mock_name).fetch_method({{method_name.stringify}})
%result = %method.call(::Mocks::Registry::ObjectId.build(self), {{args_tuple_notype}})

if %result.call_original
{{previous}}
Expand All @@ -50,7 +59,7 @@ module Mocks
{% if method.args.empty? %}
"#{ %type_error } {{method_name}}[]. #{ %type_error_detail }"
{% else %}
"#{ %type_error } {{method_name}}#{[{{method.args.splat}}]}. #{ %type_error_detail }"
"#{ %type_error } {{method_name}}#{[{{args_splat_notype.id}}]}. #{ %type_error_detail }"
{% end %}
)
end
Expand Down