From e5fb4de4ae6d508a640175f653a1b6a5ded6a60d Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 16 May 2024 21:00:35 -0600 Subject: [PATCH] Remove unused specs These use types that no longer exist. The underlying types are in the Mocks shard and are tested there. --- spec/spectator/mocks/allow_spec.cr | 39 - spec/spectator/mocks/arguments_spec.cr | 284 ----- spec/spectator/mocks/double_spec.cr | 542 --------- spec/spectator/mocks/exception_stub_spec.cr | 166 --- spec/spectator/mocks/formal_arguments_spec.cr | 325 ----- spec/spectator/mocks/lazy_double_spec.cr | 352 ------ spec/spectator/mocks/method_call_spec.cr | 26 - spec/spectator/mocks/mock_spec.cr | 1062 ----------------- spec/spectator/mocks/multi_value_stub_spec.cr | 173 --- spec/spectator/mocks/null_double_spec.cr | 503 -------- spec/spectator/mocks/null_stub_spec.cr | 165 --- spec/spectator/mocks/proc_stub_spec.cr | 182 --- .../mocks/reference_mock_registry_spec.cr | 93 -- .../mocks/value_mock_registry_spec.cr | 93 -- spec/spectator/mocks/value_stub_spec.cr | 165 --- 15 files changed, 4170 deletions(-) delete mode 100644 spec/spectator/mocks/allow_spec.cr delete mode 100644 spec/spectator/mocks/arguments_spec.cr delete mode 100644 spec/spectator/mocks/double_spec.cr delete mode 100644 spec/spectator/mocks/exception_stub_spec.cr delete mode 100644 spec/spectator/mocks/formal_arguments_spec.cr delete mode 100644 spec/spectator/mocks/lazy_double_spec.cr delete mode 100644 spec/spectator/mocks/method_call_spec.cr delete mode 100644 spec/spectator/mocks/mock_spec.cr delete mode 100644 spec/spectator/mocks/multi_value_stub_spec.cr delete mode 100644 spec/spectator/mocks/null_double_spec.cr delete mode 100644 spec/spectator/mocks/null_stub_spec.cr delete mode 100644 spec/spectator/mocks/proc_stub_spec.cr delete mode 100644 spec/spectator/mocks/reference_mock_registry_spec.cr delete mode 100644 spec/spectator/mocks/value_mock_registry_spec.cr delete mode 100644 spec/spectator/mocks/value_stub_spec.cr diff --git a/spec/spectator/mocks/allow_spec.cr b/spec/spectator/mocks/allow_spec.cr deleted file mode 100644 index 090014e..0000000 --- a/spec/spectator/mocks/allow_spec.cr +++ /dev/null @@ -1,39 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::Allow do - let(dbl) { Spectator::LazyDouble.new(foo: 42) } - let(stub) { Spectator::ValueStub.new(:foo, 123) } - subject(alw) { Spectator::Allow.new(dbl) } - - describe "#to" do - it "applies a stub" do - expect { alw.to(stub) }.to change { dbl.foo }.from(42).to(123) - end - - context "leak" do - class Thing - def foo - 42 - end - end - - mock Thing - - getter(thing : Thing) { mock(Thing) } - - # Workaround type restrictions requiring a constant. - def fake - class_mock(Thing).cast(thing) - end - - specify do - expect { allow(fake).to(stub) }.to change { fake.foo }.from(42).to(123) - end - - # This example must be run after the previous (random order may break this). - it "clears the stub after the example completes" do - expect { fake.foo }.to eq(42) - end - end - end -end diff --git a/spec/spectator/mocks/arguments_spec.cr b/spec/spectator/mocks/arguments_spec.cr deleted file mode 100644 index f6a09b7..0000000 --- a/spec/spectator/mocks/arguments_spec.cr +++ /dev/null @@ -1,284 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::Arguments do - subject(arguments) { Spectator::Arguments.new({42, "foo"}, {bar: "baz", qux: 123}) } - - it "stores the arguments" do - expect(arguments).to have_attributes( - args: {42, "foo"}, - kwargs: {bar: "baz", qux: 123} - ) - end - - describe ".capture" do - subject { Spectator::Arguments.capture(42, "foo", bar: "baz", qux: 123) } - - it "stores the arguments and keyword arguments" do - is_expected.to have_attributes(args: {42, "foo"}, kwargs: {bar: "baz", qux: 123}) - end - end - - describe "#[](index)" do - it "returns a positional argument" do - aggregate_failures do - expect(arguments[0]).to eq(42) - expect(arguments[1]).to eq("foo") - end - end - end - - describe "#[](symbol)" do - it "returns a keyword argument" do - aggregate_failures do - expect(arguments[:bar]).to eq("baz") - expect(arguments[:qux]).to eq(123) - end - end - end - - describe "#to_s" do - subject { arguments.to_s } - - it "formats the arguments" do - is_expected.to eq("(42, \"foo\", bar: \"baz\", qux: 123)") - end - - context "when empty" do - let(arguments) { Spectator::Arguments.none } - - it "returns (no args)" do - is_expected.to eq("(no args)") - end - end - end - - describe "#==" do - subject { arguments == other } - - context "with Arguments" do - context "with equal arguments" do - let(other) { arguments } - - it { is_expected.to be_true } - end - - context "with different arguments" do - let(other) { Spectator::Arguments.new({123, :foo, "bar"}, {opt: "foobar"}) } - - it { is_expected.to be_false } - end - - context "with the same kwargs in a different order" do - let(other) { Spectator::Arguments.new(arguments.args, {qux: 123, bar: "baz"}) } - - it { is_expected.to be_true } - end - - context "with a missing kwarg" do - let(other) { Spectator::Arguments.new(arguments.args, {bar: "baz"}) } - - it { is_expected.to be_false } - end - - context "with an extra kwarg" do - let(other) { Spectator::Arguments.new(arguments.args, {bar: "baz", qux: 123, extra: 0}) } - - it { is_expected.to be_false } - end - end - - context "with FormalArguments" do - context "with equal arguments" do - let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {bar: "baz", qux: 123}) } - - it { is_expected.to be_true } - end - - context "with different arguments" do - let(other) { Spectator::FormalArguments.new({arg1: 123, arg2: :foo, arg3: "bar"}, {opt: "foobar"}) } - - it { is_expected.to be_false } - end - - context "with the same kwargs in a different order" do - let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {qux: 123, bar: "baz"}) } - - it { is_expected.to be_true } - end - - context "with a missing kwarg" do - let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {bar: "baz"}) } - - it { is_expected.to be_false } - end - - context "with an extra kwarg" do - let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {bar: "baz", qux: 123, extra: 0}) } - - it { is_expected.to be_false } - end - - context "with different splat arguments" do - let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {1, 2, 3}, {bar: "baz", qux: 123}) } - - it { is_expected.to be_false } - end - - context "with mixed positional tuple types" do - let(other) { Spectator::FormalArguments.new({arg1: 42}, :splat, {"foo"}, {bar: "baz", qux: 123}) } - - it { is_expected.to be_true } - end - end - end - - describe "#===" do - subject { pattern === arguments } - - context "with Arguments" do - context "with equal arguments" do - let(pattern) { arguments } - - it { is_expected.to be_true } - end - - context "with matching arguments" do - let(pattern) { Spectator::Arguments.new({Int32, /foo/}, {bar: /baz/, qux: Int32}) } - - it { is_expected.to be_true } - end - - context "with non-matching arguments" do - let(pattern) { Spectator::Arguments.new({Float64, /bar/}, {bar: /foo/, qux: "123"}) } - - it { is_expected.to be_false } - end - - context "with different arguments" do - let(pattern) { Spectator::Arguments.new({123, :foo, "bar"}, {opt: "foobar"}) } - - it { is_expected.to be_false } - end - - context "with the same kwargs in a different order" do - let(pattern) { Spectator::Arguments.new(arguments.args, {qux: Int32, bar: /baz/}) } - - it { is_expected.to be_true } - end - - context "with an additional kwarg" do - let(pattern) { Spectator::Arguments.new(arguments.args, {bar: /baz/}) } - - it { is_expected.to be_true } - end - - context "with a missing kwarg" do - let(pattern) { Spectator::Arguments.new(arguments.args, {bar: /baz/, qux: Int32, extra: 0}) } - - it { is_expected.to be_false } - end - end - - context "with FormalArguments" do - let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {bar: "baz", qux: 123}) } - - context "with equal arguments" do - let(pattern) { Spectator::Arguments.new({42, "foo"}, {bar: "baz", qux: 123}) } - - it { is_expected.to be_true } - end - - context "with matching arguments" do - let(pattern) { Spectator::Arguments.new({Int32, /foo/}, {bar: /baz/, qux: Int32}) } - - it { is_expected.to be_true } - end - - context "with non-matching arguments" do - let(pattern) { Spectator::Arguments.new({Float64, /bar/}, {bar: /foo/, qux: "123"}) } - - it { is_expected.to be_false } - end - - context "with different arguments" do - let(pattern) { Spectator::Arguments.new({123, :foo, "bar"}, {opt: "foobar"}) } - - it { is_expected.to be_false } - end - - context "with the same kwargs in a different order" do - let(pattern) { Spectator::Arguments.new(arguments.positional, {qux: Int32, bar: /baz/}) } - - it { is_expected.to be_true } - end - - context "with an additional kwarg" do - let(pattern) { Spectator::Arguments.new(arguments.positional, {bar: /baz/}) } - - it { is_expected.to be_true } - end - - context "with a missing kwarg" do - let(pattern) { Spectator::Arguments.new(arguments.positional, {bar: /baz/, qux: Int32, extra: 0}) } - - it { is_expected.to be_false } - end - - context "with different splat arguments" do - let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {1, 2, 3}, super.kwargs) } - let(pattern) { Spectator::Arguments.new({Int32, /foo/, 5}, arguments.kwargs) } - - it { is_expected.to be_false } - end - - context "with matching mixed positional tuple types" do - let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {1, 2, 3}, super.kwargs) } - let(pattern) { Spectator::Arguments.new({Int32, /foo/, 1, 2, 3}, arguments.kwargs) } - - it { is_expected.to be_true } - end - - context "with non-matching mixed positional tuple types" do - let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {1, 2, 3}, super.kwargs) } - let(pattern) { Spectator::Arguments.new({Float64, /bar/, 3, 2, Symbol}, arguments.kwargs) } - - it { is_expected.to be_false } - end - - context "with matching args spilling over into splat and mixed positional tuple types" do - let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) } - let(pattern) { Spectator::Arguments.capture(Int32, /foo/, Symbol, Symbol, :z, bar: /baz/, qux: Int32) } - - it { is_expected.to be_true } - end - - context "with non-matching args spilling over into splat and mixed positional tuple types" do - let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) } - let(pattern) { Spectator::Arguments.capture(Float64, /bar/, Symbol, String, :z, bar: /foo/, qux: Int32) } - - it { is_expected.to be_false } - end - - context "with matching mixed named positional and keyword arguments" do - let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) } - let(pattern) { Spectator::Arguments.capture(/foo/, Symbol, :y, Symbol, arg1: Int32, bar: /baz/, qux: 123) } - - it { is_expected.to be_true } - end - - context "with non-matching mixed named positional and keyword arguments" do - let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) } - let(pattern) { Spectator::Arguments.capture(5, Symbol, :z, Symbol, arg2: /foo/, bar: /baz/, qux: Int32) } - - it { is_expected.to be_false } - end - - context "with non-matching mixed named positional and keyword arguments" do - let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) } - let(pattern) { Spectator::Arguments.capture(/bar/, String, :y, Symbol, arg1: 0, bar: /foo/, qux: Float64) } - - it { is_expected.to be_false } - end - end - end -end diff --git a/spec/spectator/mocks/double_spec.cr b/spec/spectator/mocks/double_spec.cr deleted file mode 100644 index d195e26..0000000 --- a/spec/spectator/mocks/double_spec.cr +++ /dev/null @@ -1,542 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::Double do - Spectator::Double.define(EmptyDouble) - Spectator::Double.define(FooBarDouble, "dbl-name", foo: 42, bar: "baz") - - # The subject `dbl` must be carefully used in sub-contexts, otherwise it pollutes parent scopes. - # This changes the type of `dbl` to `Double+`, which produces a union of methods and their return types. - context "plain double" do - subject(dbl) { FooBarDouble.new } - - it "responds to defined messages" do - aggregate_failures do - expect(dbl.foo).to eq(42) - expect(dbl.bar).to eq("baz") - end - end - - it "fails on undefined messages" do - expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/) - end - - it "reports the name in errors" do - expect { dbl.baz }.to raise_error(/"dbl-name"/) - end - - it "reports arguments" do - expect { dbl.baz(123, "qux", field: :value) }.to raise_error(Spectator::UnexpectedMessage, /\(123, "qux", field: :value\)/) - end - - it "has a non-union return type" do - aggregate_failures do - expect(dbl.foo).to compile_as(Int32) - expect(dbl.bar).to compile_as(String) - end - end - - it "uses nil for undefined messages" do - expect { dbl.baz }.to compile_as(Nil) - end - - context "blocks" do - it "supports blocks" do - aggregate_failures do - expect(dbl.foo { nil }).to eq(42) - expect(dbl.bar { nil }).to eq("baz") - end - end - - it "supports blocks and has non-union return types" do - aggregate_failures do - expect(dbl.foo { nil }).to compile_as(Int32) - expect(dbl.bar { nil }).to compile_as(String) - end - end - - it "fails on undefined messages" do - expect do - dbl.baz { nil } - end.to raise_error(Spectator::UnexpectedMessage, /baz/) - end - end - end - - context "without a double name" do - Spectator::Double.define(NamelessDouble, foo: 42) - - subject(dbl) { NamelessDouble.new } - - it "reports as anonymous" do - expect { dbl.baz }.to raise_error(/anonymous/i) - end - end - - context "with abstract stubs and return type annotations" do - Spectator::Double.define(TestDouble) do - stub abstract def foo(value) : String - end - - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(stub) { Spectator::ValueStub.new(:foo, "bar", arguments).as(Spectator::Stub) } - subject(dbl) { TestDouble.new([stub]) } - - it "enforces the return type" do - expect(dbl.foo("foobar")).to compile_as(String) - end - - it "raises on non-matching arguments" do - expect { dbl.foo("bar") }.to raise_error(Spectator::UnexpectedMessage, /foo/) - end - - it "raises on non-matching stub" do - stub = Spectator::ValueStub.new(:foo, 42, arguments).as(Spectator::Stub) - dbl._spectator_define_stub(stub) - expect { dbl.foo("foobar") }.to raise_error(TypeCastError, /String/) - end - end - - context "with nillable return type annotations" do - Spectator::Double.define(TestDouble) do - stub abstract def foo : String? - stub abstract def bar : Nil - end - - let(foo_stub) { Spectator::ValueStub.new(:foo, nil).as(Spectator::Stub) } - let(bar_stub) { Spectator::ValueStub.new(:bar, nil).as(Spectator::Stub) } - subject(dbl) { TestDouble.new([foo_stub, bar_stub]) } - - it "doesn't raise on nil" do - aggregate_failures do - expect(dbl.foo).to be_nil - expect(dbl.bar).to be_nil - end - end - end - - context "with a method that uses NoReturn" do - Spectator::Double.define(NoReturnDouble) do - 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([ - Spectator::ValueStub.new(:"!=", false), - Spectator::ValueStub.new(:"!~", false), - Spectator::ValueStub.new(:"==", true), - Spectator::ValueStub.new(:"===", true), - Spectator::ValueStub.new(:"=~", nil), - Spectator::ValueStub.new(:class, EmptyDouble), - Spectator::ValueStub.new(:dup, EmptyDouble.new), - Spectator::ValueStub.new(:"in?", true), - Spectator::ValueStub.new(:inspect, "inspect"), - Spectator::ValueStub.new(:itself, EmptyDouble.new), - Spectator::ValueStub.new(:"not_nil!", EmptyDouble.new), - Spectator::ValueStub.new(:pretty_inspect, "pretty_inspect"), - Spectator::ValueStub.new(:tap, EmptyDouble.new), - Spectator::ValueStub.new(:to_json, "to_json"), - Spectator::ValueStub.new(:to_pretty_json, "to_pretty_json"), - Spectator::ValueStub.new(:to_s, "to_s"), - Spectator::ValueStub.new(:to_yaml, "to_yaml"), - Spectator::ValueStub.new(:try, nil), - Spectator::ValueStub.new(:object_id, 42_u64), - Spectator::ValueStub.new(:"same?", true), - ] of Spectator::Stub) - end - - it "responds with defined messages" do - aggregate_failures do - expect(dbl.!=(42)).to be_false - expect(dbl.!~(42)).to be_false - expect(dbl.==(42)).to be_true - expect(dbl.===(42)).to be_true - expect(dbl.=~(42)).to be_nil - expect(dbl.class).to eq(EmptyDouble) - expect(dbl.dup).to be_a(EmptyDouble) - expect(dbl.in?([42])).to eq(true) - expect(dbl.in?(1, 2, 3)).to eq(true) - expect(dbl.inspect).to eq("inspect") - expect(dbl.itself).to be_a(EmptyDouble) - expect(dbl.not_nil!).to be_a(EmptyDouble) - expect(dbl.pretty_inspect).to eq("pretty_inspect") - expect(dbl.tap { nil }).to be_a(EmptyDouble) - expect(dbl.to_json).to eq("to_json") - expect(dbl.to_pretty_json).to eq("to_pretty_json") - expect(dbl.to_s).to eq("to_s") - expect(dbl.to_yaml).to eq("to_yaml") - expect(dbl.try { nil }).to be_nil - expect(dbl.object_id).to eq(42_u64) - expect(dbl.same?(dbl)).to be_true - expect(dbl.same?(nil)).to be_true - end - end - - it "has a non-union return type" do - expect(dbl.inspect).to compile_as(String) - end - end - - context "without common object methods" do - subject(dbl) { EmptyDouble.new } - - it "returns original implementation with undefined messages" do - io = IO::Memory.new - pp = PrettyPrint.new(io) - hasher = Crystal::Hasher.new - aggregate_failures do - expect(dbl.!=(42)).to be_true - expect(dbl.!~(42)).to be_true - expect(dbl.==(42)).to be_false - expect(dbl.===(42)).to be_false - expect(dbl.=~(42)).to be_nil - expect(dbl.class).to eq(EmptyDouble) - expect(dbl.dup).to be_a(EmptyDouble) - expect(dbl.hash(hasher)).to be_a(Crystal::Hasher) - expect(dbl.hash).to be_a(UInt64) - expect(dbl.in?([42])).to be_false - expect(dbl.in?(1, 2, 3)).to be_false - expect(dbl.itself).to be(dbl) - expect(dbl.not_nil!).to be(dbl) - expect(dbl.pretty_print(pp)).to be_nil - expect(dbl.tap { nil }).to be(dbl) - expect(dbl.try { nil }).to be_nil - expect(dbl.object_id).to be_a(UInt64) - expect(dbl.same?(dbl)).to be_true - expect(dbl.same?(nil)).to be_false - end - end - - it "reports arguments when they don't match" do - expect { dbl.same?(123, :xyz) }.to raise_error(Spectator::UnexpectedMessage, /\(123, :xyz\)/) - end - end - - context "with arguments constraints" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - - context "without common object methods" do - Spectator::Double.define(TestDouble) do - stub abstract def foo(value) : String - stub abstract def foo(value, & : -> _) : String - end - - let(stub) { Spectator::ValueStub.new(:foo, "bar", arguments).as(Spectator::Stub) } - subject(dbl) { TestDouble.new([stub]) } - - it "returns the response when constraint satisfied" do - expect(dbl.foo("foobar")).to eq("bar") - end - - it "raises an error when constraint unsatisfied" do - expect { dbl.foo("baz") }.to raise_error(Spectator::UnexpectedMessage) - end - - it "raises an error when argument count doesn't match" do - expect { dbl.foo }.to raise_error(Spectator::UnexpectedMessage) - end - - it "has a non-union return type" do - expect(dbl.foo("foobar")).to compile_as(String) - end - - it "ignores the block argument if not in the constraint" do - expect(dbl.foo("foobar") { nil }).to eq("bar") - end - end - - context "with common object methods" do - Spectator::Double.define(TestDouble) do - stub abstract def same?(other : Reference) : Bool - end - - let(stub) { Spectator::ValueStub.new(:"same?", true, arguments).as(Spectator::Stub) } - subject(dbl) { TestDouble.new([stub]) } - - it "returns the response when constraint satisfied" do - expect(dbl.same?("foobar")).to eq(true) - end - - it "raises an error when constraint unsatisfied" do - expect { dbl.same?("baz") }.to raise_error(Spectator::UnexpectedMessage) - end - - it "raises an error when argument count doesn't match" do - expect { dbl.same? }.to raise_error(Spectator::UnexpectedMessage) - end - - it "has a non-union return type" do - expect(dbl.same?("foobar")).to compile_as(Bool) - end - end - end - - context "class method stubs" do - Spectator::Double.define(ClassDouble) do - stub def self.foo - :stub - end - - stub def self.bar(arg) - arg - end - - stub def self.baz(arg, &) - yield - end - end - - subject(dbl) { ClassDouble } - let(foo_stub) { Spectator::ValueStub.new(:foo, :override) } - - after { dbl._spectator_clear_stubs } - - it "overrides an existing method" do - expect { dbl._spectator_define_stub(foo_stub) }.to change { dbl.foo }.from(:stub).to(:override) - end - - it "doesn't affect other methods" do - expect { dbl._spectator_define_stub(foo_stub) }.to_not change { dbl.bar(42) } - end - - it "replaces an existing stub" do - dbl._spectator_define_stub(foo_stub) - stub = Spectator::ValueStub.new(:foo, :replacement) - expect { dbl._spectator_define_stub(stub) }.to change { dbl.foo }.to(:replacement) - end - - it "picks the correct stub based on arguments" do - stub1 = Spectator::ValueStub.new(:bar, :fallback) - stub2 = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match)) - dbl._spectator_define_stub(stub1) - dbl._spectator_define_stub(stub2) - aggregate_failures do - expect(dbl.bar(:wrong)).to eq(:fallback) - expect(dbl.bar(:match)).to eq(:override) - end - end - - it "only uses a stub if an argument constraint is met" do - stub = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match)) - dbl._spectator_define_stub(stub) - aggregate_failures do - expect(dbl.bar(:original)).to eq(:original) - expect(dbl.bar(:match)).to eq(:override) - end - end - - it "ignores the block argument if not in the constraint" do - stub1 = Spectator::ValueStub.new(:baz, 1) - stub2 = Spectator::ValueStub.new(:baz, 2, Spectator::Arguments.capture(3)) - dbl._spectator_define_stub(stub1) - dbl._spectator_define_stub(stub2) - aggregate_failures do - expect(dbl.baz(5) { 42 }).to eq(1) - expect(dbl.baz(3) { 42 }).to eq(2) - end - end - - describe "._spectator_clear_stubs" do - before { dbl._spectator_define_stub(foo_stub) } - - it "removes previously defined stubs" do - expect { dbl._spectator_clear_stubs }.to change { dbl.foo }.from(:override).to(:stub) - end - end - - describe "._spectator_calls" do - before { dbl._spectator_clear_calls } - - # Retrieves symbolic names of methods called on a double. - def called_method_names(dbl) - dbl._spectator_calls.map(&.method) - end - - it "stores calls to stubbed methods" do - expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo]) - end - - it "stores multiple calls to the same stub" do - dbl.foo - expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo]) - end - - it "stores arguments for a call" do - dbl.bar(42) - args = Spectator::Arguments.capture(42) - call = dbl._spectator_calls.first - expect(call.arguments).to eq(args) - end - end - end - - describe "#_spectator_define_stub" do - subject(dbl) { FooBarDouble.new } - let(stub3) { Spectator::ValueStub.new(:foo, 3) } - let(stub5) { Spectator::ValueStub.new(:foo, 5) } - let(stub7) { Spectator::ValueStub.new(:foo, 7, Spectator::Arguments.capture(:lucky)) } - - it "overrides an existing method" do - expect { dbl._spectator_define_stub(stub3) }.to change { dbl.foo }.from(42).to(3) - end - - it "replaces an existing stub" do - dbl._spectator_define_stub(stub3) - expect { dbl._spectator_define_stub(stub5) }.to change { dbl.foo }.from(3).to(5) - end - - it "doesn't affect other methods" do - expect { dbl._spectator_define_stub(stub5) }.to_not change { dbl.bar } - end - - it "picks the correct stub based on arguments" do - dbl._spectator_define_stub(stub5) - dbl._spectator_define_stub(stub7) - aggregate_failures do - expect(dbl.foo).to eq(5) - expect(dbl.foo(:lucky)).to eq(7) - end - end - - it "only uses a stub if an argument constraint is met" do - dbl._spectator_define_stub(stub7) - aggregate_failures do - expect(dbl.foo).to eq(42) - expect(dbl.foo(:lucky)).to eq(7) - end - end - - it "ignores the block argument if not in the constraint" do - dbl._spectator_define_stub(stub5) - dbl._spectator_define_stub(stub7) - aggregate_failures do - expect(dbl.foo { nil }).to eq(5) - expect(dbl.foo(:lucky) { nil }).to eq(7) - end - end - end - - describe "#_spectator_clear_stubs" do - subject(dbl) { FooBarDouble.new } - let(stub) { Spectator::ValueStub.new(:foo, 5) } - - before { dbl._spectator_define_stub(stub) } - - it "removes previously defined stubs" do - expect { dbl._spectator_clear_stubs }.to change { dbl.foo }.from(5).to(42) - end - end - - describe "#_spectator_calls" do - subject(dbl) { FooBarDouble.new } - let(stub) { Spectator::ValueStub.new(:foo, 5) } - - before { dbl._spectator_define_stub(stub) } - - # Retrieves symbolic names of methods called on a double. - def called_method_names(dbl) - dbl._spectator_calls.map(&.method) - end - - it "stores calls to stubbed methods" do - expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo]) - end - - it "stores multiple calls to the same stub" do - dbl.foo - expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo]) - end - - it "stores calls to non-stubbed methods" do - expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/) - expect(called_method_names(dbl)).to contain(:baz) - end - - it "stores arguments for a call" do - dbl.foo(42) - args = Spectator::Arguments.capture(42) - call = dbl._spectator_calls.first - expect(call.arguments).to eq(args) - end - end - - describe "#to_s" do - subject(string) { dbl.to_s } - - context "with a name" do - let(dbl) { FooBarDouble.new } - - it "indicates it's a double" do - expect(string).to contain("Double") - end - - it "contains the double name" do - expect(string).to contain("dbl-name") - end - end - - context "without a name" do - let(dbl) { EmptyDouble.new } - - it "indicates it's a double" do - expect(string).to contain("Double") - end - - it "contains \"Anonymous\"" do - expect(string).to contain("Anonymous") - end - end - end - - describe "#inspect" do - subject(string) { dbl.inspect } - - context "with a name" do - let(dbl) { FooBarDouble.new } - - it "indicates it's a double" do - expect(string).to contain("Double") - end - - it "contains the double name" do - expect(string).to contain("dbl-name") - end - - it "contains the object ID" do - expect(string).to contain(dbl.object_id.to_s(16)) - end - end - - context "without a name" do - let(dbl) { EmptyDouble.new } - - it "indicates it's a double" do - expect(string).to contain("Double") - end - - it "contains \"Anonymous\"" do - expect(string).to contain("Anonymous") - end - - it "contains the object ID" do - expect(string).to contain(dbl.object_id.to_s(16)) - end - end - end -end diff --git a/spec/spectator/mocks/exception_stub_spec.cr b/spec/spectator/mocks/exception_stub_spec.cr deleted file mode 100644 index 7f45455..0000000 --- a/spec/spectator/mocks/exception_stub_spec.cr +++ /dev/null @@ -1,166 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::ExceptionStub do - let(method_call) { Spectator::MethodCall.capture(:foo) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(exception) { RuntimeError.new("Test exception") } - subject(stub) { described_class.new(:foo, exception, location: location) } - - it "stores the method name" do - expect(stub.method).to eq(:foo) - end - - it "stores the location" do - expect(stub.location).to eq(location) - end - - it "raises the specified exception" do - expect { stub.call(method_call) }.to raise_error(RuntimeError, "Test exception") - end - - context Spectator::StubModifiers do - describe "#and_return(value)" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::ExceptionStub.new(:foo, exception, arguments, location) } - subject(stub) { original.and_return(123) } - - it "produces a stub that returns a value" do - expect(stub.call(method_call)).to eq(123) - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - - describe "#and_return(*values)" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::ExceptionStub.new(:foo, exception, arguments, location) } - subject(stub) { original.and_return(3, 2, 1, 0) } - - it "produces a stub that returns values" do - values = Array.new(5) { stub.call(method_call) } - expect(values).to eq([3, 2, 1, 0, 0]) - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - - describe "#and_raise" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::ExceptionStub.new(:foo, exception, arguments, location) } - let(new_exception) { ArgumentError.new("Test argument error") } - subject(stub) { original.and_raise(new_exception) } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error") - end - - context "with a class and message" do - subject(stub) { original.and_raise(ArgumentError, "Test argument error") } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error") - end - end - - context "with a message" do - subject(stub) { original.and_raise("Test exception") } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(Exception, "Test exception") - end - end - - context "with a class" do - subject(stub) { original.and_raise(ArgumentError) } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError) - end - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - end - - describe "#===" do - subject { stub === call } - - context "with a matching method name" do - let(call) { Spectator::MethodCall.capture(:foo, "foobar") } - - it "returns true" do - is_expected.to be_true - end - end - - context "with a different method name" do - let(call) { Spectator::MethodCall.capture(:bar, "foobar") } - - it "returns false" do - is_expected.to be_false - end - end - - context "with a constraint" do - let(constraint) { Spectator::Arguments.capture(/foo/) } - let(stub) { Spectator::ValueStub.new(:foo, 42, constraint) } - - context "with a matching method name" do - let(call) { Spectator::MethodCall.capture(:foo, "foobar") } - - it "returns true" do - is_expected.to be_true - end - - context "with a non-matching arguments" do - let(call) { Spectator::MethodCall.capture(:foo, "baz") } - - it "returns false" do - is_expected.to be_false - end - end - end - - context "with a different method name" do - let(call) { Spectator::MethodCall.capture(:bar, "foobar") } - - it "returns false" do - is_expected.to be_false - end - end - end - end -end diff --git a/spec/spectator/mocks/formal_arguments_spec.cr b/spec/spectator/mocks/formal_arguments_spec.cr deleted file mode 100644 index 963b6eb..0000000 --- a/spec/spectator/mocks/formal_arguments_spec.cr +++ /dev/null @@ -1,325 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::FormalArguments do - subject(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) } - - it "stores the arguments" do - expect(arguments).to have_attributes( - args: {arg1: 42, arg2: "foo"}, - splat_name: :splat, - splat: {:x, :y, :z}, - kwargs: {bar: "baz", qux: 123} - ) - end - - describe ".build" do - subject { Spectator::FormalArguments.build({arg1: 42, arg2: "foo"}, :splat, {1, 2, 3}, {bar: "baz", qux: 123}) } - - it "stores the arguments and keyword arguments" do - is_expected.to have_attributes( - args: {arg1: 42, arg2: "foo"}, - splat_name: :splat, - splat: {1, 2, 3}, - kwargs: {bar: "baz", qux: 123} - ) - end - - context "without a splat" do - subject { Spectator::FormalArguments.build({arg1: 42, arg2: "foo"}, {bar: "baz", qux: 123}) } - - it "stores the arguments and keyword arguments" do - is_expected.to have_attributes( - args: {arg1: 42, arg2: "foo"}, - splat: nil, - kwargs: {bar: "baz", qux: 123} - ) - end - end - end - - describe "#[](index)" do - it "returns a positional argument" do - aggregate_failures do - expect(arguments[0]).to eq(42) - expect(arguments[1]).to eq("foo") - end - end - - it "returns splat arguments" do - aggregate_failures do - expect(arguments[2]).to eq(:x) - expect(arguments[3]).to eq(:y) - expect(arguments[4]).to eq(:z) - end - end - - context "with named positional arguments" do - subject(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) } - - it "returns a positional argument" do - aggregate_failures do - expect(arguments[0]).to eq(42) - expect(arguments[1]).to eq("foo") - end - end - - it "returns splat arguments" do - aggregate_failures do - expect(arguments[2]).to eq(:x) - expect(arguments[3]).to eq(:y) - expect(arguments[4]).to eq(:z) - end - end - end - end - - describe "#[](symbol)" do - it "returns a keyword argument" do - aggregate_failures do - expect(arguments[:bar]).to eq("baz") - expect(arguments[:qux]).to eq(123) - end - end - - context "with named positional arguments" do - subject(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) } - - it "returns a positional argument" do - aggregate_failures do - expect(arguments[:arg1]).to eq(42) - expect(arguments[:arg2]).to eq("foo") - end - end - - it "returns a keyword argument" do - aggregate_failures do - expect(arguments[:bar]).to eq("baz") - expect(arguments[:qux]).to eq(123) - end - end - end - end - - describe "#to_s" do - subject { arguments.to_s } - - it "formats the arguments" do - is_expected.to eq("(arg1: 42, arg2: \"foo\", *splat: {:x, :y, :z}, bar: \"baz\", qux: 123)") - end - - context "when empty" do - let(arguments) { Spectator::FormalArguments.none } - - it "returns (no args)" do - is_expected.to eq("(no args)") - end - end - - context "with a splat and no arguments" do - let(arguments) { Spectator::FormalArguments.build(NamedTuple.new, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) } - - it "omits the splat name" do - is_expected.to eq("(:x, :y, :z, bar: \"baz\", qux: 123)") - end - end - end - - describe "#==" do - subject { arguments == other } - - context "with Arguments" do - context "with equal arguments" do - let(other) { Spectator::Arguments.new(arguments.positional, arguments.kwargs) } - - it { is_expected.to be_true } - end - - context "with different arguments" do - let(other) { Spectator::Arguments.new({123, :foo, "bar"}, {opt: "foobar"}) } - - it { is_expected.to be_false } - end - - context "with the same kwargs in a different order" do - let(other) { Spectator::Arguments.new(arguments.positional, {qux: 123, bar: "baz"}) } - - it { is_expected.to be_true } - end - - context "with a missing kwarg" do - let(other) { Spectator::Arguments.new(arguments.positional, {bar: "baz"}) } - - it { is_expected.to be_false } - end - - context "with an extra kwarg" do - let(other) { Spectator::Arguments.new(arguments.positional, {bar: "baz", qux: 123, extra: 0}) } - - it { is_expected.to be_false } - end - end - - context "with FormalArguments" do - context "with equal arguments" do - let(other) { arguments } - - it { is_expected.to be_true } - end - - context "with different arguments" do - let(other) { Spectator::FormalArguments.new({arg1: 123, arg2: :foo, arg3: "bar"}, :splat, {1, 2, 3}, {opt: "foobar"}) } - - it { is_expected.to be_false } - end - - context "with the same kwargs in a different order" do - let(other) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {qux: 123, bar: "baz"}) } - - it { is_expected.to be_true } - end - - context "with a missing kwarg" do - let(other) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: "baz"}) } - - it { is_expected.to be_false } - end - - context "with an extra kwarg" do - let(other) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: "baz", qux: 123, extra: 0}) } - - it { is_expected.to be_false } - end - - context "with different splat arguments" do - let(other) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, {1, 2, 3}, arguments.kwargs) } - - it { is_expected.to be_false } - end - - context "with mixed positional tuple types" do - let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, arguments.splat_name, arguments.splat, arguments.kwargs) } - - it { is_expected.to be_true } - end - - context "with mixed positional tuple types (flipped)" do - let(arguments) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) } - let(other) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) } - - it { is_expected.to be_true } - end - end - end - - describe "#===" do - subject { pattern === arguments } - - context "with Arguments" do - let(arguments) { Spectator::Arguments.new({42, "foo"}, {bar: "baz", qux: 123}) } - - context "with equal arguments" do - let(pattern) { Spectator::FormalArguments.new({arg1: 42, arg2: "foo"}, {bar: "baz", qux: 123}) } - - it { is_expected.to be_true } - end - - context "with matching arguments" do - let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, {bar: /baz/, qux: Int32}) } - - it { is_expected.to be_true } - end - - context "with non-matching arguments" do - let(pattern) { Spectator::FormalArguments.new({arg1: Float64, arg2: /bar/}, {bar: /foo/, qux: "123"}) } - - it { is_expected.to be_false } - end - - context "with different arguments" do - let(pattern) { Spectator::FormalArguments.new({arg1: 123, arg2: :foo, arg3: "bar"}, {opt: "foobar"}) } - - it { is_expected.to be_false } - end - - context "with the same kwargs in a different order" do - let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, {qux: Int32, bar: /baz/}) } - - it { is_expected.to be_true } - end - - context "with an additional kwarg" do - let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, {bar: /baz/}) } - - it { is_expected.to be_true } - end - - context "with a missing kwarg" do - let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, {bar: /baz/, qux: Int32, extra: 0}) } - - it { is_expected.to be_false } - end - end - - context "with FormalArguments" do - context "with equal arguments" do - let(pattern) { arguments } - - it { is_expected.to be_true } - end - - context "with matching arguments" do - let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, :splat, {Symbol, Symbol, :z}, {bar: /baz/, qux: Int32}) } - - it { is_expected.to be_true } - end - - context "with non-matching arguments" do - let(pattern) { Spectator::FormalArguments.new({arg1: Float64, arg2: /bar/}, :splat, {String, Int32, :x}, {bar: /foo/, qux: "123"}) } - - it { is_expected.to be_false } - end - - context "with different arguments" do - let(pattern) { Spectator::FormalArguments.new({arg1: 123, arg2: :foo, arg3: "bar"}, :splat, {1, 2, 3}, {opt: "foobar"}) } - - it { is_expected.to be_false } - end - - context "with the same kwargs in a different order" do - let(pattern) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {qux: Int32, bar: /baz/}) } - - it { is_expected.to be_true } - end - - context "with an additional kwarg" do - let(pattern) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: /baz/}) } - - it { is_expected.to be_true } - end - - context "with a missing kwarg" do - let(pattern) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, arguments.splat, {bar: /baz/, qux: Int32, extra: 0}) } - - it { is_expected.to be_false } - end - - context "with different splat arguments" do - let(pattern) { Spectator::FormalArguments.new(arguments.args, arguments.splat_name, {1, 2, 3}, arguments.kwargs) } - - it { is_expected.to be_false } - end - - context "with matching mixed positional tuple types" do - let(pattern) { Spectator::FormalArguments.new({arg1: Int32, arg2: /foo/}, arguments.splat_name, arguments.splat, arguments.kwargs) } - - it { is_expected.to be_true } - end - - context "with non-matching mixed positional tuple types" do - let(pattern) { Spectator::FormalArguments.new({arg1: Float64, arg2: /bar/}, arguments.splat_name, arguments.splat, arguments.kwargs) } - - it { is_expected.to be_false } - end - end - end -end diff --git a/spec/spectator/mocks/lazy_double_spec.cr b/spec/spectator/mocks/lazy_double_spec.cr deleted file mode 100644 index 8ea5a5d..0000000 --- a/spec/spectator/mocks/lazy_double_spec.cr +++ /dev/null @@ -1,352 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::LazyDouble do - context "plain double" do - subject(dbl) { Spectator::LazyDouble.new("dbl-name", foo: 42, bar: "baz") } - - it "responds to defined messages" do - aggregate_failures do - expect(dbl.foo).to eq(42) - expect(dbl.bar).to eq("baz") - end - end - - it "fails on undefined messages" do - expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/) - end - - it "reports the name in errors" do - expect { dbl.baz }.to raise_error(/"dbl-name"/) - end - - it "reports arguments" do - expect { dbl.baz(123, "qux", field: :value) }.to raise_error(Spectator::UnexpectedMessage, /\(123, "qux", field: :value\)/) - end - - context "blocks" do - it "supports blocks" do - aggregate_failures do - expect(dbl.foo { nil }).to eq(42) - expect(dbl.bar { nil }).to eq("baz") - end - end - - it "fails on undefined messages" do - expect do - dbl.baz { nil } - end.to raise_error(Spectator::UnexpectedMessage, /baz/) - end - end - end - - context "without a double name" do - subject(dbl) { Spectator::LazyDouble.new } - - it "reports as anonymous" do - expect { dbl.baz }.to raise_error(/anonymous/i) - end - end - - context "with nillable values" do - subject(dbl) { Spectator::LazyDouble.new(foo: nil.as(String?), bar: nil) } - - it "doesn't raise on nil" do - aggregate_failures do - expect(dbl.foo).to be_nil - expect(dbl.bar).to be_nil - end - end - end - - context "with common object methods" do - let(dup) { double(:dup) } - - subject(dbl) do - Spectator::LazyDouble.new(nil, [ - Spectator::ValueStub.new(:"!=", false), - Spectator::ValueStub.new(:"!~", false), - Spectator::ValueStub.new(:"==", true), - Spectator::ValueStub.new(:"===", true), - Spectator::ValueStub.new(:"=~", nil), - Spectator::ValueStub.new(:dup, dup), - Spectator::ValueStub.new(:hash, 42_u64), - Spectator::ValueStub.new(:"in?", true), - Spectator::ValueStub.new(:inspect, "inspect"), - Spectator::ValueStub.new(:itself, dup), - Spectator::ValueStub.new(:"not_nil!", dup), - Spectator::ValueStub.new(:pretty_inspect, "pretty_inspect"), - Spectator::ValueStub.new(:tap, dup), - Spectator::ValueStub.new(:to_json, "to_json"), - Spectator::ValueStub.new(:to_pretty_json, "to_pretty_json"), - Spectator::ValueStub.new(:to_s, "to_s"), - Spectator::ValueStub.new(:to_yaml, "to_yaml"), - Spectator::ValueStub.new(:try, nil), - Spectator::ValueStub.new(:object_id, 42_u64), - Spectator::ValueStub.new(:"same?", true), - ] of Spectator::Stub) - end - - it "responds with defined messages" do - aggregate_failures do - expect(dbl.!=(42)).to eq(false) - expect(dbl.!~(42)).to eq(false) - expect(dbl.==(42)).to eq(true) - expect(dbl.===(42)).to eq(true) - expect(dbl.=~(42)).to be_nil - expect(dbl.dup).to be(dup) - expect(dbl.hash).to eq(42_u64) - expect(dbl.in?([42])).to eq(true) - expect(dbl.in?(1, 2, 3)).to eq(true) - expect(dbl.inspect).to eq("inspect") - expect(dbl.itself).to be(dup) - expect(dbl.not_nil!).to be(dup) - expect(dbl.pretty_inspect).to eq("pretty_inspect") - expect(dbl.tap { nil }).to be(dup) - expect(dbl.to_json).to eq("to_json") - expect(dbl.to_pretty_json).to eq("to_pretty_json") - expect(dbl.to_s).to eq("to_s") - expect(dbl.to_yaml).to eq("to_yaml") - expect(dbl.try { nil }).to be_nil - expect(dbl.object_id).to eq(42_u64) - expect(dbl.same?(dbl)).to eq(true) - expect(dbl.same?(nil)).to eq(true) - end - end - - it "has a non-union return type" do - expect(dbl.inspect).to compile_as(String) - end - end - - context "without common object methods" do - subject(dbl) { Spectator::LazyDouble.new } - - it "returns the original value" do - io = IO::Memory.new - aggregate_failures do - expect(dbl.!=(42)).to be_true - expect(dbl.!~(42)).to be_true - expect(dbl.==(42)).to be_false - expect(dbl.===(42)).to be_false - expect(dbl.=~(42)).to be_nil - expect(dbl.class).to be_lt(Spectator::LazyDouble) - expect(dbl.in?([42])).to be_false - expect(dbl.in?(1, 2, 3)).to be_false - expect(dbl.itself).to be(dbl) - expect(dbl.not_nil!).to be(dbl) - expect(dbl.tap { nil }).to be(dbl) - expect(dbl.to_s(io)).to be_nil - expect(dbl.try { nil }).to be_nil - expect(dbl.same?(dbl)).to be_true - expect(dbl.same?(nil)).to be_false - end - end - end - - context "with arguments constraints" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - - context "without common object methods" do - let(stub) { Spectator::ValueStub.new(:foo, "bar", arguments).as(Spectator::Stub) } - subject(dbl) { Spectator::LazyDouble.new(nil, [stub], foo: "fallback") } - - it "returns the response when constraint satisfied" do - expect(dbl.foo("foobar")).to eq("bar") - end - - it "returns the fallback value when constraint unsatisfied" do - expect { dbl.foo("baz") }.to eq("fallback") - end - - it "returns the fallback value when argument count doesn't match" do - expect { dbl.foo }.to eq("fallback") - end - end - - context "with common object methods" do - let(stub) { Spectator::ValueStub.new(:"same?", true, arguments).as(Spectator::Stub) } - subject(dbl) { Spectator::LazyDouble.new(nil, [stub]) } - - it "returns the response when constraint satisfied" do - expect(dbl.same?("foobar")).to eq(true) - end - - it "raises an error when constraint unsatisfied" do - expect { dbl.same?("baz") }.to raise_error(Spectator::UnexpectedMessage) - end - - it "raises an error when argument count doesn't match" do - expect { dbl.same? }.to raise_error(Spectator::UnexpectedMessage) - end - - context "with a fallback defined" do - subject(dbl) { Spectator::LazyDouble.new(nil, [stub], "same?": true) } - - it "returns the fallback when constraint unsatisfied" do - expect(dbl.same?("baz")).to be_true - end - end - end - end - - describe "#_spectator_define_stub" do - subject(dbl) { Spectator::LazyDouble.new(foo: 42, bar: "baz") } - let(stub3) { Spectator::ValueStub.new(:foo, 3) } - let(stub5) { Spectator::ValueStub.new(:foo, 5) } - let(stub7) { Spectator::ValueStub.new(:foo, 7, Spectator::Arguments.capture(:lucky)) } - - it "overrides an existing method" do - expect { dbl._spectator_define_stub(stub3) }.to change { dbl.foo }.from(42).to(3) - end - - it "replaces an existing stub" do - dbl._spectator_define_stub(stub3) - expect { dbl._spectator_define_stub(stub5) }.to change { dbl.foo }.from(3).to(5) - end - - it "doesn't affect other methods" do - expect { dbl._spectator_define_stub(stub5) }.to_not change { dbl.bar } - end - - it "picks the correct stub based on arguments" do - dbl._spectator_define_stub(stub5) - dbl._spectator_define_stub(stub7) - aggregate_failures do - expect(dbl.foo).to eq(5) - expect(dbl.foo(:lucky)).to eq(7) - end - end - - it "only uses a stub if an argument constraint is met" do - dbl._spectator_define_stub(stub7) - aggregate_failures do - expect(dbl.foo).to eq(42) - expect(dbl.foo(:lucky)).to eq(7) - end - end - - it "ignores the block argument if not in the constraint" do - dbl._spectator_define_stub(stub5) - dbl._spectator_define_stub(stub7) - aggregate_failures do - expect(dbl.foo { nil }).to eq(5) - expect(dbl.foo(:lucky) { nil }).to eq(7) - end - end - - context "with previously undefined methods" do - it "raises an error" do - stub = Spectator::ValueStub.new(:baz, :xyz) - expect { dbl._spectator_define_stub(stub) }.to raise_error(/stub/) - end - end - end - - describe "#_spectator_clear_stubs" do - subject(dbl) { Spectator::LazyDouble.new(foo: 42, bar: "baz") } - let(stub) { Spectator::ValueStub.new(:foo, 5) } - - before { dbl._spectator_define_stub(stub) } - - it "removes previously defined stubs" do - expect { dbl._spectator_clear_stubs }.to change { dbl.foo }.from(5).to(42) - end - end - - describe "#_spectator_calls" do - subject(dbl) { Spectator::LazyDouble.new(foo: 42, bar: "baz") } - let(stub) { Spectator::ValueStub.new(:foo, 5) } - - before { dbl._spectator_define_stub(stub) } - - # Retrieves symbolic names of methods called on a double. - def called_method_names(dbl) - dbl._spectator_calls.map(&.method) - end - - it "stores calls to stubbed methods" do - expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo]) - end - - it "stores multiple calls to the same stub" do - dbl.foo - expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo]) - end - - it "stores calls to non-stubbed methods" do - expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/) - expect(called_method_names(dbl)).to contain(:baz) - end - - it "stores arguments for a call" do - dbl.foo(42) - args = Spectator::Arguments.capture(42) - call = dbl._spectator_calls.first - expect(call.arguments).to eq(args) - end - end - - describe "#to_s" do - subject(string) { dbl.to_s } - - context "with a name" do - let(dbl) { Spectator::LazyDouble.new("dbl-name") } - - it "indicates it's a double" do - expect(string).to contain("LazyDouble") - end - - it "contains the double name" do - expect(string).to contain("dbl-name") - end - end - - context "without a name" do - let(dbl) { Spectator::LazyDouble.new } - - it "contains the double type" do - expect(string).to contain("LazyDouble") - end - - it "contains \"Anonymous\"" do - expect(string).to contain("Anonymous") - end - end - end - - describe "#inspect" do - subject(string) { dbl.inspect } - - context "with a name" do - let(dbl) { Spectator::LazyDouble.new("dbl-name") } - - it "contains the double type" do - expect(string).to contain("LazyDouble") - end - - it "contains the double name" do - expect(string).to contain("dbl-name") - end - - it "contains the object ID" do - expect(string).to contain(dbl.object_id.to_s(16)) - end - end - - context "without a name" do - let(dbl) { Spectator::LazyDouble.new } - - it "contains the double type" do - expect(string).to contain("LazyDouble") - end - - it "contains \"Anonymous\"" do - expect(string).to contain("Anonymous") - end - - it "contains the object ID" do - expect(string).to contain(dbl.object_id.to_s(16)) - end - end - end -end diff --git a/spec/spectator/mocks/method_call_spec.cr b/spec/spectator/mocks/method_call_spec.cr deleted file mode 100644 index 52a9e4f..0000000 --- a/spec/spectator/mocks/method_call_spec.cr +++ /dev/null @@ -1,26 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::MethodCall do - let(arguments) { Spectator::Arguments.capture(42, "foobar", foo: :bar) } - subject(call) { Spectator::MethodCall.new(:foo, arguments) } - - it "stores the method name" do - expect(&.method).to eq(:foo) - end - - it "stores arguments" do - expect(&.arguments).to eq(arguments) - end - - describe ".capture" do - subject(call) { Spectator::MethodCall.capture(:foo, 42, "foobar", foo: :bar) } - - it "stores the method name" do - expect(&.method).to eq(:foo) - end - - it "stores arguments" do - expect(&.arguments).to eq(arguments) - end - end -end diff --git a/spec/spectator/mocks/mock_spec.cr b/spec/spectator/mocks/mock_spec.cr deleted file mode 100644 index 3ddd0fe..0000000 --- a/spec/spectator/mocks/mock_spec.cr +++ /dev/null @@ -1,1062 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::Mock do - let(stub1) { Spectator::ValueStub.new(:method1, 777) } - let(stub2) { Spectator::ValueStub.new(:method2, :override) } - let(stub3) { Spectator::ValueStub.new(:method3, "stubbed") } - - # Retrieves symbolic names of methods called on a mock. - def called_method_names(mock) - mock._spectator_calls.map(&.method) - end - - describe "#define_subtype" do - context "with a concrete class" do - class Thing - getter _spectator_invocations = [] of Symbol - - def method1 - @_spectator_invocations << :method1 - 42 - end - - def method2 - @_spectator_invocations << :method2 - :original - end - - def method3 - @_spectator_invocations << :method3 - "original" - end - - def method4 : Thing - self - end - - def method5 : OtherThing - OtherThing.new - end - end - - class OtherThing; end - - Spectator::Mock.define_subtype(:class, Thing, MockThing, :mock_name, method1: 123) do - stub def method2 - :stubbed - end - end - - let(mock) { MockThing.new } - - it "defines a subclass of the mocked type" do - expect(mock).to be_a(Thing) - end - - it "overrides responses from methods with keyword arguments" do - expect(mock.method1).to eq(123) - end - - it "overrides responses from methods defined in the block" do - expect(mock.method2).to eq(:stubbed) - end - - it "allows methods to be stubbed" do - aggregate_failures do - expect { mock._spectator_define_stub(stub1) }.to change { mock.method1 }.to(777) - expect { mock._spectator_define_stub(stub2) }.to change { mock.method2 }.to(:override) - expect { mock._spectator_define_stub(stub3) }.to change { mock.method3 }.from("original").to("stubbed") - end - end - - it "can clear stubs" do - mock._spectator_define_stub(stub1) - mock._spectator_define_stub(stub2) - mock._spectator_define_stub(stub3) - - mock._spectator_clear_stubs - aggregate_failures do - expect(mock.method1).to eq(123) - expect(mock.method2).to eq(:stubbed) - expect(mock.method3).to eq("original") - end - end - - it "sets the mock name" do - args = Spectator::Arguments.capture("foo") - stub = Spectator::ValueStub.new(:method3, 0, args) - mock._spectator_define_stub(stub) - expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name." - end - - it "records invoked stubs" do - expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[]).to(%i[method2]) - expect { mock.method1 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method1]) - expect { mock.method3 }.to change { called_method_names(mock) }.from(%i[method2 method1]).to(%i[method2 method1 method3]) - end - - it "records multiple invocations of the same stub" do - mock.method2 - expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method2]) - end - - def restricted(thing : Thing) - thing.method1 - end - - it "can be used in type restricted methods" do - expect(restricted(mock)).to eq(123) - end - - it "does not call the original method when stubbed" do - mock.method1 - mock.method2 - mock.method3 - expect(mock._spectator_invocations).to contain_exactly(:method3) - end - - it "can reference its own type" do - new_mock = MockThing.new - stub = Spectator::ValueStub.new(:method4, new_mock) - mock._spectator_define_stub(stub) - expect(mock.method4).to be(new_mock) - end - - it "can reference other types in the original namespace" do - other = OtherThing.new - stub = Spectator::ValueStub.new(:method5, other) - mock._spectator_define_stub(stub) - expect(mock.method5).to be(other) - end - end - - context "with an abstract class" do - abstract class Thing - getter _spectator_invocations = [] of Symbol - - abstract def method1 - - abstract def method2 : Symbol - - def method3 - @_spectator_invocations << :method3 - "original" - end - - abstract def method4 - - abstract def method4 : Thing - - abstract def method5 : OtherThing - end - - class OtherThing; end - - Spectator::Mock.define_subtype(:class, Thing, MockThing, :mock_name, method2: :stubbed) do - stub def method1 : Int32 # NOTE: Return type is required since one wasn't provided in the parent. - 123 - end - end - - let(mock) { MockThing.new } - - it "defines a subclass of the mocked type" do - expect(mock).to be_a(Thing) - end - - it "overrides responses from methods defined in the block" do - expect(mock.method1).to eq(123) - end - - it "overrides responses from methods with keyword arguments" do - expect(mock.method2).to eq(:stubbed) - end - - it "allows methods to be stubbed" do - aggregate_failures do - expect { mock._spectator_define_stub(stub1) }.to change { mock.method1 }.to(777) - expect { mock._spectator_define_stub(stub2) }.to change { mock.method2 }.to(:override) - expect { mock._spectator_define_stub(stub3) }.to change { mock.method3 }.from("original").to("stubbed") - end - end - - it "can clear stubs" do - mock._spectator_define_stub(stub1) - mock._spectator_define_stub(stub2) - mock._spectator_define_stub(stub3) - - mock._spectator_clear_stubs - aggregate_failures do - expect(mock.method1).to eq(123) - expect(mock.method2).to eq(:stubbed) - expect(mock.method3).to eq("original") - end - end - - it "raises when calling an abstract method that isn't stubbed" do - expect { mock.method4 }.to raise_error(Spectator::UnexpectedMessage, /method4/) - end - - it "sets the mock name" do - args = Spectator::Arguments.capture("foo") - stub = Spectator::ValueStub.new(:method3, 0, args) - mock._spectator_define_stub(stub) - expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name." - end - - it "records invoked stubs" do - expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[]).to(%i[method2]) - expect { mock.method1 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method1]) - expect { mock.method3 }.to change { called_method_names(mock) }.from(%i[method2 method1]).to(%i[method2 method1 method3]) - end - - it "records multiple invocations of the same stub" do - mock.method2 - expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method2]) - end - - def restricted(thing : Thing) - thing.method1 - end - - it "can be used in type restricted methods" do - expect(restricted(mock)).to eq(123) - end - - it "does not call the original method when stubbed" do - mock.method1 - mock.method2 - mock.method3 - expect(mock._spectator_invocations).to contain_exactly(:method3) - end - - it "can reference its own type" do - new_mock = MockThing.new - stub = Spectator::ValueStub.new(:method4, new_mock) - mock._spectator_define_stub(stub) - expect(mock.method4).to be(new_mock) - end - - it "can reference other types in the original namespace" do - other = OtherThing.new - stub = Spectator::ValueStub.new(:method5, other) - mock._spectator_define_stub(stub) - expect(mock.method5).to be(other) - end - end - - context "with an abstract struct" do - abstract struct Thing - getter _spectator_invocations = [] of Symbol - - abstract def method1 - - abstract def method2 : Symbol - - def method3 - @_spectator_invocations << :method3 - "original" - end - - abstract def method4 - - abstract def method4 : Thing - - abstract def method5 : OtherThing - end - - class OtherThing; end - - Spectator::Mock.define_subtype(:struct, Thing, MockThing, :mock_name, method2: :stubbed) do - stub def method1 : Int32 # NOTE: Return type is required since one wasn't provided in the parent. - 123 - end - end - - let(mock) { MockThing.new } - - it "defines a sub-type of the mocked type" do - expect(mock).to be_a(Thing) - end - - it "overrides responses from methods defined in the block" do - expect(mock.method1).to eq(123) - end - - it "overrides responses from methods with keyword arguments" do - expect(mock.method2).to eq(:stubbed) - end - - it "allows methods to be stubbed" do - mock = self.mock # FIXME: Workaround for passing by value messing with stubs. - aggregate_failures do - expect { mock._spectator_define_stub(stub1) }.to change { mock.method1 }.to(777) - expect { mock._spectator_define_stub(stub2) }.to change { mock.method2 }.to(:override) - expect { mock._spectator_define_stub(stub3) }.to change { mock.method3 }.from("original").to("stubbed") - end - end - - it "can clear stubs" do - mock._spectator_define_stub(stub1) - mock._spectator_define_stub(stub2) - mock._spectator_define_stub(stub3) - - mock._spectator_clear_stubs - aggregate_failures do - expect(mock.method1).to eq(123) - expect(mock.method2).to eq(:stubbed) - expect(mock.method3).to eq("original") - end - end - - it "raises when calling an abstract method that isn't stubbed" do - expect { mock.method4 }.to raise_error(Spectator::UnexpectedMessage, /method4/) - end - - it "sets the mock name" do - mock = self.mock # FIXME: Workaround for passing by value messing with stubs. - args = Spectator::Arguments.capture("foo") - stub = Spectator::ValueStub.new(:method3, 0, args) - mock._spectator_define_stub(stub) - expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name." - end - - def restricted(thing : Thing) - thing.method1 - end - - it "can be used in type restricted methods" do - expect(restricted(mock)).to eq(123) - end - - it "does not call the original method when stubbed" do - mock = self.mock # FIXME: Workaround for passing by value messing with stubs. - mock.method1 - mock.method2 - mock.method3 - expect(mock._spectator_invocations).to contain_exactly(:method3) - end - - it "can reference its own type" do - mock = self.mock # FIXME: Workaround for passing by value messing with stubs. - new_mock = MockThing.new - stub = Spectator::ValueStub.new(:method4, new_mock) - mock._spectator_define_stub(stub) - expect(mock.method4).to be_a(Thing) - end - - it "can reference other types in the original namespace" do - mock = self.mock # FIXME: Workaround for passing by value messing with stubs. - other = OtherThing.new - stub = Spectator::ValueStub.new(:method5, other) - mock._spectator_define_stub(stub) - expect(mock.method5).to be(other) - end - end - - context "class method stubs" do - class Thing - def self.foo - :original - end - - def self.bar(arg) - arg - end - - def self.baz(arg, &) - yield - end - - def self.thing : Thing - new - end - - def self.other : OtherThing - OtherThing.new - end - end - - class OtherThing; end - - Spectator::Mock.define_subtype(:class, Thing, MockThing) do - stub def self.foo - :stub - end - end - - let(mock) { MockThing } - let(foo_stub) { Spectator::ValueStub.new(:foo, :override) } - - after { mock._spectator_clear_stubs } - - it "overrides an existing method" do - expect { mock._spectator_define_stub(foo_stub) }.to change { mock.foo }.from(:stub).to(:override) - end - - it "doesn't affect other methods" do - expect { mock._spectator_define_stub(foo_stub) }.to_not change { mock.bar(42) } - end - - it "replaces an existing stub" do - mock._spectator_define_stub(foo_stub) - stub = Spectator::ValueStub.new(:foo, :replacement) - expect { mock._spectator_define_stub(stub) }.to change { mock.foo }.to(:replacement) - end - - it "picks the correct stub based on arguments" do - stub1 = Spectator::ValueStub.new(:bar, :fallback) - stub2 = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match)) - mock._spectator_define_stub(stub1) - mock._spectator_define_stub(stub2) - aggregate_failures do - expect(mock.bar(:wrong)).to eq(:fallback) - expect(mock.bar(:match)).to eq(:override) - end - end - - it "only uses a stub if an argument constraint is met" do - stub = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match)) - mock._spectator_define_stub(stub) - aggregate_failures do - expect(mock.bar(:original)).to eq(:original) - expect(mock.bar(:match)).to eq(:override) - end - end - - it "ignores the block argument if not in the constraint" do - stub1 = Spectator::ValueStub.new(:baz, 1) - stub2 = Spectator::ValueStub.new(:baz, 2, Spectator::Arguments.capture(3)) - mock._spectator_define_stub(stub1) - mock._spectator_define_stub(stub2) - aggregate_failures do - expect(mock.baz(5) { 42 }).to eq(1) - expect(mock.baz(3) { 42 }).to eq(2) - end - end - - def restricted(thing : Thing.class) - thing.foo - end - - it "can be used in type restricted methods" do - expect(restricted(mock)).to eq(:stub) - end - - it "can reference its own type" do - new_mock = MockThing.new - stub = Spectator::ValueStub.new(:thing, new_mock) - mock._spectator_define_stub(stub) - expect(mock.thing).to be(new_mock) - end - - it "can reference other types in the original namespace" do - other = OtherThing.new - stub = Spectator::ValueStub.new(:other, other) - mock._spectator_define_stub(stub) - expect(mock.other).to be(other) - end - - describe "._spectator_clear_stubs" do - before { mock._spectator_define_stub(foo_stub) } - - it "removes previously defined stubs" do - expect { mock._spectator_clear_stubs }.to change { mock.foo }.from(:override).to(:stub) - end - end - - describe "._spectator_calls" do - before { mock._spectator_clear_calls } - - # Retrieves symbolic names of methods called on a mock. - def called_method_names(mock) - mock._spectator_calls.map(&.method) - end - - it "stores calls to stubbed methods" do - expect { mock.foo }.to change { called_method_names(mock) }.from(%i[]).to(%i[foo]) - end - - it "stores multiple calls to the same stub" do - mock.foo - expect { mock.foo }.to change { called_method_names(mock) }.from(%i[foo]).to(%i[foo foo]) - end - - it "stores arguments for a call" do - mock.bar(42) - args = Spectator::Arguments.capture(42) - call = mock._spectator_calls.first - expect(call.arguments).to eq(args) - end - end - end - - context "with a module" do - module Thing - # `extend self` cannot be used. - # The Crystal compiler doesn't report the methods as class methods when doing so. - - def self.original_method - :original - end - - def self.default_method - :original - end - - def self.stubbed_method(_value = 42) - :original - end - end - - Spectator::Mock.define_subtype(:module, Thing, MockThing) do - stub def self.stubbed_method(_value = 42) - :stubbed - end - end - - let(mock) { MockThing } - - after { mock._spectator_clear_stubs } - - it "overrides an existing method" do - stub = Spectator::ValueStub.new(:original_method, :override) - expect { mock._spectator_define_stub(stub) }.to change { mock.original_method }.from(:original).to(:override) - end - - it "doesn't affect other methods" do - stub = Spectator::ValueStub.new(:stubbed_method, :override) - expect { mock._spectator_define_stub(stub) }.to_not change { mock.original_method } - end - - it "replaces an existing default stub" do - stub = Spectator::ValueStub.new(:default_method, :override) - expect { mock._spectator_define_stub(stub) }.to change { mock.default_method }.to(:override) - end - - it "replaces an existing stubbed method" do - stub = Spectator::ValueStub.new(:stubbed_method, :override) - expect { mock._spectator_define_stub(stub) }.to change { mock.stubbed_method }.to(:override) - end - - def restricted(thing : Thing.class) - thing.stubbed_method - end - - it "can be used in type restricted methods" do - expect(restricted(mock)).to eq(:stubbed) - end - - describe "._spectator_clear_stubs" do - before do - stub = Spectator::ValueStub.new(:original_method, :override) - mock._spectator_define_stub(stub) - end - - it "removes previously defined stubs" do - expect { mock._spectator_clear_stubs }.to change { mock.original_method }.from(:override).to(:original) - end - end - - describe "._spectator_calls" do - before { mock._spectator_clear_calls } - - # Retrieves symbolic names of methods called on a mock. - def called_method_names(mock) - mock._spectator_calls.map(&.method) - end - - it "stores calls to original methods" do - expect { mock.original_method }.to change { called_method_names(mock) }.from(%i[]).to(%i[original_method]) - end - - it "stores calls to default methods" do - expect { mock.default_method }.to change { called_method_names(mock) }.from(%i[]).to(%i[default_method]) - end - - it "stores calls to stubbed methods" do - expect { mock.stubbed_method }.to change { called_method_names(mock) }.from(%i[]).to(%i[stubbed_method]) - end - - it "stores multiple calls to the same stub" do - mock.stubbed_method - expect { mock.stubbed_method }.to change { called_method_names(mock) }.from(%i[stubbed_method]).to(%i[stubbed_method stubbed_method]) - end - - it "stores arguments for a call" do - mock.stubbed_method(5) - args = Spectator::Arguments.capture(5) - call = mock._spectator_calls.first - expect(call.arguments).to eq(args) - end - end - end - - context "with a mocked module included in a class" do - module Thing - def original_method - :original - end - - def default_method - :original - end - - def stubbed_method(_value = 42) - :original - end - end - - Spectator::Mock.define_subtype(:module, Thing, MockThing, default_method: :default) do - stub def stubbed_method(_value = 42) - :stubbed - end - end - - class IncludedMock - include MockThing - end - - let(mock) { IncludedMock.new } - - it "overrides an existing method" do - stub = Spectator::ValueStub.new(:original_method, :override) - expect { mock._spectator_define_stub(stub) }.to change { mock.original_method }.from(:original).to(:override) - end - - it "doesn't affect other methods" do - stub = Spectator::ValueStub.new(:stubbed_method, :override) - expect { mock._spectator_define_stub(stub) }.to_not change { mock.original_method } - end - - it "replaces an existing default stub" do - stub = Spectator::ValueStub.new(:default_method, :override) - expect { mock._spectator_define_stub(stub) }.to change { mock.default_method }.to(:override) - end - - it "replaces an existing stubbed method" do - stub = Spectator::ValueStub.new(:stubbed_method, :override) - expect { mock._spectator_define_stub(stub) }.to change { mock.stubbed_method }.to(:override) - end - - def restricted(thing : Thing.class) - thing.default_method - end - - describe "#_spectator_clear_stubs" do - before do - stub = Spectator::ValueStub.new(:original_method, :override) - mock._spectator_define_stub(stub) - end - - it "removes previously defined stubs" do - expect { mock._spectator_clear_stubs }.to change { mock.original_method }.from(:override).to(:original) - end - end - - describe "#_spectator_calls" do - before { mock._spectator_clear_calls } - - # Retrieves symbolic names of methods called on a mock. - def called_method_names(mock) - mock._spectator_calls.map(&.method) - end - - it "stores calls to original methods" do - expect { mock.original_method }.to change { called_method_names(mock) }.from(%i[]).to(%i[original_method]) - end - - it "stores calls to default methods" do - expect { mock.default_method }.to change { called_method_names(mock) }.from(%i[]).to(%i[default_method]) - end - - it "stores calls to stubbed methods" do - expect { mock.stubbed_method }.to change { called_method_names(mock) }.from(%i[]).to(%i[stubbed_method]) - end - - it "stores multiple calls to the same stub" do - mock.stubbed_method - expect { mock.stubbed_method }.to change { called_method_names(mock) }.from(%i[stubbed_method]).to(%i[stubbed_method stubbed_method]) - end - - it "stores arguments for a call" do - mock.stubbed_method(5) - args = Spectator::Arguments.capture(5) - call = mock._spectator_calls.first - expect(call.arguments).to eq(args) - 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 { 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 - context "with a class" do - class ::MockedClass - getter _spectator_invocations = [] of Symbol - - getter method1 do - @_spectator_invocations << :method1 - 42 - end - - def method2 - @_spectator_invocations << :method2 - :original - end - - def method3 - @_spectator_invocations << :method3 - "original" - end - - def instance_variables - {% begin %}[{{@type.instance_vars.map(&.name.symbolize).splat}}]{% end %} - end - end - - Spectator::Mock.inject(:class, ::MockedClass, :mock_name, method1: 123) do - stub def method2 - :stubbed - end - end - - let(mock) { MockedClass.new } - - # Necessary to clear stubs to prevent leakages between tests. - after { mock._spectator_clear_stubs } - - it "overrides responses from methods with keyword arguments" do - expect(mock.method1).to eq(123) - end - - it "overrides responses from methods defined in the block" do - expect(mock.method2).to eq(:stubbed) - end - - it "allows methods to be stubbed" do - aggregate_failures do - expect { mock._spectator_define_stub(stub1) }.to change { mock.method1 }.to(777) - expect { mock._spectator_define_stub(stub2) }.to change { mock.method2 }.to(:override) - expect { mock._spectator_define_stub(stub3) }.to change { mock.method3 }.from("original").to("stubbed") - end - end - - it "can clear stubs" do - mock._spectator_define_stub(stub1) - mock._spectator_define_stub(stub2) - mock._spectator_define_stub(stub3) - - mock._spectator_clear_stubs - aggregate_failures do - expect(mock.method1).to eq(123) - expect(mock.method2).to eq(:stubbed) - expect(mock.method3).to eq("original") - end - end - - it "doesn't change the size of an instance" do - size = sizeof(Int64) + sizeof(Int32?) + sizeof(Array(Symbol)) # TypeID + Int32? + _spectator_invocations - expect(instance_sizeof(MockedClass)).to eq(size) - end - - it "doesn't affect instance variables" do - expect(mock.instance_variables).to contain_exactly(:method1, :_spectator_invocations) - end - - it "sets the mock name" do - args = Spectator::Arguments.capture("foo") - stub = Spectator::ValueStub.new(:method3, 0, args) - mock._spectator_define_stub(stub) - expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name." - end - - it "records invoked stubs" do - expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[]).to(%i[method2]) - expect { mock.method1 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method1]) - expect { mock.method3 }.to change { called_method_names(mock) }.from(%i[method2 method1]).to(%i[method2 method1 method3]) - end - - it "records multiple invocations of the same stub" do - mock.method2 - expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method2]) - end - - def restricted(thing : MockedClass) - thing.method1 - end - - it "can be used in type restricted methods" do - expect(restricted(mock)).to eq(123) - end - - it "does not call the original method when stubbed" do - mock.method1 - mock.method2 - mock.method3 - expect(mock._spectator_invocations).to contain_exactly(:method3) - end - end - - context "with a struct" do - struct ::MockedStruct - # Using a class variable instead of an instance variable to prevent mutability problems with stub lookup. - class_getter _spectator_invocations = [] of Symbol - - @method1 = 42 - - def method1 - @@_spectator_invocations << :method1 - @method1 - end - - def method2 - @@_spectator_invocations << :method2 - :original - end - - def method3 - @@_spectator_invocations << :method3 - "original" - end - - def instance_variables - {% begin %}[{{@type.instance_vars.map(&.name.symbolize).splat}}]{% end %} - end - end - - Spectator::Mock.inject(:struct, ::MockedStruct, :mock_name, method1: 123) do - stub def method2 - :stubbed - end - end - - let(mock) { MockedStruct.new } - - # Necessary to clear stubs to prevent leakages between tests. - after { mock._spectator_clear_stubs } - after { MockedStruct._spectator_invocations.clear } - - it "overrides responses from methods with keyword arguments" do - expect(mock.method1).to eq(123) - end - - it "overrides responses from methods defined in the block" do - expect(mock.method2).to eq(:stubbed) - end - - it "allows methods to be stubbed" do - aggregate_failures do - expect { mock._spectator_define_stub(stub1) }.to change { mock.method1 }.to(777) - expect { mock._spectator_define_stub(stub2) }.to change { mock.method2 }.to(:override) - expect { mock._spectator_define_stub(stub3) }.to change { mock.method3 }.from("original").to("stubbed") - end - end - - it "doesn't change the size of an instance" do - expect(sizeof(MockedStruct)).to eq(sizeof(Int32)) - end - - it "doesn't affect instance variables" do - expect(mock.instance_variables).to contain_exactly(:method1) - end - - it "sets the mock name" do - args = Spectator::Arguments.capture("foo") - stub = Spectator::ValueStub.new(:method3, 0, args) - mock._spectator_define_stub(stub) - expect { mock.method3 }.to raise_error(Spectator::UnexpectedMessage, /mock_name/), "Raised error doesn't contain the mocked name." - end - - it "records invoked stubs" do - expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[]).to(%i[method2]) - expect { mock.method1 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method1]) - expect { mock.method3 }.to change { called_method_names(mock) }.from(%i[method2 method1]).to(%i[method2 method1 method3]) - end - - it "records multiple invocations of the same stub" do - mock.method2 - expect { mock.method2 }.to change { called_method_names(mock) }.from(%i[method2]).to(%i[method2 method2]) - end - - def restricted(thing : MockedStruct) - thing.method1 - end - - it "can be used in type restricted methods" do - expect(restricted(mock)).to eq(123) - end - - it "does not call the original method when stubbed" do - mock.method1 - mock.method2 - mock.method3 - expect(MockedStruct._spectator_invocations).to contain_exactly(:method3) - end - end - - context "class method stubs" do - class ::Thing - def self.foo - :original - end - - def self.bar(arg) - arg - end - - def self.baz(arg, &) - yield - end - end - - Spectator::Mock.inject(:class, ::Thing) do - stub def self.foo - :stub - end - end - - let(mock) { Thing } - let(foo_stub) { Spectator::ValueStub.new(:foo, :override) } - - after { mock._spectator_clear_stubs } - - it "overrides an existing method" do - expect { mock._spectator_define_stub(foo_stub) }.to change { mock.foo }.from(:stub).to(:override) - end - - it "doesn't affect other methods" do - expect { mock._spectator_define_stub(foo_stub) }.to_not change { mock.bar(42) } - end - - it "replaces an existing stub" do - mock._spectator_define_stub(foo_stub) - stub = Spectator::ValueStub.new(:foo, :replacement) - expect { mock._spectator_define_stub(stub) }.to change { mock.foo }.to(:replacement) - end - - it "picks the correct stub based on arguments" do - stub1 = Spectator::ValueStub.new(:bar, :fallback) - stub2 = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match)) - mock._spectator_define_stub(stub1) - mock._spectator_define_stub(stub2) - aggregate_failures do - expect(mock.bar(:wrong)).to eq(:fallback) - expect(mock.bar(:match)).to eq(:override) - end - end - - it "only uses a stub if an argument constraint is met" do - stub = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match)) - mock._spectator_define_stub(stub) - aggregate_failures do - expect(mock.bar(:original)).to eq(:original) - expect(mock.bar(:match)).to eq(:override) - end - end - - it "ignores the block argument if not in the constraint" do - stub1 = Spectator::ValueStub.new(:baz, 1) - stub2 = Spectator::ValueStub.new(:baz, 2, Spectator::Arguments.capture(3)) - mock._spectator_define_stub(stub1) - mock._spectator_define_stub(stub2) - aggregate_failures do - expect(mock.baz(5) { 42 }).to eq(1) - expect(mock.baz(3) { 42 }).to eq(2) - end - end - - def restricted(thing : Thing.class) - thing.foo - end - - it "can be used in type restricted methods" do - expect(restricted(mock)).to eq(:stub) - end - - describe "._spectator_clear_stubs" do - before { mock._spectator_define_stub(foo_stub) } - - it "removes previously defined stubs" do - expect { mock._spectator_clear_stubs }.to change { mock.foo }.from(:override).to(:stub) - end - end - - describe "._spectator_calls" do - before { mock._spectator_clear_calls } - - # Retrieves symbolic names of methods called on a mock. - def called_method_names(mock) - mock._spectator_calls.map(&.method) - end - - it "stores calls to stubbed methods" do - expect { mock.foo }.to change { called_method_names(mock) }.from(%i[]).to(%i[foo]) - end - - it "stores multiple calls to the same stub" do - mock.foo - expect { mock.foo }.to change { called_method_names(mock) }.from(%i[foo]).to(%i[foo foo]) - end - - it "stores arguments for a call" do - mock.bar(42) - args = Spectator::Arguments.capture(42) - call = mock._spectator_calls.first - expect(call.arguments).to eq(args) - 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 { 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/multi_value_stub_spec.cr b/spec/spectator/mocks/multi_value_stub_spec.cr deleted file mode 100644 index a68a273..0000000 --- a/spec/spectator/mocks/multi_value_stub_spec.cr +++ /dev/null @@ -1,173 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::MultiValueStub do - let(method_call) { Spectator::MethodCall.capture(:foo) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - subject(stub) { described_class.new(:foo, [3, 5, 7], location: location) } - - it "stores the method name" do - expect(stub.method).to eq(:foo) - end - - it "stores the location" do - expect(stub.location).to eq(location) - end - - describe "#call" do - it "returns the values in order" do - values = Array.new(3) { stub.call(method_call) } - expect(values).to eq([3, 5, 7]) - end - - it "returns the final value after exhausting other values" do - values = Array.new(5) { stub.call(method_call) } - expect(values).to eq([3, 5, 7, 7, 7]) - end - end - - context Spectator::StubModifiers do - describe "#and_return(value)" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::MultiValueStub.new(:foo, [3, 5, 7], arguments, location) } - subject(stub) { original.and_return(123) } - - it "produces a stub that returns a value" do - expect(stub.call(method_call)).to eq(123) - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - - describe "#and_return(*values)" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::MultiValueStub.new(:foo, [3, 5, 7], arguments, location) } - subject(stub) { original.and_return(3, 2, 1, 0) } - - it "produces a stub that returns values" do - values = Array.new(5) { stub.call(method_call) } - expect(values).to eq([3, 2, 1, 0, 0]) - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - - describe "#and_raise" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::MultiValueStub.new(:foo, [3, 5, 7], arguments, location) } - let(new_exception) { ArgumentError.new("Test argument error") } - subject(stub) { original.and_raise(new_exception) } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error") - end - - context "with a class and message" do - subject(stub) { original.and_raise(ArgumentError, "Test argument error") } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error") - end - end - - context "with a message" do - subject(stub) { original.and_raise("Test exception") } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(Exception, "Test exception") - end - end - - context "with a class" do - subject(stub) { original.and_raise(ArgumentError) } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError) - end - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - end - - describe "#===" do - subject { stub === call } - - context "with a matching method name" do - let(call) { Spectator::MethodCall.capture(:foo, "foobar") } - - it "returns true" do - is_expected.to be_true - end - end - - context "with a different method name" do - let(call) { Spectator::MethodCall.capture(:bar, "foobar") } - - it "returns false" do - is_expected.to be_false - end - end - - context "with a constraint" do - let(constraint) { Spectator::Arguments.capture(/foo/) } - let(stub) { Spectator::ValueStub.new(:foo, 42, constraint) } - - context "with a matching method name" do - let(call) { Spectator::MethodCall.capture(:foo, "foobar") } - - it "returns true" do - is_expected.to be_true - end - - context "with a non-matching arguments" do - let(call) { Spectator::MethodCall.capture(:foo, "baz") } - - it "returns false" do - is_expected.to be_false - end - end - end - - context "with a different method name" do - let(call) { Spectator::MethodCall.capture(:bar, "foobar") } - - it "returns false" do - is_expected.to be_false - end - end - end - end -end diff --git a/spec/spectator/mocks/null_double_spec.cr b/spec/spectator/mocks/null_double_spec.cr deleted file mode 100644 index 6ebca50..0000000 --- a/spec/spectator/mocks/null_double_spec.cr +++ /dev/null @@ -1,503 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::NullDouble do - Spectator::NullDouble.define(EmptyDouble) - Spectator::NullDouble.define(FooBarDouble, "dbl-name", foo: 42, bar: "baz") - - # The subject `dbl` must be carefully used in sub-contexts, otherwise it pollutes parent scopes. - # This changes the type of `dbl` to `Double`, which produces a union of methods and their return types. - context "plain double" do - subject(dbl) { FooBarDouble.new } - - it "responds to defined messages" do - aggregate_failures do - expect(dbl.foo).to eq(42) - expect(dbl.bar).to eq("baz") - end - end - - it "returns self on undefined messages" do - expect(dbl.baz).to be(dbl) - end - - it "has a non-union return type" do - aggregate_failures do - expect(dbl.foo).to compile_as(Int32) - expect(dbl.bar).to compile_as(String) - end - end - - context "blocks" do - it "supports blocks" do - aggregate_failures do - expect(dbl.foo { nil }).to eq(42) - expect(dbl.bar { nil }).to eq("baz") - end - end - - it "supports blocks and has non-union return types" do - aggregate_failures do - expect(dbl.foo { nil }).to compile_as(Int32) - expect(dbl.bar { nil }).to compile_as(String) - end - end - - it "returns self on undefined messages" do - expect(dbl.baz { nil }).to be(dbl) - end - end - end - - context "with abstract stubs and return type annotations" do - Spectator::NullDouble.define(TestDouble2) do - stub abstract def foo(value) : String - end - - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(stub) { Spectator::ValueStub.new(:foo, "bar", arguments).as(Spectator::Stub) } - subject(dbl) { TestDouble2.new([stub]) } - - it "enforces the return type" do - expect(dbl.foo("foobar")).to compile_as(String) - end - - it "raises on non-matching arguments" do - expect { dbl.foo("bar") }.to raise_error(Spectator::UnexpectedMessage, /foo/) - end - - it "raises on non-matching stub" do - stub = Spectator::ValueStub.new(:foo, 42, arguments).as(Spectator::Stub) - dbl._spectator_define_stub(stub) - expect { dbl.foo("foobar") }.to raise_error(TypeCastError, /String/) - end - end - - context "with nillable return type annotations" do - Spectator::NullDouble.define(TestDouble) do - stub abstract def foo : String? - stub abstract def bar : Nil - end - - let(foo_stub) { Spectator::ValueStub.new(:foo, nil).as(Spectator::Stub) } - let(bar_stub) { Spectator::ValueStub.new(:bar, nil).as(Spectator::Stub) } - subject(dbl) { TestDouble.new([foo_stub, bar_stub]) } - - it "doesn't raise on nil" do - aggregate_failures do - expect(dbl.foo).to be_nil - expect(dbl.bar).to be_nil - end - end - end - - context "with a method that uses NoReturn" do - Spectator::NullDouble.define(NoReturnDouble) do - 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([ - Spectator::ValueStub.new(:"!=", false), - Spectator::ValueStub.new(:"!~", false), - Spectator::ValueStub.new(:"==", true), - Spectator::ValueStub.new(:"===", true), - Spectator::ValueStub.new(:"=~", nil), - Spectator::ValueStub.new(:class, EmptyDouble), - Spectator::ValueStub.new(:dup, EmptyDouble.new), - Spectator::ValueStub.new(:"in?", true), - Spectator::ValueStub.new(:inspect, "inspect"), - Spectator::ValueStub.new(:itself, EmptyDouble.new), - Spectator::ValueStub.new(:"not_nil!", EmptyDouble.new), - Spectator::ValueStub.new(:pretty_inspect, "pretty_inspect"), - Spectator::ValueStub.new(:tap, EmptyDouble.new), - Spectator::ValueStub.new(:to_json, "to_json"), - Spectator::ValueStub.new(:to_pretty_json, "to_pretty_json"), - Spectator::ValueStub.new(:to_s, "to_s"), - Spectator::ValueStub.new(:to_yaml, "to_yaml"), - Spectator::ValueStub.new(:try, nil), - Spectator::ValueStub.new(:object_id, 42_u64), - Spectator::ValueStub.new(:"same?", true), - ] of Spectator::Stub) - end - - it "responds with defined messages" do - aggregate_failures do - expect(dbl.!=(42)).to be_false - expect(dbl.!~(42)).to be_false - expect(dbl.==(42)).to be_true - expect(dbl.===(42)).to be_true - expect(dbl.=~(42)).to be_nil - expect(dbl.class).to eq(EmptyDouble) - expect(dbl.dup).to be_a(EmptyDouble) - expect(dbl.in?([42])).to eq(true) - expect(dbl.in?(1, 2, 3)).to eq(true) - expect(dbl.inspect).to eq("inspect") - expect(dbl.itself).to be_a(EmptyDouble) - expect(dbl.not_nil!).to be_a(EmptyDouble) - expect(dbl.pretty_inspect).to eq("pretty_inspect") - expect(dbl.tap { nil }).to be_a(EmptyDouble) - expect(dbl.to_json).to eq("to_json") - expect(dbl.to_pretty_json).to eq("to_pretty_json") - expect(dbl.to_s).to eq("to_s") - expect(dbl.to_yaml).to eq("to_yaml") - expect(dbl.try { nil }).to be_nil - expect(dbl.object_id).to eq(42_u64) - expect(dbl.same?(dbl)).to be_true - expect(dbl.same?(nil)).to be_true - end - end - - it "has a non-union return type" do - expect(dbl.inspect).to compile_as(String) - end - end - - context "without common object methods" do - subject(dbl) { EmptyDouble.new } - - it "returns original implementation with undefined messages" do - hasher = Crystal::Hasher.new - aggregate_failures do - expect(dbl.!=(42)).to be_true - expect(dbl.!~(42)).to be_true - expect(dbl.==(42)).to be_false - expect(dbl.===(42)).to be_false - expect(dbl.=~(42)).to be_nil - expect(dbl.class).to eq(EmptyDouble) - expect(dbl.dup).to be_a(EmptyDouble) - expect(dbl.hash(hasher)).to be_a(Crystal::Hasher) - expect(dbl.hash).to be_a(UInt64) - expect(dbl.in?([42])).to be_false - expect(dbl.in?(1, 2, 3)).to be_false - expect(dbl.itself).to be(dbl) - expect(dbl.not_nil!).to be(dbl) - expect(dbl.tap { nil }).to be(dbl) - expect(dbl.try { nil }).to be_nil - expect(dbl.object_id).to be_a(UInt64) - expect(dbl.same?(dbl)).to be_true - expect(dbl.same?(nil)).to be_false - end - end - end - - context "with arguments constraints" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - - context "without common object methods" do - Spectator::NullDouble.define(TestDouble) do - stub abstract def foo(value) : String - stub abstract def foo(value, & : -> _) : String - end - - let(stub) { Spectator::ValueStub.new(:foo, "bar", arguments).as(Spectator::Stub) } - subject(dbl) { TestDouble.new([stub]) } - - it "returns the response when constraint satisfied" do - expect(dbl.foo("foobar")).to eq("bar") - end - - it "raises when constraint unsatisfied" do - expect { dbl.foo("baz") }.to raise_error(Spectator::UnexpectedMessage, /foo/) - end - - it "returns self when argument count doesn't match" do - expect(dbl.foo).to be(dbl) - end - - it "ignores the block argument if not in the constraint" do - expect(dbl.foo("foobar") { nil }).to eq("bar") - end - end - - context "with common object methods" do - Spectator::NullDouble.define(TestDouble) do - stub abstract def hash(hasher) : Crystal::Hasher - end - - let(hasher) { Crystal::Hasher.new } - let(stub) { Spectator::ValueStub.new(:hash, hasher, arguments).as(Spectator::Stub) } - subject(dbl) { TestDouble.new([stub]) } - - it "returns the response when constraint satisfied" do - expect(dbl.hash("foobar")).to be(hasher) - end - - it "raises when constraint unsatisfied" do - expect { dbl.hash("baz") }.to raise_error(Spectator::UnexpectedMessage, /hash/) - end - - it "raises when argument count doesn't match" do - expect { dbl.hash }.to raise_error(Spectator::UnexpectedMessage, /hash/) - end - end - end - - context "class method stubs" do - Spectator::NullDouble.define(ClassDouble) do - stub def self.foo - :stub - end - - stub def self.bar(arg) - arg - end - - stub def self.baz(arg, &) - yield - end - end - - subject(dbl) { ClassDouble } - let(foo_stub) { Spectator::ValueStub.new(:foo, :override) } - - after { dbl._spectator_clear_stubs } - - it "overrides an existing method" do - expect { dbl._spectator_define_stub(foo_stub) }.to change { dbl.foo }.from(:stub).to(:override) - end - - it "doesn't affect other methods" do - expect { dbl._spectator_define_stub(foo_stub) }.to_not change { dbl.bar(42) } - end - - it "replaces an existing stub" do - dbl._spectator_define_stub(foo_stub) - stub = Spectator::ValueStub.new(:foo, :replacement) - expect { dbl._spectator_define_stub(stub) }.to change { dbl.foo }.to(:replacement) - end - - it "picks the correct stub based on arguments" do - stub1 = Spectator::ValueStub.new(:bar, :fallback) - stub2 = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match)) - dbl._spectator_define_stub(stub1) - dbl._spectator_define_stub(stub2) - aggregate_failures do - expect(dbl.bar(:wrong)).to eq(:fallback) - expect(dbl.bar(:match)).to eq(:override) - end - end - - it "only uses a stub if an argument constraint is met" do - stub = Spectator::ValueStub.new(:bar, :override, Spectator::Arguments.capture(:match)) - dbl._spectator_define_stub(stub) - aggregate_failures do - expect(dbl.bar(:original)).to eq(:original) - expect(dbl.bar(:match)).to eq(:override) - end - end - - it "ignores the block argument if not in the constraint" do - stub1 = Spectator::ValueStub.new(:baz, 1) - stub2 = Spectator::ValueStub.new(:baz, 2, Spectator::Arguments.capture(3)) - dbl._spectator_define_stub(stub1) - dbl._spectator_define_stub(stub2) - aggregate_failures do - expect(dbl.baz(5) { 42 }).to eq(1) - expect(dbl.baz(3) { 42 }).to eq(2) - end - end - - describe "._spectator_clear_stubs" do - before { dbl._spectator_define_stub(foo_stub) } - - it "removes previously defined stubs" do - expect { dbl._spectator_clear_stubs }.to change { dbl.foo }.from(:override).to(:stub) - end - end - - describe "._spectator_calls" do - before { dbl._spectator_clear_calls } - - # Retrieves symbolic names of methods called on a double. - def called_method_names(dbl) - dbl._spectator_calls.map(&.method) - end - - it "stores calls to stubbed methods" do - expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo]) - end - - it "stores multiple calls to the same stub" do - dbl.foo - expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo]) - end - - it "stores arguments for a call" do - dbl.bar(42) - args = Spectator::Arguments.capture(42) - call = dbl._spectator_calls.first - expect(call.arguments).to eq(args) - end - end - end - - describe "#_spectator_define_stub" do - subject(dbl) { FooBarDouble.new } - let(stub3) { Spectator::ValueStub.new(:foo, 3) } - let(stub5) { Spectator::ValueStub.new(:foo, 5) } - let(stub7) { Spectator::ValueStub.new(:foo, 7, Spectator::Arguments.capture(:lucky)) } - - it "overrides an existing method" do - expect { dbl._spectator_define_stub(stub3) }.to change { dbl.foo }.from(42).to(3) - end - - it "replaces an existing stub" do - dbl._spectator_define_stub(stub3) - expect { dbl._spectator_define_stub(stub5) }.to change { dbl.foo }.from(3).to(5) - end - - it "doesn't affect other methods" do - expect { dbl._spectator_define_stub(stub5) }.to_not change { dbl.bar } - end - - it "picks the correct stub based on arguments" do - dbl._spectator_define_stub(stub5) - dbl._spectator_define_stub(stub7) - aggregate_failures do - expect(dbl.foo).to eq(5) - expect(dbl.foo(:lucky)).to eq(7) - end - end - - it "only uses a stub if an argument constraint is met" do - dbl._spectator_define_stub(stub7) - aggregate_failures do - expect(dbl.foo).to eq(42) - expect(dbl.foo(:lucky)).to eq(7) - end - end - - it "ignores the block argument if not in the constraint" do - dbl._spectator_define_stub(stub5) - dbl._spectator_define_stub(stub7) - aggregate_failures do - expect(dbl.foo { nil }).to eq(5) - expect(dbl.foo(:lucky) { nil }).to eq(7) - end - end - end - - describe "#_spectator_clear_stubs" do - subject(dbl) { FooBarDouble.new } - let(stub) { Spectator::ValueStub.new(:foo, 5) } - - before { dbl._spectator_define_stub(stub) } - - it "removes previously defined stubs" do - expect { dbl._spectator_clear_stubs }.to change { dbl.foo }.from(5).to(42) - end - end - - describe "#_spectator_calls" do - subject(dbl) { FooBarDouble.new } - let(stub) { Spectator::ValueStub.new(:foo, 5) } - - before { dbl._spectator_define_stub(stub) } - - # Retrieves symbolic names of methods called on a double. - def called_method_names(dbl) - dbl._spectator_calls.map(&.method) - end - - it "stores calls to stubbed methods" do - expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[]).to(%i[foo]) - end - - it "stores multiple calls to the same stub" do - dbl.foo - expect { dbl.foo }.to change { called_method_names(dbl) }.from(%i[foo]).to(%i[foo foo]) - end - - it "stores calls to non-stubbed methods" do - expect { dbl.baz }.to change { called_method_names(dbl) }.from(%i[]).to(%i[baz]) - end - - it "stores arguments for a call" do - dbl.foo(42) - args = Spectator::Arguments.capture(42) - call = dbl._spectator_calls.first - expect(call.arguments).to eq(args) - end - end - - describe "#to_s" do - subject(string) { dbl.to_s } - - context "with a name" do - let(dbl) { FooBarDouble.new } - - it "indicates it's a double" do - expect(string).to contain("NullDouble") - end - - it "contains the double name" do - expect(string).to contain("dbl-name") - end - end - - context "without a name" do - let(dbl) { EmptyDouble.new } - - it "contains the double type" do - expect(string).to contain("NullDouble") - end - - it "contains \"Anonymous\"" do - expect(string).to contain("Anonymous") - end - end - end - - describe "#inspect" do - subject(string) { dbl.inspect } - - context "with a name" do - let(dbl) { FooBarDouble.new } - - it "contains the double type" do - expect(string).to contain("NullDouble") - end - - it "contains the double name" do - expect(string).to contain("dbl-name") - end - - it "contains the object ID" do - expect(string).to contain(dbl.object_id.to_s(16)) - end - end - - context "without a name" do - let(dbl) { EmptyDouble.new } - - it "contains the double type" do - expect(string).to contain("NullDouble") - end - - it "contains \"Anonymous\"" do - expect(string).to contain("Anonymous") - end - - it "contains the object ID" do - expect(string).to contain(dbl.object_id.to_s(16)) - end - end - end -end diff --git a/spec/spectator/mocks/null_stub_spec.cr b/spec/spectator/mocks/null_stub_spec.cr deleted file mode 100644 index 662fd23..0000000 --- a/spec/spectator/mocks/null_stub_spec.cr +++ /dev/null @@ -1,165 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::NullStub do - let(method_call) { Spectator::MethodCall.capture(:foo) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - subject(stub) { described_class.new(:foo, location: location) } - - it "stores the method name" do - expect(stub.method).to eq(:foo) - end - - it "stores the location" do - expect(stub.location).to eq(location) - end - - it "returns nil" do - expect(stub.call(method_call)).to be_nil - end - - context Spectator::StubModifiers do - describe "#and_return(value)" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::NullStub.new(:foo, arguments, location) } - subject(stub) { original.and_return(42) } - - it "produces a stub that returns a value" do - expect(stub.call(method_call)).to eq(42) - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - - describe "#and_return(*values)" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::NullStub.new(:foo, arguments, location) } - subject(stub) { original.and_return(3, 2, 1, 0) } - - it "produces a stub that returns values" do - values = Array.new(5) { stub.call(method_call) } - expect(values).to eq([3, 2, 1, 0, 0]) - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - - describe "#and_raise" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::NullStub.new(:foo, arguments, location) } - let(new_exception) { ArgumentError.new("Test argument error") } - subject(stub) { original.and_raise(new_exception) } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error") - end - - context "with a class and message" do - subject(stub) { original.and_raise(ArgumentError, "Test argument error") } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error") - end - end - - context "with a message" do - subject(stub) { original.and_raise("Test exception") } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(Exception, "Test exception") - end - end - - context "with a class" do - subject(stub) { original.and_raise(ArgumentError) } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError) - end - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - end - - describe "#===" do - subject { stub === call } - - context "with a matching method name" do - let(call) { Spectator::MethodCall.capture(:foo, "foobar") } - - it "returns true" do - is_expected.to be_true - end - end - - context "with a different method name" do - let(call) { Spectator::MethodCall.capture(:bar, "foobar") } - - it "returns false" do - is_expected.to be_false - end - end - - context "with a constraint" do - let(constraint) { Spectator::Arguments.capture(/foo/) } - let(stub) { described_class.new(:foo, constraint) } - - context "with a matching method name" do - let(call) { Spectator::MethodCall.capture(:foo, "foobar") } - - it "returns true" do - is_expected.to be_true - end - - context "with a non-matching arguments" do - let(call) { Spectator::MethodCall.capture(:foo, "baz") } - - it "returns false" do - is_expected.to be_false - end - end - end - - context "with a different method name" do - let(call) { Spectator::MethodCall.capture(:bar, "foobar") } - - it "returns false" do - is_expected.to be_false - end - end - end - end -end diff --git a/spec/spectator/mocks/proc_stub_spec.cr b/spec/spectator/mocks/proc_stub_spec.cr deleted file mode 100644 index bca4a05..0000000 --- a/spec/spectator/mocks/proc_stub_spec.cr +++ /dev/null @@ -1,182 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::ProcStub do - let(method_call) { Spectator::MethodCall.capture(:foo) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(proc) { Proc(Spectator::AbstractArguments, Int32).new { @call_count += 1 } } - subject(stub) { described_class.new(:foo, proc, location: location) } - - @call_count = 0 - - it "stores the method name" do - expect(stub.method).to eq(:foo) - end - - it "stores the location" do - expect(stub.location).to eq(location) - end - - it "calls the proc" do - expect(stub.call(method_call)).to eq(1) - end - - it "calls the proc for each invocation" do - stub.call(method_call) - expect { stub.call(method_call) }.to change { @call_count }.from(1).to(2) - end - - it "passed the original arguments" do - proc = Proc(Spectator::AbstractArguments, Spectator::AbstractArguments).new { |a| a } - stub = described_class.new(:foo, proc) - args = Spectator::Arguments.capture(42, bar: "baz") - call = Spectator::MethodCall.new(:foo, args) - captured = stub.call(call) - expect(captured).to eq(args) - end - - context Spectator::StubModifiers do - describe "#and_return(value)" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::ProcStub.new(:foo, proc, arguments, location) } - subject(stub) { original.and_return(123) } - - it "produces a stub that returns a value" do - expect(stub.call(method_call)).to eq(123) - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - - describe "#and_return(*values)" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::ProcStub.new(:foo, proc, arguments, location) } - subject(stub) { original.and_return(3, 2, 1, 0) } - - it "produces a stub that returns values" do - values = Array.new(5) { stub.call(method_call) } - expect(values).to eq([3, 2, 1, 0, 0]) - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - - describe "#and_raise" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::ProcStub.new(:foo, proc, arguments, location) } - let(new_exception) { ArgumentError.new("Test argument error") } - subject(stub) { original.and_raise(new_exception) } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error") - end - - context "with a class and message" do - subject(stub) { original.and_raise(ArgumentError, "Test argument error") } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error") - end - end - - context "with a message" do - subject(stub) { original.and_raise("Test exception") } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(Exception, "Test exception") - end - end - - context "with a class" do - subject(stub) { original.and_raise(ArgumentError) } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError) - end - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - end - - describe "#===" do - subject { stub === call } - - context "with a matching method name" do - let(call) { Spectator::MethodCall.capture(:foo, "foobar") } - - it "returns true" do - is_expected.to be_true - end - end - - context "with a different method name" do - let(call) { Spectator::MethodCall.capture(:bar, "foobar") } - - it "returns false" do - is_expected.to be_false - end - end - - context "with a constraint" do - let(constraint) { Spectator::Arguments.capture(/foo/) } - let(stub) { Spectator::ValueStub.new(:foo, 42, constraint) } - - context "with a matching method name" do - let(call) { Spectator::MethodCall.capture(:foo, "foobar") } - - it "returns true" do - is_expected.to be_true - end - - context "with a non-matching arguments" do - let(call) { Spectator::MethodCall.capture(:foo, "baz") } - - it "returns false" do - is_expected.to be_false - end - end - end - - context "with a different method name" do - let(call) { Spectator::MethodCall.capture(:bar, "foobar") } - - it "returns false" do - is_expected.to be_false - end - end - end - end -end diff --git a/spec/spectator/mocks/reference_mock_registry_spec.cr b/spec/spectator/mocks/reference_mock_registry_spec.cr deleted file mode 100644 index a872367..0000000 --- a/spec/spectator/mocks/reference_mock_registry_spec.cr +++ /dev/null @@ -1,93 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::ReferenceMockRegistry do - subject(registry) { described_class.new } - let(obj) { "foobar" } - let(stub) { Spectator::ValueStub.new(:test, 42) } - let(stubs) { [stub] of Spectator::Stub } - let(no_stubs) { [] of Spectator::Stub } - let(call) { Spectator::MethodCall.capture(:method2, 5) } - let(calls) { [call] } - let(no_calls) { [] of Spectator::MethodCall } - - it "initially has no stubs" do - expect(registry[obj].stubs).to be_empty - end - - it "initially has no calls" do - expect(registry[obj].calls).to be_empty - end - - it "stores stubs for an object" do - expect { registry[obj].stubs << stub }.to change { registry[obj].stubs }.from(no_stubs).to(stubs) - end - - it "stores calls for an object" do - expect { registry[obj].calls << call }.to change { registry[obj].calls }.from(no_calls).to(calls) - end - - it "isolates stubs between different objects" do - obj1 = "foo" - obj2 = "bar" - registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42) - expect { registry[obj1].stubs << stub }.to_not change { registry[obj2].stubs } - end - - it "isolates calls between different objects" do - obj1 = "foo" - obj2 = "bar" - registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42) - expect { registry[obj1].calls << call }.to_not change { registry[obj2].calls } - end - - describe "#fetch" do - it "retrieves existing stubs" do - registry[obj].stubs << stub - expect(registry.fetch(obj) { no_stubs }.stubs).to eq(stubs) - end - - it "stores stubs on the first retrieval" do - expect(registry.fetch(obj) { stubs }.stubs).to eq(stubs) - end - - it "isolates stubs between different objects" do - obj1 = "foo" - obj2 = "bar" - registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42) - expect { registry.fetch(obj1) { no_stubs }.stubs }.to_not change { registry[obj2].stubs } - end - - it "isolates calls between different objects" do - obj1 = "foo" - obj2 = "bar" - registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42) - expect { registry.fetch(obj1) { no_stubs }.calls }.to_not change { registry[obj2].calls } - end - end - - describe "#delete" do - it "clears stubs for an object" do - registry[obj].stubs << stub - expect { registry.delete(obj) }.to change { registry[obj].stubs }.from(stubs).to(no_stubs) - end - - it "doesn't clear initial stubs provided with #fetch" do - registry[obj].stubs << Spectator::ValueStub.new(:stub2, 42) - expect { registry.delete(obj) }.to change { registry.fetch(obj) { stubs }.stubs }.to(stubs) - end - - it "isolates stubs between different objects" do - obj1 = "foo" - obj2 = "bar" - registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42) - expect { registry.delete(obj1) }.to_not change { registry[obj2].stubs } - end - - it "isolates calls between different objects" do - obj1 = "foo" - obj2 = "bar" - registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42) - expect { registry.delete(obj1) }.to_not change { registry[obj2].calls } - end - end -end diff --git a/spec/spectator/mocks/value_mock_registry_spec.cr b/spec/spectator/mocks/value_mock_registry_spec.cr deleted file mode 100644 index de2a1cc..0000000 --- a/spec/spectator/mocks/value_mock_registry_spec.cr +++ /dev/null @@ -1,93 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::ValueMockRegistry do - subject(registry) { Spectator::ValueMockRegistry(Int32).new } - let(obj) { 42 } - let(stub) { Spectator::ValueStub.new(:test, 5) } - let(stubs) { [stub] of Spectator::Stub } - let(no_stubs) { [] of Spectator::Stub } - let(call) { Spectator::MethodCall.capture(:method2, 5) } - let(calls) { [call] } - let(no_calls) { [] of Spectator::MethodCall } - - it "initially has no stubs" do - expect(registry[obj].stubs).to be_empty - end - - it "initially has no calls" do - expect(registry[obj].calls).to be_empty - end - - it "stores stubs for an object" do - expect { registry[obj].stubs << stub }.to change { registry[obj].stubs }.from(no_stubs).to(stubs) - end - - it "stores calls for an object" do - expect { registry[obj].calls << call }.to change { registry[obj].calls }.from(no_calls).to(calls) - end - - it "isolates stubs between different objects" do - obj1 = 1 - obj2 = 2 - registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42) - expect { registry[obj1].stubs << stub }.to_not change { registry[obj2].stubs } - end - - it "isolates calls between different objects" do - obj1 = 1 - obj2 = 2 - registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42) - expect { registry[obj1].calls << call }.to_not change { registry[obj2].calls } - end - - describe "#fetch" do - it "retrieves existing stubs" do - registry[obj].stubs << stub - expect(registry.fetch(obj) { no_stubs }.stubs).to eq(stubs) - end - - it "stores stubs on the first retrieval" do - expect(registry.fetch(obj) { stubs }.stubs).to eq(stubs) - end - - it "isolates stubs between different objects" do - obj1 = 1 - obj2 = 2 - registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42) - expect { registry.fetch(obj1) { no_stubs }.stubs }.to_not change { registry[obj2].stubs } - end - - it "isolates calls between different objects" do - obj1 = 1 - obj2 = 2 - registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42) - expect { registry.fetch(obj1) { no_stubs }.calls }.to_not change { registry[obj2].calls } - end - end - - describe "#delete" do - it "clears stubs for an object" do - registry[obj].stubs << stub - expect { registry.delete(obj) }.to change { registry[obj].stubs }.from(stubs).to(no_stubs) - end - - it "doesn't clear initial stubs provided with #fetch" do - registry[obj].stubs << Spectator::ValueStub.new(:stub2, 42) - expect { registry.delete(obj) }.to change { registry.fetch(obj) { stubs }.stubs }.to(stubs) - end - - it "isolates stubs between different objects" do - obj1 = 1 - obj2 = 2 - registry[obj2].stubs << Spectator::ValueStub.new(:obj2, 42) - expect { registry.delete(obj1) }.to_not change { registry[obj2].stubs } - end - - it "isolates calls between different objects" do - obj1 = 1 - obj2 = 2 - registry[obj2].calls << Spectator::MethodCall.capture(:method1, 42) - expect { registry.delete(obj1) }.to_not change { registry[obj2].calls } - end - end -end diff --git a/spec/spectator/mocks/value_stub_spec.cr b/spec/spectator/mocks/value_stub_spec.cr deleted file mode 100644 index 6f71619..0000000 --- a/spec/spectator/mocks/value_stub_spec.cr +++ /dev/null @@ -1,165 +0,0 @@ -require "../../spec_helper" - -Spectator.describe Spectator::ValueStub do - let(method_call) { Spectator::MethodCall.capture(:foo) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - subject(stub) { described_class.new(:foo, 42, location: location) } - - it "stores the method name" do - expect(stub.method).to eq(:foo) - end - - it "stores the location" do - expect(stub.location).to eq(location) - end - - it "stores the return value" do - expect(stub.call(method_call)).to eq(42) - end - - context Spectator::StubModifiers do - describe "#and_return(value)" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::ValueStub.new(:foo, 42, arguments, location) } - subject(stub) { original.and_return(123) } - - it "produces a stub that returns a value" do - expect(stub.call(method_call)).to eq(123) - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - - describe "#and_return(*values)" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::ValueStub.new(:foo, 42, arguments, location) } - subject(stub) { original.and_return(3, 2, 1, 0) } - - it "produces a stub that returns values" do - values = Array.new(5) { stub.call(method_call) } - expect(values).to eq([3, 2, 1, 0, 0]) - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - - describe "#and_raise" do - let(arguments) { Spectator::Arguments.capture(/foo/) } - let(location) { Spectator::Location.new(__FILE__, __LINE__) } - let(original) { Spectator::ValueStub.new(:foo, 42, arguments, location) } - let(new_exception) { ArgumentError.new("Test argument error") } - subject(stub) { original.and_raise(new_exception) } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error") - end - - context "with a class and message" do - subject(stub) { original.and_raise(ArgumentError, "Test argument error") } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError, "Test argument error") - end - end - - context "with a message" do - subject(stub) { original.and_raise("Test exception") } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(Exception, "Test exception") - end - end - - context "with a class" do - subject(stub) { original.and_raise(ArgumentError) } - - it "produces a stub that raises" do - expect { stub.call(method_call) }.to raise_error(ArgumentError) - end - end - - it "retains the method name" do - expect(stub.method).to eq(:foo) - end - - it "retains the arguments constraint" do - expect(stub.constraint).to eq(arguments) - end - - it "retains the location" do - expect(stub.location).to eq(location) - end - end - end - - describe "#===" do - subject { stub === call } - - context "with a matching method name" do - let(call) { Spectator::MethodCall.capture(:foo, "foobar") } - - it "returns true" do - is_expected.to be_true - end - end - - context "with a different method name" do - let(call) { Spectator::MethodCall.capture(:bar, "foobar") } - - it "returns false" do - is_expected.to be_false - end - end - - context "with a constraint" do - let(constraint) { Spectator::Arguments.capture(/foo/) } - let(stub) { Spectator::ValueStub.new(:foo, 42, constraint) } - - context "with a matching method name" do - let(call) { Spectator::MethodCall.capture(:foo, "foobar") } - - it "returns true" do - is_expected.to be_true - end - - context "with a non-matching arguments" do - let(call) { Spectator::MethodCall.capture(:foo, "baz") } - - it "returns false" do - is_expected.to be_false - end - end - end - - context "with a different method name" do - let(call) { Spectator::MethodCall.capture(:bar, "foobar") } - - it "returns false" do - is_expected.to be_false - end - end - end - end -end