shard-spectator/spec/spectator/dsl/mocks/double_spec.cr

386 lines
9.2 KiB
Crystal

require "../../../spec_helper"
Spectator.describe "Double DSL", :smoke do
context "specifying methods as keyword args" do
double(:test, foo: "foobar", bar: 42)
subject(dbl) { double(:test) }
it "defines a double with methods" do
aggregate_failures do
expect(dbl.foo).to eq("foobar")
expect(dbl.bar).to eq(42)
end
end
it "compiles types without unions" do
aggregate_failures do
expect(dbl.foo).to compile_as(String)
expect(dbl.bar).to compile_as(Int32)
end
end
context "with an unexpected message" do
it "raises an error" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /baz/)
end
it "reports the double name" do
expect { dbl.baz }.to raise_error(Spectator::UnexpectedMessage, /:test/)
end
it "reports the arguments" do
expect { dbl.baz(:xyz, 123, a: "XYZ") }.to raise_error(Spectator::UnexpectedMessage, /\(:xyz, 123, a: "XYZ"\)/)
end
end
context "blocks" do
it "supports blocks" do
aggregate_failures do
expect(dbl.foo { nil }).to eq("foobar")
expect(dbl.bar { nil }).to eq(42)
end
end
it "supports blocks and has non-union return types" do
aggregate_failures do
expect(dbl.foo { nil }).to compile_as(String)
expect(dbl.bar { nil }).to compile_as(Int32)
end
end
it "fails on undefined messages" do
expect do
dbl.baz { nil }
end.to raise_error(Spectator::UnexpectedMessage, /baz/)
end
end
end
context "block with stubs" do
context "one method" do
double(:test2) do
stub def foo
"one method"
end
end
subject(dbl) { double(:test2) }
it "defines a double with methods" do
expect(dbl.foo).to eq("one method")
end
it "compiles types without unions" do
expect(dbl.foo).to compile_as(String)
end
end
context "two methods" do
double(:test3) do
stub def foo
"two methods"
end
stub def bar
42
end
end
subject(dbl) { double(:test3) }
it "defines a double with methods" do
aggregate_failures do
expect(dbl.foo).to eq("two methods")
expect(dbl.bar).to eq(42)
end
end
it "compiles types without unions" do
aggregate_failures do
expect(dbl.foo).to compile_as(String)
expect(dbl.bar).to compile_as(Int32)
end
end
end
context "empty block" do
double(:test4) do
end
subject(dbl) { double(:test4) }
it "defines a double" do
expect(dbl).to be_a(Spectator::Double)
end
end
context "stub-less method" do
double(:test5) do
def foo
"no stub"
end
end
subject(dbl) { double(:test5) }
it "defines a double with methods" do
expect(dbl.foo).to eq("no stub")
end
end
context "mixing keyword arguments" do
double(:test6, foo: "kwargs", bar: 42) do
stub def foo
"block"
end
stub def baz
"block"
end
stub def baz(value)
"block2"
end
end
subject(dbl) { double(:test6) }
it "overrides the keyword arguments with the block methods" do
expect(dbl.foo).to eq("block")
end
it "falls back to the keyword argument value for mismatched arguments" do
expect(dbl.foo(42)).to eq("kwargs")
end
it "can call methods defined only by keyword arguments" do
expect(dbl.bar).to eq(42)
end
it "can call methods defined only by the block" do
expect(dbl.baz).to eq("block")
end
it "can call methods defined by the block with different signatures" do
expect(dbl.baz(42)).to eq("block2")
end
end
context "methods accepting blocks" do
double(:test7) do
stub def foo(&)
yield
end
stub def bar(& : Int32 -> String)
yield 42
end
end
subject(dbl) { double(:test7) }
it "defines the method and yields" do
expect(dbl.foo { :xyz }).to eq(:xyz)
end
it "matches methods with block argument type restrictions" do
expect(dbl.bar &.to_s).to eq("42")
end
end
end
describe "double naming" do
double(:Name, type: :symbol)
it "accepts a symbolic double name" do
dbl = double(:Name)
expect(dbl.type).to eq(:symbol)
end
it "accepts a string double name" do
dbl = double("Name")
expect(dbl.type).to eq(:symbol)
end
it "accepts a constant double name" do
dbl = double(Name)
expect(dbl.type).to eq(:symbol)
end
end
describe "predefined method stubs" do
double(:test8, foo: 42)
let(dbl) { double(:test8, foo: 7) }
it "overrides the original value" do
expect(dbl.foo).to eq(7)
end
end
describe "scope" do
double(:outer, scope: :outer)
double(:scope, scope: :outer)
it "finds a double in the same scope" do
dbl = double(:outer)
expect(dbl.scope).to eq(:outer)
end
it "uses an identically named double from the same scope" do
dbl = double(:scope)
expect(dbl.scope).to eq(:outer)
end
context "inner1" do
double(:inner, scope: :inner1)
double(:scope, scope: :inner1)
it "finds a double in the same scope" do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner1)
end
it "uses an identically named double from the same scope" do
dbl = double(:scope)
expect(dbl.scope).to eq(:inner1)
end
context "nested" do
it "finds a double from a parent scope" do
aggregate_failures do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner1)
dbl = double(:outer)
expect(dbl.scope).to eq(:outer)
end
end
it "uses the inner-most identically named double" do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner1)
end
end
end
context "inner2" do
double(:inner, scope: :inner2)
double(:scope, scope: :inner2)
it "finds a double in the same scope" do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner2)
end
it "uses an identically named double from the same scope" do
dbl = double(:scope)
expect(dbl.scope).to eq(:inner2)
end
context "nested" do
it "finds a double from a parent scope" do
aggregate_failures do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner2)
dbl = double(:outer)
expect(dbl.scope).to eq(:outer)
end
end
it "uses the inner-most identically named double" do
dbl = double(:inner)
expect(dbl.scope).to eq(:inner2)
end
end
end
end
describe "context" do
double(:context_double, predefined: :predefined, override: :predefined) do
stub abstract def memoize : Symbol
stub def inline : Symbol
:inline # Memoized values can't be used here.
end
stub def reference : String
memoize.to_s
end
end
let(memoize) { :memoize }
let(override) { :override }
let(dbl) { double(:context_double, override: override) }
before { allow(dbl).to receive(:memoize).and_return(memoize) }
it "doesn't change predefined values" do
expect(dbl.predefined).to eq(:predefined)
end
it "can use memoized values for overrides" do
expect(dbl.override).to eq(:override)
end
it "can use memoized values for stubs" do
expect(dbl.memoize).to eq(:memoize)
end
it "can override inline stubs" do
expect { allow(dbl).to receive(:inline).and_return(override) }.to change { dbl.inline }.from(:inline).to(:override)
end
it "can reference memoized values with indirection" do
expect { allow(dbl).to receive(:memoize).and_return(override) }.to change { dbl.reference }.from("memoize").to("override")
end
end
describe "class doubles" do
double(:class_double) do
abstract_stub def self.abstract_method
:abstract
end
stub def self.default_method
:default
end
stub def self.args(arg)
arg
end
stub def self.method1
:method1
end
stub def self.reference
method1.to_s
end
end
let(dbl) { class_double(:class_double) }
it "raises on abstract stubs" do
expect { dbl.abstract_method }.to raise_error(Spectator::UnexpectedMessage, /abstract_method/)
end
it "can define default stubs" do
expect(dbl.default_method).to eq(:default)
end
it "can define new stubs" do
expect { allow(dbl).to receive(:args).and_return(42) }.to change { dbl.args(5) }.from(5).to(42)
end
it "can override class method stubs" do
allow(dbl).to receive(:method1).and_return(:override)
expect(dbl.method1).to eq(:override)
end
it "can reference stubs" do
allow(dbl).to receive(:method1).and_return(:reference)
expect(dbl.reference).to eq("reference")
end
end
end