We recently upgraded our test suite to RSpec 3. We did this to stay up to date
with the most recent code rather than because we wanted any particular new
feature. And for the most part RSpec 3 doesn't seem to add many features so much
as it cleans up the API (the biggest change is that you can now use
expect(something)
syntax everywhere, instead of something.should
). In fact,
a lot of the changes that we had to make when upgrading involved adding gems
that had been extracted from RSpec.
This post
by RSpec core team developer Myron Marston is a comprehensive summary of the
changes.
There is one new feature in RSpec 3 that introduces a new and powerful behavior that has important implications for testing practice. I would go so far as to say that the project seriously buried the lede on this. The new feature is called verifying doubles, and in a nutshell it allows you to create test doubles that verify themselves as conforming to the same interface as a "real" object. Thanks to Xavier Shay for adding this great feature.
There are two steps for gaining access to this magic. The first is by using
instance_double
(and the related class_double
) instead of double
throughout your code. The second is to enable verifying partial doubles in
your test suite.
Using instance_double in your code
Here is an example of using instance_double
in a spec. Note that you need to
pass the class that the double is meant to represent as the first argument to
instance_double
:
When you run this test you will get the following error:
The verifying double alerted us to the fact that we hadn't implemented
#description
on Post
. Hopefully this bug would have been caught further down
the line in an acceptance test but there are often gaps in coverage.
Even better, verifying doubles will verify the number of arguments on a method:
This results in a different error:
Just in case you missed it, RSpec is telling us that we are using the wrong
number of arguments to call a method on the double. This is really powerful
when you think about how we tend to extend method signatures. First we start off
with a method call without arguments. Then a little later we decide we need more
flexibility and add a switch flag like include_cents
. If our test suite uses a
lot of doubles we might not realize that we've just broken a lot of code (in the
real world we would probably be more careful and make include_cents
an
optional argument).
You can also use class_double
, which works the same as instance_double
but
for classes.
(Update: see Myron Marston's comment below about other ways that the method signature is verified)
Enabling verifying partial doubles
A partial double is what is more commonly known as a stubbed method. It is arguably even more important that stubbed methods on real objects correspond to reality, especially when you are stubbing code for external libraries that you do not test directly:
I haven't included the code for ExternalLibrary
because the point is that we
don't own this code and so we tend to be less sure about the interface. What if
we update the gem version and the method signature changes? As written this test
will keep on happily chugging along, even though the code will throw errors in
production.
I call this situation when your code is broken but your test suite is all green due to use of stubs a La La Land. To avoid a La La Land you can enable RSpec's verify partial doubles feature:
Now your stubs will behave like verifying doubles, throwing an error if you try to stub a method that doesn't exist on the object, or if you stub a method with the wrong number of arguments.
I presented this step second because I wanted to explain verifying doubles
through examples using instance_double
first, but you probably want to enable
this option first. After that you can go through your test suite replacing
double
with instance_double
at your leisure. You might find it eye-opening
when you turn on verifying partial doubles. Suddenly a lot of tests that you
thought were sound might turn out to contain stubs on methods that don't exist,
or that take different numbers of arguments. It can be an opportunity to find
out what your test suite is really testing.
Gotchas
There are two gotchas involving verifying doubles that we've run into so far.
The first involves redefinition of #respond_to?
. Under the hood RSpec uses
#respond_to?
to determine if it should throw an error when a verifying double
tries to implement a method. Sometimes you'll find that certain classes redefine
#respond_to?
(for example if they lean on #method_missing?
to respond to
undefined method calls), but they don't always implement it correctly, leaving
off the optional second argument. This can cause an ArgumentError
when
stubbing on these classes. For example, this used to be an issue with
Rails::Railtie::Configuration
but it appears to be fixed in recent releases.
To be clear, this was never a bug in RSpec but an issue of classes incorrectly
implementing #respond_to?
.
The second issue is more endemic. Classes that uses tricks to defer defining
methods will cause your verifying doubles to fail if an actual instance of the
class hasn't been created and put through its paces first. This can be
frustrating when working with ActiveRecord
models, for instance, since they
use a ton of fancy introspection on database columns to define methods on the
fly. Depending on the order of your tests you can end up with false positive
errors where RSpec will complain that a method doesn't exist on the class.
Merely using the method on a real instance in your test anywhere prior to the
stub request will silence the error, however. We don't have a good solution for
this problem, so we've been avoiding using instance_double
on ActiveRecord
models for the time being, instead using mock_model
from the
RSpec-activemodel-mocks gem.
Something you can try is to create in-memory instances of your models before
your test suite runs. (Update: in the comments Myron Marston points out
that you can use object_double
with an instance of the object)
Some testing theory
You can skip this section if you just wanted to know how to use verifying doubles. That's all done.
There have recently been some (in my opinion) rather productive debates in the Ruby world about the role of mocks and stubs in testing. On the one side were the proponents of mockist testing as a component of Test Driven Development (TDD). On the other side was the argument that mockist tests are poor tests that produce (in my terms) dangerous La La Lands where all the code is small and "testable" but doesn't necessarily work together. The anti-mockist argument further went that if you can get your tests to run "fast enough" (since another argument for using doubles is that they have less overhead) then you're better off using doubles much less frequently and simply exercising your real code.
Something that seemed to get lost in that debate is the role that doubles play in the most important aspect (he asserted) of Ruby programming, which is duck typing. When you write a method definition that takes in an object, like
you may think that you wrote a method that takes in a LineItem
, but you
actually wrote a method that takes in anything that quacks like a
LineItem
, that is anything with an #amount
method. Doubles in specs serve as
a form of documentation of the always implicit contract that methods require
incoming objects to uphold. They help keep the programmer honest by forcing them
to explicitly specify all the methods that an object is required to implement.
If you need to stub 10 values on a double to make a test pass, that should tell
you something about the degree of coupling in your code and should make you
wonder if your classes are following Single Responsibility Principle.
I'm excited about verifying doubles because they introduce something close to
the Interface concept that other languages have, but without the formality of
having to create an explicit definition. Interfaces as language constructs in
typed languages like Java are used to allow for more flexibility in passing
arguments and returning values. Instead of saying that a method can only receive
an argument of type Widget
you can say that a method can receive any object
that implements the Widget
interface, which has to be formally specified in
a separate class-like definition. Often the interface will be named IWidget
to
distinguish it from the Widget
class that implements IWidget
. These
languages perform compile and runtime checks to make sure that classes correctly
implement the interfaces that they claim to implement. So if Widget
claims
to implement the IWidget
interface then it must implement all the methods
(with the correct number of arguments and argument types) that are specified by
IWidget
, or else an error will be raised.
In essence, verifying doubles do an interface check in your test rather than at
runtime in your code. Instead of maintaining a separate formal interface
definition you can simply refer to the method signatures of a canonical class,
like String
or File
. You can also use verifying doubles to make sure that
calling code only uses the common interface for an inheritance structure of
classes, by passing in the abstract class to instance_double
:
This test fails because somebody extended the #publish
method's contract
beyond what Publication
's interface promises. Note however that this test
would not help us detect that Magazine
does not implement a particular
interface if we went back and implemented #summary
on Publication
. That is a
different problem which in other languages would be solved by, well, an
interface check. In her
Practical Object Oriented Design in Ruby
book Sandi Metz addresses this problem by using a set of shared tests for the
common interface that she mixes into the unit tests for each concrete class. It
would be nice to have an automated way of doing this that reuses the verifying
double mechanism.
Conclusion
Verifying doubles may sound intimidating, but they are easy to switch over to
and use today. Start by turning on verifying partial doubles and then gradually
switch over your existing doubles to use instance_double
. Expect to have to
fix some tests, but take comfort in the fact that you're making your test suite
so much more rigorous and meaningful with a relatively small amount of work.
Verifying doubles make RSpec a much more powerful and relevant testing tool.