diff --git a/spec/mocks_spec.cr b/spec/mocks_spec.cr index 4297f29..6912b82 100644 --- a/spec/mocks_spec.cr +++ b/spec/mocks_spec.cr @@ -17,6 +17,10 @@ class Example "hey, #{name}" end + def say_hello_type(name : String) + "hey, #{name} String" + end + def say_nothing nil end @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -229,6 +254,9 @@ 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 @@ -236,6 +264,11 @@ describe Mocks do 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 @@ -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 @@ -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 @@ -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 diff --git a/src/macro/base_double.cr b/src/macro/base_double.cr index a5528f1..177af0f 100644 --- a/src/macro/base_double.cr +++ b/src/macro/base_double.cr @@ -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 %} @@ -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 %} @@ -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 @@ -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 %} ) @@ -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 diff --git a/src/macro/base_mock.cr b/src/macro/base_mock.cr index d340e82..6afc349 100644 --- a/src/macro/base_mock.cr +++ b/src/macro/base_mock.cr @@ -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}} @@ -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