Ethan Gustafson
Posted on August 15, 2020
Test Doubles in RSpec have been a little difficult to wrap my head around. No doubt you may have encountered many examples online of using test doubles, but you were not able to exaclty land down how or why you would use them in your tests.
Why would you use them?
Example 1: You have one class written, but it depends on another class which is not yet written. You can create fake objects and methods of the missing class so that it could work with the class that is written. If the written class fails a test, then you know immediately the issue is with that class, since it is independent, not depending on other classes. This is where you can use Mocks and Stubs.
Mocks & Stubs
- A Mock is defined using the
double
keyword. It's essentially a fake object. It stands in for the object you want to test. - A Stub is defined using the
allow
keyword. A Stub is a fake method you can use on thedouble
. You useallow
in order to define what messages are allowed to be called on thedouble
.
If methods aren't given values, the stub returns a value of nil
by default. You need to set what the return value will be of the stub.
it "creates a mock and a stub" do
dbl = double("greeting")
allow(dbl).to receive(:hello)
expect(dbl.hello).to be_nil
end
Below, I stubbed methods when the double was created. To do that, you provide the double an argument of a hash. The hash keys will be set as the stubbing methods for the double.
class Restaurant
def initialize(workers)
@workers = workers
end
def workers
@workers.map{|w| w.name}
end
end
require_relative '../restaurant.rb'
describe Restaurant do
it "returns a list of workers" do
dbl1 = double("manager", name: "Nancy") # The new fake objects from the missing class
dbl2 = double("host", name: "Chris")
dbl3 = double("waiter", name: "Michael")
restaurant = Restaurant.new([dbl1, dbl2, dbl3])
expect(restaurant.workers).to eq(["Nancy", "Chris", "Michael"])
end
end
Running the test:
ā rspec spec
Restaurant
returns a list of workers
Finished in 0.00341 seconds (files took 0.13641 seconds to load)
1 example, 0 failures
Partial Test Doubles
A partial test double uses a real object from your application, but you stub its methods.
Example 2: You have a class, but certain methods are not yet written for that class.
class Worker
end
describe Worker do
it "greets customer" do
dbl = double("worker")
allow(dbl).to receive(:greet).and_return("Welcome!")
expect(dbl.greet).to eq("Welcome")
end
end
Example 3: You need to make a database call, but it would be faster to just use a double.
describe Worker do
it "stubs a database call" do
dbl = double("worker", :name => "Ethan")
allow(Worker).to receive(:find).and_return(dbl)
worker = Worker.find
expect(worker.name).to eq("Ethan")
end
end
Example 4: When you have a object that is expensive to use, it would probably be better to test using mocks and stubs.
Message Expectations
Ruby uses dot notation to send messages to objects. Those messages are methods. "hello".capitalize
- .capitalize
is the method, sending a message to the string "hello"
which the string will receive and perform the message request. Then the value is returned as "Hello"
RSpec can also use expectations in this same way. The test will expect an object to receive a method, and then you call that method.
it "is a message expectation" do
dbl = double("Cashier")
expect(dbl).to receive(:take_order).and_return("Order when you're ready.")
dbl.take_order
end
Spies
Spies are exactly like doubles. The only difference is that you don't have to stub methods in order to make them available for use. Remember that in order to stub a method, you either provide a hash argument to a double
, or you use the allow
keyword to stub a method.
it "host gives a greeting" do
host_dbl = spy("Host")
expect(host_dbl).to receive(:say_hello).and_return("Welcome!")
host_dbl.say_hello
expect(host_dbl).to have_received(:say_hello)
end
So you don't have to expect the double
to receive the method and return a value, instead you can just see if the message has been received.
it "host gives a greeting" do
host_dbl = spy("Host")
host_dbl.say_hello
expect(host_dbl).to have_received(:say_hello)
end
Resources
More on Test Doubles: Test Doubles
Posted on August 15, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.