- Stubs
- Mocks
- Doubles
- Spies
- Null object doubles
Especially helpful to fake state of secondary objects that are auxiliary to our test. They allow us to imitate state.
- Stubs define context
- Too many stubs are a code smell
- Avoid stubbing in the object under test
- Don't make assertions about stubs!
# for example
allow(die).to receive(:roll) { 3 }
# BUT DO NOT DO THIS
describe "#sum" do
it "adds two numbers" do
allow(Calculator).to receive(:sum).with(2,3).and_return(5)
result = Calculator.sum(2,3)
expect(result).to eq 5
end
endMocks allow us to define what calls a method we are testing should make. Mocks verify what you expect to happens happens (i.e. a message have been sent). They allow us to imitate and verify behaviour.
- Test behaviour
- Set expectations on mocks to test outgoing command messages
- One expectation per test
- Do not stub using a mock! => don't assert the result
- Never mock what you don't own!
RSpec.describe ApplicationHelper do
describe "#markdown" do
it "delegates to MarkdownRenderer" do
text = "hello"
allow(MarkdownRenderer).to receive(:render).with(text)
helper.markdown(text)
expect(MarkdownRenderer).to have_received(:render).with(text)
end
end
endFrom RSpec documentation:
Test double is a generic term for any object that stands in for a real object during a test (think "stunt double"). You create one using the double method. Doubles are "strict" by default -- any message you have not allowed or expected will trigger an error -- but you can switch a double to being "loose". When creating a double, you can allow messages (and set their return values) by passing a hash.
Once you have a test double, you can allow or expect messages on it.
We recommend you use verifying doubles whenever possible.
Another definition:
The precise term for a fake object that takes the place of a real object when a text is executed.
And...
instance_doublecan fail a test if methods are not available in the specified classdoubledoesn't care about anything.
describe "A test double" do
it "returns canned responses from the methods named in the provided hash" do
d = double("Some Collaborator", foo: 3, bar: 4)
expect(d.foo).to eq(3)
expect(d.bar).to eq(4)
end
endEnsure test doubles stay in sync with the API!
From RSpec documentation:
Message expectations put an example's expectation at the start, before you've invoked the code-under-test. Many developers prefer using an act-arrange-assert (or given-when-then) pattern for structuring tests. Spies are an alternate type of test double that support this pattern by allowing you to expect that a message has been received after the fact, using
have_received.
You can use any test double (or partial double) as a spy, but the double must be setup to spy on the messages you care about. Spies automatically spy on all messages, or you can allow a message to spy on it.
describe "have_received" do
it "passes when the message has been received" do
invitation = spy("invitation")
invitation.deliver
expect(invitation).to have_received(:deliver)
end
endTest doubles are strict by default, raising errors when they receive messages that have not been allowed or expected.
You can chain as_null_object off of double in order to make the double "loose".
For any message that has not explicitly allowed or expected, the double will return itself. It acts as a black hole null object, allowing arbitrarily deep method chains.
describe "as_null_object" do
it "returns itself" do
d = double("Some Collaborator").as_null_object
expect(d.foo.bar.bazz).to be(d)
end
endTest doubles that we have are:
- Fake: an alternative implementation to stand in for something you depend on (think fake server when testing APIs)
- Stub: replies to certain messages with responses that help us write tests
- Mock: ensures certain messages are received (and explodes upon receiving any unexpected messages)
- Spy: stealthily records all interactions, allowing us to make assertions about them later


