carlosgaldino

Review of Endo-Testing: Unit Testing with Mock Objects

Endo-Testing: Unit Testing with Mock Objects is the paper written by Tim Mackinnon, Steve Freeman and Philip Craig where they introduced Mock Objects as a new technique for testing software.

I had this paper printed for quite some time but I haven't read it yet. After all the fuss about testing in the Rails community because of DHH's talk at RailsConf 2014 I decided to read the paper.

Below you will find what I highlighted along with my notes, comments and questions that I had while I was reading the paper. FYI, I wrote these notes around 2:00 AM and didn't review them after writing so things are about to get raw. I hope you enjoy them.

You will notice a common question that came up while I was reading the paper. The question is about dependency injection being used to make the testing process easier or if it is common to have DI in well designed systems, where its parts aren't highly coupled. I watched and read a couple of resources talking about DI and I couldn't find anyone saying that the main goal of DI is to make testing easier.

You might be thinking that I was silly just by having this doubt but the countless examples of straw men that appear these days can lead people to have such thoughts. :(

Reading the paper was a good exercise to know how they introduced the concept, the ideas, motivations and also to realize that the paper is still relevant nowadays.

Introduction

We propose a technique called Mock Objects in which we replace domain code with dummy implementations that emulate real code. These Mock Objects are passed to the target domain code which they test from inside, hence the term Endo-Testing.

…we use our tests and stubs to drive the development of our production code.

Unit tests written with Mock Objects have a regular format that gives the development team a common vocabulary.

Unit testing with Mock Objects

An essential aspect of unit testing is to test one feature at time;

Test code should communicate its intent as simply and clearly as possible. This can be difficult if a test has to set up domain state or the domain causes side effects.

A Mock Object is a substitute implementation to emulate or instrument other domain code. It should be simpler than the real code, not duplicate its implementation, and allow you to set up private state to aid in testing. The emphasis in mock implementations is on absolute simplicity, rather than completeness.

Not just stubs

Mock Objects that become too complex to manage suggest that their domain clients are candidates for refactoring, and we avoid chaining Mock Objects.

…our coding style of passing stub objects as parameters, rather than relinking the domain code, clarifies the scope of unit testing, and reduces the risk of mistakes during a build.

Why use Mock Objects?

Localising unit tests

Deferring Infrastructure Choices

…we might with to write functionality without committing to a particular database. Until a choice is made, we can write a mock class that provides the minimum behaviour that we would expect from our database.

The mock code also gives us an initial definition of the functionality we will require from the database.

Coping with scale

Unit tests, as distinct from functional tests, should exercise a single piece of functionality. A unit test that depends on complex system state can be difficult to set up, especially as the rest of the system develops.

No stone unturned

The test has no dependencies on components outside the development system and is insulated from other possible real world failures.

This style of test is repeated for other types of failure, and the entire test suite documents the possible server failures that our client code can handle.

Better tests

Failures fail fast

…a mock implementation can test assertions each time it interacts with domain code and so is more likely to fail at the right time and generate a useful message.

Refactored assertions

…assertions are built into Mock Objects and so are applied by default whenever the object is used.

During development, the authors have come across situations where assertions in their Mock Objects have failed unexpectedly. Usually this is a timely warning about a constraint that the programmers have forgotten, but sometimes this is because the failing constraints are not always relevant. These cases suggest candidates for refactoring of either the domain code or Mock Objects, and help to push the developers towards a better understanding of the system.

Effects on coding style

Developing with Mock Objects reduces the need to expose the structure of the domain code. A test knows more about the behavior and less about the structure of tested code.

Developing with Mock Objects encourages a coding style where objects are passed into the code that needs them. This makes substitution possible and reduces the risk of unexpected side-effects.

…developing with Mock Objects tends to push behaviour towards Visitor-like objects that are passed around…

Interface discovery

When writing code that depends on other related objects, we have found that developing with Mock Objects is a good technique for discovering the interface to those other objects.

Limitations of Mock Objects

…unit testing will not catch failures that arise from interactions between components.

This is why functional tests are still necessary, even with good unit tests.

In some cases it can be hard to create Mock Objects to represent types in a complex external library.

This process can be costly and sometimes must be weighed against the benefits of having the unit tests. However, when only a small part of a library needs to be stubbed out, Mock Objects is a useful technique for doing so.

A Pattern for unit testing

With this style, the test makes clear what the domain code is expecting from its environment, in effect documenting its preconditions, postconditions, and intended use.

Notes