Inconsistencies with union types when stubbing.
A couple of inconsistencies;
-
When a method returns a specific type, that type is expected irrespective of whether said method has an explicit union type return definition. This breaks when mocking and
allow
ing for a different return type (of the union type defined). This might be intentional, the case in which it's tripped up for me is where mock objects are given as dependencies which contain canned responses of a certain type. I'm getting around it in a similar way to how#alt1_call
works where#call
does not. -
Having an
allow
which breaks, defined inbefore_each
, seems to get in the way of otherallow
s defined in this samebefore_each
.
Bar
#call
✅ invokes Foo#call
✅ invokes Foo#alt1_call
✅ invokes Foo#alt2_call
with an explicit return of nil
❌ should invoke Foo#call?
✅ invokes Foo#alt1_call
✅ invokes Foo#alt2_call
with returns set in before_each for all calls
❌ should invoke Foo#call?
❌ should invoke Foo#alt1_call?
❌ should invoke Foo#alt2_call?
with returns set in before_each for alt calls only
✅ invokes Foo#alt1_call
✅ invokes Foo#alt2_call
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
class Bar
def call(a_foo)
a_foo.call("")
a_foo.alt1_call("")
a_foo.alt2_call("")
end
end
Spectator.describe Bar do
mock Foo do
stub call(str : String) { "" }
stub alt1_call(str : String) { "" }
stub alt2_call(str : String) { "" }
end
let(:foo) { Foo.new}
subject(:call) { described_class.new.call(foo) }
describe "#call" do
it "invokes Foo#call" do
call
expect(foo).to have_received(:call)
end
it "invokes Foo#alt1_call" do
call
expect(foo).to have_received(:alt1_call)
end
it "invokes Foo#alt2_call" do
call
expect(foo).to have_received(:alt2_call)
end
describe "with an explicit return of nil" do
it "should invoke Foo#call?" do
allow(foo).to receive(:call).and_return(nil)
call
expect(foo).to have_received(:call)
end
it "invokes Foo#alt1_call" do
allow(foo).to receive(:alt1_call).and_return(nil)
call
expect(foo).to have_received(:alt1_call)
end
it "invokes Foo#alt2_call" do
allow(foo).to receive(:alt2_call).and_return(nil)
call
expect(foo).to have_received(:alt2_call)
end
end
describe "with returns set in before_each for all calls" do
before_each do
allow(foo).to receive(:call).and_return(nil)
allow(foo).to receive(:alt1_call).and_return(nil)
allow(foo).to receive(:alt2_call).and_return(nil)
end
it "should invoke Foo#call?" do
call
expect(foo).to have_received(:call)
end
it "should invoke Foo#alt1_call?" do
call
expect(foo).to have_received(:alt1_call)
end
it "should invoke Foo#alt2_call?" do
call
expect(foo).to have_received(:alt2_call)
end
end
describe "with returns set in before_each for alt calls only" do
before_each do
allow(foo).to receive(:alt1_call).and_return(nil)
allow(foo).to receive(:alt2_call).and_return(nil)
end
it "invokes Foo#alt1_call" do
call
expect(foo).to have_received(:alt1_call)
end
it "invokes Foo#alt2_call" do
call
expect(foo).to have_received(:alt2_call)
end
end
end
end