Three Reasons To Roll Your Own Mocks

I've spent the last several months writing my own mock objects, but
for the code I'm currently working on, we were mostly using RSpec's built in dynamic mocking
library. I paid attention to see how effective it was and if I would change
my mind about it, but after a couple of weeks, I'm still pretty strongly in favour of
rolling my own. Here are the three biggest reasons that I've noticed for
preferring this approach.

1. It enables simple tests.

Tests can be an asset or a liability. I strongly prefer
them in the asset category. One of the ways to turn them into liabilities is
with long complex tests. I found that when doubling an object that needed
several methods to be mocked out, this extraneous setup is necessary for
the object to behave correctly, but irrelevant to the specific test they
must be written in, and distracting when you later want to reread the test.

For example, lets say you want to specify the behaviour around submitting
requests to a warehouse to send their inventory to your client. But it turns
out your client is a military organization, and they protect all such requests
with an encrypted key that changes every half hour and must be submitted with
each request. What you want to test is something like "when the first warehouse
doesn't have sufficient inventory, it requests the rest of the inventory from
the second warehouse" but when you go to look at the tests, you'll see all this
code dealing with setting up the key that it is going to need before it can send
the request. That is essentially spam, it makes it harder to get the relevant
information out of the test, making it a chore to read them.

If you roll your own, you can set it up with a sensible default such that this
information is already taken care of.

2. It keeps the setup in one place, abstracted away from the tests.

When you make changes, many tests may break. Perhaps you change the name of a method
that it invokes on its dependent object. Now you must go update all your dynamic mocks to
change the name of the method. But if you have written your own mock object, then
you only need to go change it in the one location where you've defined what methods
it presents. Thus the tests do not need to be changed, they are still correct.

Or, using the previous example, what if the warehouses implement the key requirement
at some point in the future. Now you need to go retrofit all your tests to add the key.
But if you have rolled your own, then you can just give them a default value and not
need to update each test. All your tests will pass without any changes (you'll,
still need to add new tests for the stories around this new feature, but all
the old ones will still valid without any change). Here is a simplified example of
what that might look like.

1moduleMock 2classWarehouse 3definitialize(initial_quantity) 4@quantity_requested=0 5end 6 7defrequest_inventory(quantity) 8@quantity_requested+=quantity 9end1011defhas_been_asked_for?(quantity)12@quantity_requested==quantity13end14end1516classStore17definitialize18@quantity_sent=019end2021defsend_inventory(quantity)22@quantity_sent+=quantity23end2425defhas_been_sent?(quantity)26@quantity_sent==quantity27end28end29end3031describeInventoryOrdererdo32let(:warehouse){Mock::Warehouse.new15}33let(:store){Mock::Store.new}34let(:orderer){InventoryOrderer.new}35before{orderer.add_warehousewarehouse}3637it'requests the inventory from the warehouse'do38orderer.place_order10,store39warehouse.shouldhave_been_asked_for1040end4142it'sends the inventory to the store'do43orderer.place_order10,store44store.shouldhave_been_sent1045end46# ... more tests for things like inventory type or not enough inventory ...47end

Now what happens when we add the key? We have to add the new tests around the key,
and we have to update the mocks to reflect the new interface, but we do not have to update the tests.
We have focused the test to the specific behaviour we are interested in, and
changes to these interfaces won't require combing through spec files, updating many tests as we go.

3. It consolidates the entire interface expected of the object being mocked

I greatly prefer having the interface expected of the object defined in one location.
When you roll your own mock objects, you only need to go look at the mock to see what
methods this object is presenting and you will know what is expected of any object wishing to
fill that responsibility.

This consolidation helps us follow the
Interface Segregation Principle
Like in many statically typed languages, it presents the interface that any given object
wishing to fill that role must implement. In dynamically typed languages like Ruby, there
are no explicit interfaces, causing every object to implicitly be its own interface.
This conceals the violation of ISP in cases like ActiveRecord::Base, leading to
highly coupled, volatile classes whose interfaces are difficult to take alone or consider independently.

A consolidated interface is particularly useful if you develop the way I prefer to, where you
first define the class using the object, and then later define the object
itself. With this approach, when you need to figure out what methods to implement,
you just go look at the mock object definition. When you use a mocking framework,
you must go through each test to see what methods you're defining. This makes
them easy to miss as they are distributed all across the source code.

Conclusion

Now I could foresee a mocking framework that fits well with the way that I like
to roll my own mocks. Unlike the dynamic mocking frameworks, it would probably
provide an API like FactoryGirl's which would allow it to present the advantages
that I listed above. But until then, I'll probably continue to prefer rolling my own.