This post is for those who are familiar with RSpec, but do not understand the meaning of "to mock something" or "to stub something". Short, to the point, and with examples.
Double
A stunt object that substitutes a real system object during tests:
describe NotificationsController do
# The NotificationsController downloads
# the latest notifications from a third-party service
# via HTTP call using NotificationsDatasource
let(:datasource) do
double(:datasource, as_json: { notifications: [] })
end
before do
# Replace the real NotificationsDatasource with a double,
# avoiding external depency in the test:
allow(NotificationsDatasource)
.to receive(:new)
.and_return(datasource)
end
describe "#index" do
it "wraps notifications in 'data' key" do
get :index, format: :json
expect(json_response["data"].keys)
.to have_key "notifications"
end
end
end
Stub
A stub for a method or object that returns canned value:
context "when attachment file is too large to email" do
let(:max_file_size) { Attachment::MAX_FILE_SIZE }
before do
allow(attachment)
.to receive(:file_size)
.and_return(max_file_size + 1)
end
it "raises 'file is too large' error" do
# ...
end
end
The attentive reader has already noticed that in the previous example with NotificationsController
there was a stub, too. That's right: a stub is a double with pre-defined responses.
Mock
Stub with expectations, which RSpec will check at the end of the test:
context "when cloning process cannot be performed" do
before do
allow(doctor).to receive(:clone) { raise "can't clone" } # stub
end
it "notifies airbrake" do
expect(Airbrake).to receive(:notify) # mock
# RSpec will stub `Airbrake.notify`
# and at the end of this `it do...end` block
# will check, if it has been called.
# If it hasn't been called,
# it would raise an error and fail the test.
#
# So when asked at a job interview what's
# the difference between a mock and a stub, answer,
# "Only a mock can fail a test."
clone_poor_dolly
end
end
Mocks change the order of phases in the test. Instead of "Setup — Exercise — Verify" you get "Setup & Verify — Exercise". If you, like me, find this hard to read, use a stub with a check:
# mock
it "notifies airbrake" do
expect(Airbrake).to receive(:notify) # setup & verify
clone_poor_dolly # exercise
end
# stub
it "notifies airbrake" do
allow(Airbrake).to receive(:notify) # setup
clone_poor_dolly # exercise
expect(Airbrake).to have_received(:notify) # verify
end
Top comments (0)