What is Mocking?

what is mocking

Here’s a post I wrote for Typemock back in August. They published it in two parts here and here. TDD has become a passion of mine, and I enjoy writing about testing and testing concepts. This post was an excuse to go back and read some posts and books from Martin Fowler and Kent Beck, which were added bonuses. Enjoy!


Mocking is a term that comes up often when we talk about unit testing. What does it mean?

The verb “mock” has several definitions, but only one relates to how we use it in software engineering:

a: to imitate (someone or something) closely: mimic
mockingbird was mocking a cardinal —Nelson Hayes

Mocking means replicating the behavior of something. We replace an object with a mock for testing purposes.

Sometimes there is debate over the differences between a mock, a stub, and a fake. We’ll cover all three, with an emphasis on mocking and how you can use it to improve how you design and test your code.

Mocking Defined

Mocking is creating an object that mimics the behavior of another object. It’s a strategy for isolating an object to test it and verify its behavior.

Definitions of mocking differ over whether a mock must verify behavior. However, Fowler calls mocks “objects pre-programmed with expectations which form a specification of the calls they are expected to receive.” Yet Pivotal says they are “self-verifying spies.” Their definitions are good enough for me.

So let’s expand our initial definition:

Mocking is creating an object that mimics another and setting it with expectations about how it will be used.

“Regular” Unit Testing

Regular unit testing verifies results. A typical example is writing tests for a calculator. A method that adds two integers can be verified by passing it 2 and 2 and checking for 4.

A calculator is unit testing nirvana. If all tests were as simple as a finite set of inputs and a computed output, verifying behavior would be simple. But even in an imaginary functional utopia, objects interact with the outside world. We need a way to keep our unit tests isolated.

Isolation

Isolation is a critical testing concept. When we test an object, it should be separated from other objects and external dependencies.

With the calculator example, this is easy. But it’s more likely that objects will interact with each other and with external dependencies. Isolation is a vital testing goal that often creates the need for mocking.

If we create an object that interacts with a database, making the database a test dependency is not acceptable. We create an object that mimics the database interface.

But we don’t only isolate from external dependencies. It’s a best practice to confine unit tests as much as possible to one object at a time. When our tests separate components, fixing new problems as they occur is easier.

Imagine moving to a new compiler version and discovering a handful of broken tests. If each test represents a single object, fixing them is simple. If the tests touch three or four objects, repairing the problems is more difficult.

Stubbing and Faking

A stub is an object that mimics another, but with predefined or “canned” responses. We use stubs for tests that rely on an interface to provide one or more calls that return void.

Canned responses can go beyond void, though. Suppose we have an object that inserts single records to a SQL database and the result of a successful insert is always “1,” indicating a single row. We can isolate this object with a simple stub.

A fake is a more sophisticated stub. It implements a subset of the behavior that a production object does, but it’s lighter weight. One example is a fake web service or an in-memory database that replaces a remote version.

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.

Leave a Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: