As you start to get more familiar with automated testing, at some point you will likely hear other developers say things like:
“You know you can just stub that method, right?”
“This is a great candidate for a double!”
And at the beginning these things can be confusing to understand. 😕
So let’s start by defining each term, followed by their practical use, in specific relation to
rspec within a
One of the tricky things I have seen when these concepts are defined is that they are often defined within the context of testing.
For me, that just adds a layer of complexity.
It is true that these concepts are often used within the context of testing, but they don’t have to be.
So let’s take a stab or should I say stub 😆 at defining them without talking about tests.
Stubbing is just a term for defining a method on an object.
However, it indicates the short-term nature of that method.
If you stub a method on an object, your intention is that it exists for a short period of time and most likely returns a simple hard-coded value.
You may use a stub to define a method that does not exist yet on an object or to override an existing method to get it to return a value you desire.
class Feature def self.available?(name) # all features are disabled by default false end end puts Feature.available?("user_preference") # => false # stub: override the method to return a canned response def Feature.available?(name) true end puts Feature.available?("user_preference") # => true
A double, is simply an object that is a double of another object.
You may be thinking, “but why would you do that?!”, but lets put that question to one side for now just so we can understand the concept in its purest form.
A double of something is just a copy of that thing.
If I have one chocolate and I double that chocolate I now have two of the same chocolate. We can call the first chocolate the original and the second chocolate a double of the original. yum. 🍫
Great Success!! We made it this far and we haven’t talked about tests!
Hopefully now that we understand these concepts independent of testing we can talk a little bit about how they can be helpful tools to use when testing.
Sometimes when you are testing a method, the method under test will send a message to another object in order to retrieve some value.
That sounds confusing but look at this example:
class EasyMath def sum(a,b) if SumFeature.available? a + b end end end
available? message is being sent to another class
SumFeature in order to return the value
When we come to test the behaviour of the
sum method, it would be really useful if we could just worry about
a + b without having to be concerned with what the
available? method does.
At the end of the day, the
available? method belongs to the
SumFeature class and has nothing to do with us over here and we can assume it is tested in the
SumFeature class where it is defined.
Well thankfully we have a tool in our arsenal called
stubbing that will allow us to
available? method so it always returns
This is great! We can now focus on testing
a + b whilst relying on
available? to always return
Ok, but what does this look like in a test?
describe "#sum" do it "adds two numbers together and returns the total" do easy_math = EasyMath.new allow(SumFeature).to receive(:available?).and_return(true) total = easy_math.sum(2,2) expect(total).to eq(4) end end
See that line
available? method has now been stubbed out to always return
No matter how many times you call
SumFeature.available? in your application code, it will always return
We did a thing! We stubbed a method! And as you can see it had nothing to do with doubles! 💥
Ok if you have made it this far, then stay with me as we are nearly there..
Let’s take the same example we used above with the
sum method, but tweak it slightly, to see how we could use a
class EasyMath def sum(a,b,user) if user.likes_math? a + b end end end
Once again we are sending a message
likes_math? somewhere else in order to retrieve a value of
Similarly we only want to concern ourselves with the behaviour of
a + b without having to worry about
Given what we know so far, we might suggest stubbing the
likes_math? method in the same way we stubbed
However, whilst a class method like
available? can be stubbed without the need for a
double, an instance method does require
double before it can be stubbed. (There are exceptions to this by using
any_instance_of but it is not recommended.
In other words we first need to create a
double of the
user object before we can stub the
likes_math? method on it, to
true for us.
Now hold on one second!! Why am I talking about
doubles? Didn’t I kind of say I didn’t want to do that?!
Ok hear me out on this one…
doubles are completely separate concepts, a
double is only meaningful and useful once you
stub methods on it.
This is because when you create a
double you are creating a
double of that object without any methods on it.
double is just a plain Ruby
Therefore, in reality, although
stubbing is used on its own, when you are using a
double you will likely need to use
stubbing as well.
Ok so lets see how the code would look for testing our
describe "#sum" do it "adds two numbers together and returns the total" do easy_math = EasyMath.new user_double = double allow(user_double).to receive(:likes_math?).and_return(true) total = easy_math.sum(2,2,user_double) expect(total).to eq(4) end end
See how we first create a
double like this
user_double = double.
And on the next line we
likes_math? method on to our
double like this:
It might be helpful to think about it this way:
double is a useful tool to help you to be able to easily
And that’s really all there is to it folks!
But before I go, let’s wrap up with some helpful tips.
- Stubbing has nothing to do with doubles.
- It helps us to force the return value of a method we don’t care about in our test because it is tested elsewhere.
- A class method can be stubbed without the need for a double.
- An instance method will need a double (unless you use
- Doubles are a useful way to create an object that acts as a double of another object.
- The most basic way to create a double in
rspecis to use the
- Another option is to use
instance_double(User). This is the same as
doublebut will verify that any methods you stub exist on the class you are doubling.
- If you are using
FactoryBotyou can use
build_stubbed(:user)which will create an
instance_doublebut with the methods stubbed out as set in the factory. It also has the added advantage of stubbing out associated objects and created timestamps.