A Mocking Example
We mock when we want to verify interactions with other objects while remaining isolated from them.
Let’s take the example of an object that inserts records to a database one step further. What if, depending on the input, we expect our object to produce a different insert query?
Let’s examine a test with Arrange-Act-Assert.
Arrange:
- The input to object under test
- The expected insert query
- The return value from the database mock for the insert query (success)
- An instance of the object to test
- The database mock
- Initialize test object with database mock
Act: Pass test input to object under test
Assert:
- Test object passed expected insert query to database mock
- Test object returned success
We’ve tested the object’s ability to create the query we expect based on a predefined input and to return a successful result.
The arrangements contain the mock’s expectations about the test. Depending on the framework, we check assertions at the end of the test or the mock fails immediately when an expectation is violated.
We have to initialize the test object with the mock. This may be explicit, such as in a framework that uses dependency injection, or it may be done implicitly by the test framework that injects mocks “under the covers.”
Since our object creates different queries based on its input, we need at least one more test. We can duplicate this test and supply a different input and matching expectation. Depending on the range of different inputs and queries, there may be many tests like this.
But what happens when the database fails? We need another test.
More Than One Mock
When we initialize a mock with an expected call, we tell it how to return. For this test, we’ll set the mock to fail regardless of the input. We’ll also need a second mock.
Arrange:
- The input to object under test
- The expected insert query
- The return value from database mock for the insert query (failure)
- The expected log message
- An instance of the object to test
- The database mock
- The logging mock
- Initialize test object with mocks (if applicable)
Act: Pass test input to object under test
Assert:
- Expected insert query was passed to database mock
- Test object returned a failure indication
- Test object logged error
If a log message is how we detect errors in production, it needs to be part of our test. Opening log files and parsing them is not the correct way to do that. (Don’t laugh, I’ve seen it done.) We treat the logger like any external dependency and mock it. Then we verify it received the correct message in our assertions.
Lessons From Mocking
Even with just two mocks, our test arrangements started to get lengthy.
The fewer the dependencies and interactions an object has, the easier it is to test. This is true regardless of the type of testing, but it is especially true with mocks. Each mock has to be created and primed with at least one expectation and a return value.
Obeying the Law of Demeter is always a good idea when designing objects. Carefully defining friends and avoiding “friends of friends” makes tests easier to define and read.
Using test-driven (or behavior-driven) development is another way to create loosely-coupled code. Placing tests first in the process lends itself to objects that are easy to isolate.
We’ve discussed the problems that static state and singletons can cause before. With static state, tests may suffer race conditions and other situations that lead to inconsistent test results. Singletons complicate creating mocks and ensuring that the test uses the correct instance. Fortunately, tools like Isolate solve these problems.
Mocking is a valuable testing tool that helps with test isolation. It’s often the best option when testing objects that interact with external resources. But don’t overlook it when creating tests for objects that communicate with each other inside application boundaries.
This post was written by Eric. Eric Goebelbecker has worked in the financial markets in New York City for 25 years, developing infrastructure for market data and financial information exchange (FIX) protocol networks. He loves to talk about what makes teams effective (or not so effective!)