From 24eec64d649c257c8d69e7b9fbd7dd29b7dbbf50 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Tue, 12 Jul 2022 20:09:18 -0600 Subject: [PATCH] Check for leaks on class mocks and doubles --- .../dsl/mocks/expect_receive_spec.cr | 111 ++++++++++++++++++ .../spectator/dsl/mocks/have_received_spec.cr | 89 ++++++++++++++ 2 files changed, 200 insertions(+) diff --git a/spec/spectator/dsl/mocks/expect_receive_spec.cr b/spec/spectator/dsl/mocks/expect_receive_spec.cr index d4cb7c5..0a0242b 100644 --- a/spec/spectator/dsl/mocks/expect_receive_spec.cr +++ b/spec/spectator/dsl/mocks/expect_receive_spec.cr @@ -11,6 +11,62 @@ Spectator.describe "Deferred stub expectation DSL" do let(dbl) { double(:dbl) } + # Ensure invocations don't leak between examples. + pre_condition { expect(dbl).to_not have_received(:foo), "Leaked method calls from previous examples" } + + it "matches when a message is received" do + expect(dbl).to receive(:foo) + dbl.foo + end + + it "returns the correct value" do + expect(dbl).to receive(:value).and_return(42) + expect(dbl.value).to eq(42) + end + + it "matches when a message isn't received" do + expect(dbl).to_not receive(:foo) + end + + it "matches when a message is received with matching arguments" do + expect(dbl).to receive(:foo).with(:bar) + dbl.foo(:bar) + end + + it "matches when a message without arguments is received" do + expect(dbl).to_not receive(:foo).with(:bar) + dbl.foo + end + + it "matches when a message without arguments isn't received" do + expect(dbl).to_not receive(:foo).with(:bar) + end + + it "matches when a message with arguments isn't received" do + expect(dbl).to_not receive(:foo).with(:baz) + dbl.foo(:bar) + end + end + + context "with a class double" do + double(:dbl) do + # Ensure the original is never called. + abstract_stub def self.foo : Nil + end + + abstract_stub def self.foo(arg) : Nil + end + + abstract_stub def self.value : Int32 + 42 + end + end + + let(dbl) { class_double(:dbl) } + + # Ensure invocations don't leak between examples. + pre_condition { expect(dbl).to_not have_received(:foo), "Leaked method calls from previous examples" } + it "matches when a message is received" do expect(dbl).to receive(:foo) dbl.foo @@ -55,6 +111,61 @@ Spectator.describe "Deferred stub expectation DSL" do let(fake) { mock(MyClass) } + # Ensure invocations don't leak between examples. + pre_condition { expect(fake).to_not have_received(:foo), "Leaked method calls from previous examples" } + + it "matches when a message is received" do + expect(fake).to receive(:foo).and_return(42) + fake.foo(:bar) + end + + it "returns the correct value" do + expect(fake).to receive(:foo).and_return(42) + expect(fake.foo).to eq(42) + end + + it "matches when a message isn't received" do + expect(fake).to_not receive(:foo) + end + + it "matches when a message is received with matching arguments" do + expect(fake).to receive(:foo).with(:bar).and_return(42) + fake.foo(:bar) + end + + it "matches when a message without arguments is received" do + expect(fake).to_not receive(:foo).with(:bar).and_return(42) + fake.foo + end + + it "matches when a message without arguments is received" do + expect(fake).to_not receive(:foo).with(:bar) + end + + it "matches when a message with arguments isn't received" do + expect(fake).to_not receive(:foo).with(:baz).and_return(42) + fake.foo(:bar) + end + end + + context "with a class mock" do + class MyClass + def self.foo : Int32 + 42 + end + + def self.foo(arg) : Int32 + 42 + end + end + + mock(MyClass) + + let(fake) { class_mock(MyClass) } + + # Ensure invocations don't leak between examples. + pre_condition { expect(fake).to_not have_received(:foo), "Leaked method calls from previous examples" } + it "matches when a message is received" do expect(fake).to receive(:foo).and_return(42) fake.foo(:bar) diff --git a/spec/spectator/dsl/mocks/have_received_spec.cr b/spec/spectator/dsl/mocks/have_received_spec.cr index a69752c..f5f40f1 100644 --- a/spec/spectator/dsl/mocks/have_received_spec.cr +++ b/spec/spectator/dsl/mocks/have_received_spec.cr @@ -6,6 +6,54 @@ Spectator.describe "Stubbable receiver DSL" do let(dbl) { double(:dbl) } + # Ensure invocations don't leak between examples. + pre_condition { expect(dbl).to_not have_received(:foo), "Leaked method calls from previous examples" } + + it "matches when a message is received" do + dbl.foo + expect(dbl).to have_received(:foo) + end + + it "matches when a message isn't received" do + expect(dbl).to_not have_received(:foo) + end + + it "matches when a message is received with matching arguments" do + dbl.foo(:bar) + expect(dbl).to have_received(:foo).with(:bar) + end + + it "matches when a message without arguments is received" do + dbl.foo + expect(dbl).to_not have_received(:foo).with(:bar) + end + + it "matches when a message without arguments isn't received" do + expect(dbl).to_not have_received(:foo).with(:bar) + end + + it "matches when a message with arguments isn't received" do + dbl.foo(:bar) + expect(dbl).to_not have_received(:foo).with(:baz) + end + end + + context "with a class double" do + double(:dbl) do + stub def self.foo + 42 + end + + stub def self.foo(arg) + 42 + end + end + + let(dbl) { class_double(:dbl) } + + # Ensure invocations don't leak between examples. + pre_condition { expect(dbl).to_not have_received(:foo), "Leaked method calls from previous examples" } + it "matches when a message is received" do dbl.foo expect(dbl).to have_received(:foo) @@ -44,6 +92,47 @@ Spectator.describe "Stubbable receiver DSL" do let(fake) { mock(MyClass) } + # Ensure invocations don't leak between examples. + pre_condition { expect(fake).to_not have_received(:foo), "Leaked method calls from previous examples" } + + it "matches when a message is received" do + fake.foo(:bar) + expect(fake).to have_received(:foo) + end + + it "matches when a message isn't received" do + expect(fake).to_not have_received(:foo) + end + + it "matches when a message is received with matching arguments" do + fake.foo(:bar) + expect(fake).to have_received(:foo).with(:bar) + end + + it "matches when a message without arguments is received" do + expect(fake).to_not have_received(:foo).with(:bar) + end + + it "matches when a message with arguments isn't received" do + fake.foo(:bar) + expect(fake).to_not have_received(:foo).with(:baz) + end + end + + context "with a class mock" do + class MyClass + def self.foo(arg) : Int32 + 42 + end + end + + mock(MyClass) + + let(fake) { class_mock(MyClass) } + + # Ensure invocations don't leak between examples. + pre_condition { expect(fake).to_not have_received(:foo), "Leaked method calls from previous examples" } + it "matches when a message is received" do fake.foo(:bar) expect(fake).to have_received(:foo)