diff --git a/spec/docs/mocks_spec.cr b/spec/docs/mocks_spec.cr new file mode 100644 index 0000000..6e164ae --- /dev/null +++ b/spec/docs/mocks_spec.cr @@ -0,0 +1,154 @@ +require "../spec_helper" + +# https://gitlab.com/arctic-fox/spectator/-/wikis/Mocks +Spectator.describe "Mocks Docs" do + context "Abstract Types" do + abstract class MyClass + abstract def something : String + end + + mock MyClass + + it "does something" do + mock = mock(MyClass) + allow(mock).to receive(:something).and_return("test") # Uncomment this line to fix. + mock.something + end + end + + abstract class MyClass + abstract def answer : Int32 + abstract def answer(arg1, arg2) : Int32 + end + + mock MyClass, answer: 5 do + def answer(arg1, arg2) : Int32 + arg1 + arg2 + end + end + + let(answer) { 42 } + + it "does something" do + mock = mock(MyClass, answer: answer) + expect(mock.answer).to eq(42) + expect(mock.answer(1, 2)).to eq(3) + end + + context "Instance Variables and Initializers" do + class MyClass + def initialize(@value : Int32) + end + end + + mock MyClass do + def initialize(@value : Int32 = 0) # Note the lack of `stub` here. + end + end + + it "can create a mock" do + mock = mock(MyClass) + expect(mock).to_not be_nil + end + end + + context "Expecting Behavior" do + abstract class Target + abstract def call(value) : Nil + end + + class Emitter + def initialize(@value : Int32) + end + + def emit(target : Target) + target.call(@value) + end + end + + describe Emitter do + subject { Emitter.new(42) } + + mock Target, call: nil + + describe "#emit" do + it "invokes #call on the target" do + target = mock(Target) + subject.emit(target) + expect(target).to have_received(:call).with(42) + end + end + end + + it "does something" do + mock = mock(MyClass) + allow(mock).to receive(:answer).and_return(42) # Merge this line... + mock.answer + expect(mock).to have_received(:answer) # and this line. + end + + it "does something" do + mock = mock(MyClass) + expect(mock).to receive(:answer).and_return(42) + mock.answer + end + end + + context "Class Mocks" do + class MyClass + def self.something + 0 + end + end + + mock MyClass do + # Define class methods with `self.` prefix. + stub def self.something + 42 + end + end + + it "does something" do + # Default stubs can be defined with key-value pairs (keyword arguments). + mock = class_mock(MyClass, something: 3) + expect(mock.something).to eq(3) + + # Stubs can be changed with `allow`. + allow(mock).to receive(:something).and_return(5) + expect(mock.something).to eq(5) + + # Even the expect-receive syntax works. + expect(mock).to receive(:something).and_return(7) + mock.something + end + end + + context "Injecting Mocks" do + struct MyStruct + def something + 42 + end + + def something_else(arg1, arg2) + "#{arg1} #{arg2}" + end + end + + inject_mock MyStruct, something: 5 do + stub def something_else(arg1, arg2) + "foo bar" + end + end + + specify "creating a mocked type without `mock`" do + inst = MyStruct.new + expect(inst).to receive(:something).and_return(7) + inst.something + end + + it "leaks stubs to other examples" do + inst = mock(MyStruct) + expect(inst.something).to eq(7) # Previous stub was leaked. + end + end +end diff --git a/spec/docs/stubs_spec.cr b/spec/docs/stubs_spec.cr new file mode 100644 index 0000000..8643b46 --- /dev/null +++ b/spec/docs/stubs_spec.cr @@ -0,0 +1,129 @@ +require "../spec_helper" + +Spectator.describe "Stubs Docs" do + double :time_double, time_in: Time.utc(2016, 2, 15, 10, 20, 30) + double :my_double, something: 42, answer?: false + + let(dbl) { double(:my_double) } + + def receive_time_in_utc + receive(:time_in).with(:utc).and_return(Time.utc) + end + + it "returns the time in UTC" do + dbl = double(:time_double) + allow(dbl).to receive_time_in_utc + expect(dbl.time_in(:utc).zone.name).to eq("UTC") + end + + context "Modifiers" do + double :my_double, something: 42, answer?: false + + let(dbl) { double(:my_double) } + + context "and_return" do + specify do + allow(dbl).to receive(:something).and_return(42) + expect(dbl.something).to eq(42) + end + + specify do + allow(dbl).to receive(:something).and_return(1, 2, 3) + expect(dbl.something).to eq(1) + expect(dbl.something).to eq(2) + expect(dbl.something).to eq(3) + expect(dbl.something).to eq(3) + end + end + + context "and_raise" do + specify do + allow(dbl).to receive(:something).and_raise # Raise `Exception` with no message. + expect { dbl.something }.to raise_error(Exception) + + allow(dbl).to receive(:something).and_raise(IO::Error) # Raise `IO::Error` with no message. + expect { dbl.something }.to raise_error(IO::Error) + + allow(dbl).to receive(:something).and_raise(KeyError, "Missing key: :foo") # Raise `KeyError` with the specified message. + expect { dbl.something }.to raise_error(KeyError, "Missing key: :foo") + + exception = ArgumentError.new("Malformed") + allow(dbl).to receive(:something).and_raise(exception) # Raise `exception`. + expect { dbl.something }.to raise_error(ArgumentError, "Malformed") + end + end + + context "with" do + specify do + allow(dbl).to receive(:answer?).and_return(false) + allow(dbl).to receive(:answer?).with(42).and_return(true) + expect(dbl.answer?(42)).to be_true + expect(dbl.answer?(5)).to be_false + end + + specify do + allow(dbl).to receive(:answer?).with(Int, key: /foo/).and_return(true) + expect(dbl.answer?(42, key: "foobar")).to be_true + end + end + end + + context "Expect-Receive Syntax" do + class Driver + def doit(thing) + thing.call + end + end + + describe Driver do + describe "#doit" do + double :thing, call: 5 + + it "calls thing.call (1)" do + thing = double(:thing) + allow(thing).to receive(:call).and_return(42) + subject.doit(thing) + expect(thing).to have_received(:call) + end + + it "calls thing.call (2)" do + thing = double(:thing) + expect(thing).to receive(:call).and_return(42) + subject.doit(thing) + end + + it "calls thing.call (3)" do + thing = double(:thing) + allow(thing).to receive(:call).and_return(42) + expect(thing).to_eventually have_received(:call) + subject.doit(thing) + end + end + end + + specify do + expect(dbl).to receive(:answer?).with(42).and_return(true) + dbl.answer?(42) + end + end + + context "Default Stubs" do + double :my_double, foo: "foo" do # Default stub for #foo + # Default stub for #bar + stub def bar + "bar" + end + end + + it "does something" do + dbl = double(:my_double) + expect(dbl.foo).to eq("foo") + expect(dbl.bar).to eq("bar") + + # Overriding initial defaults. + dbl = double(:my_double, foo: "FOO", bar: "BAR") + expect(dbl.foo).to eq("FOO") + expect(dbl.bar).to eq("BAR") + end + end +end