diff --git a/spec/spectator/mocks/double_spec.cr b/spec/spectator/mocks/double_spec.cr index 3c9130c..c93a723 100644 --- a/spec/spectator/mocks/double_spec.cr +++ b/spec/spectator/mocks/double_spec.cr @@ -114,6 +114,27 @@ Spectator.describe Spectator::Double do end end + context "with a method that uses NoReturn" do + Spectator::Double.define(NoReturnDouble) do + abstract_stub abstract def oops : NoReturn + end + + subject(dbl) { NoReturnDouble.new } + + it "raises a TypeCastError when using a value-based stub" do + stub = Spectator::ValueStub.new(:oops, nil).as(Spectator::Stub) + dbl._spectator_define_stub(stub) + expect { dbl.oops }.to raise_error(TypeCastError, /NoReturn/) + end + + it "raises when using an exception stub" do + exception = ArgumentError.new("bogus") + stub = Spectator::ExceptionStub.new(:oops, exception).as(Spectator::Stub) + dbl._spectator_define_stub(stub) + expect { dbl.oops }.to raise_error(ArgumentError, "bogus") + end + end + context "with common object methods" do subject(dbl) do EmptyDouble.new([ diff --git a/spec/spectator/mocks/mock_spec.cr b/spec/spectator/mocks/mock_spec.cr index a78b6da..0c19759 100644 --- a/spec/spectator/mocks/mock_spec.cr +++ b/spec/spectator/mocks/mock_spec.cr @@ -400,6 +400,31 @@ Spectator.describe Spectator::Mock do end end end + + context "with a method that uses NoReturn" do + abstract class Thing + abstract def oops : NoReturn + end + + Spectator::Mock.define_subtype(:class, Thing, MockThing) + + let(mock) { MockThing.new } + + after_each { mock._spectator_clear_stubs } + + it "raises a TypeCastError when using a value-based stub" do + stub = Spectator::ValueStub.new(:oops, nil).as(Spectator::Stub) + mock._spectator_define_stub(stub) + expect { mock.oops }.to raise_error(TypeCastError, /NoReturn/) + end + + it "raises when using an exception stub" do + exception = ArgumentError.new("bogus") + stub = Spectator::ExceptionStub.new(:oops, exception).as(Spectator::Stub) + mock._spectator_define_stub(stub) + expect { mock.oops }.to raise_error(ArgumentError, "bogus") + end + end end describe "#inject" do @@ -719,5 +744,32 @@ Spectator.describe Spectator::Mock do end end end + + context "with a method that uses NoReturn" do + struct ::NoReturnThing + def oops : NoReturn + raise "oops" + end + end + + Spectator::Mock.inject(:struct, ::NoReturnThing) + + let(mock) { NoReturnThing.new } + + after_each { mock._spectator_clear_stubs } + + it "raises a TypeCastError when using a value-based stub" do + stub = Spectator::ValueStub.new(:oops, nil).as(Spectator::Stub) + mock._spectator_define_stub(stub) + expect { mock.oops }.to raise_error(TypeCastError, /NoReturn/) + end + + it "raises when using an exception stub" do + exception = ArgumentError.new("bogus") + stub = Spectator::ExceptionStub.new(:oops, exception).as(Spectator::Stub) + mock._spectator_define_stub(stub) + expect { mock.oops }.to raise_error(ArgumentError, "bogus") + end + end end end diff --git a/spec/spectator/mocks/null_double_spec.cr b/spec/spectator/mocks/null_double_spec.cr index aa3b9b0..e1c0861 100644 --- a/spec/spectator/mocks/null_double_spec.cr +++ b/spec/spectator/mocks/null_double_spec.cr @@ -90,6 +90,27 @@ Spectator.describe Spectator::NullDouble do end end + context "with a method that uses NoReturn" do + Spectator::NullDouble.define(NoReturnDouble) do + abstract_stub abstract def oops : NoReturn + end + + subject(dbl) { NoReturnDouble.new } + + it "raises a TypeCastError when using a value-based stub" do + stub = Spectator::ValueStub.new(:oops, nil).as(Spectator::Stub) + dbl._spectator_define_stub(stub) + expect { dbl.oops }.to raise_error(TypeCastError, /NoReturn/) + end + + it "raises when using an exception stub" do + exception = ArgumentError.new("bogus") + stub = Spectator::ExceptionStub.new(:oops, exception).as(Spectator::Stub) + dbl._spectator_define_stub(stub) + expect { dbl.oops }.to raise_error(ArgumentError, "bogus") + end + end + context "with common object methods" do subject(dbl) do EmptyDouble.new([ diff --git a/src/spectator/mocks/stubbable.cr b/src/spectator/mocks/stubbable.cr index d998246..0734311 100644 --- a/src/spectator/mocks/stubbable.cr +++ b/src/spectator/mocks/stubbable.cr @@ -149,7 +149,7 @@ module Spectator if %stub = _spectator_find_stub(%call) # Cast the stub or return value to the expected type. # This is necessary to match the expected return type of the original method. - _spectator_cast_stub_value(%stub, %call, typeof({{original}}), {{method.return_type && method.return_type.resolve <= Nil || method.return_type.is_a?(Union) && method.return_type.types.map(&.resolve).includes?(Nil)}}) + _spectator_cast_stub_value(%stub, %call, typeof({{original}}), {{method.return_type && method.return_type.resolve != NoReturn && method.return_type.resolve <= Nil || method.return_type.is_a?(Union) && method.return_type.types.map(&.resolve).includes?(Nil)}}) else # Delegate missing stub behavior to concrete type. _spectator_stub_fallback(%call, typeof({{original}})) do @@ -241,7 +241,7 @@ module Spectator # This is necessary to match the expected return type of the original method. {% if method.return_type %} # Return type restriction takes priority since it can be a superset of the original implementation. - _spectator_cast_stub_value(%stub, %call, {{method.return_type}}, {{method.return_type.resolve <= Nil || method.return_type.is_a?(Union) && method.return_type.types.map(&.resolve).includes?(Nil)}}) + _spectator_cast_stub_value(%stub, %call, {{method.return_type}}, {{method.return_type.resolve != NoReturn && method.return_type.resolve <= Nil || method.return_type.is_a?(Union) && method.return_type.types.map(&.resolve).includes?(Nil)}}) {% elsif !method.abstract? %} # The method isn't abstract, infer the type it returns without calling it. _spectator_cast_stub_value(%stub, %call, typeof({{original}}))