Chris James - Software Engineer and other things

Start naming your test doubles correctly

30 July 2020

A lot of developers are very vague when they name their test doubles which makes tests needlessly more difficult to understand.

In conversation there’s also often ambiguities which cause problems

I overheard on a podcast

Have you ever used a mock where you make assertions on the call?

This post will hopefully illuminate why that sentence is a bit silly!

By making a little more effort to be precise with your naming, you can communicate the intent of your tests and the design of your system more effectively

Quick primer on dependencies

To separate concerns, we often make objects that will “depend” on other objects.

We “inject” or “pass in” these dependencies when we construct them, so they can do a job.

This is called Dependency Injection.

Example

A ShoppingCart sends an email when you call its sendOrder method.

We don’t want ShoppingCart to have to know how to send emails as well as everything else so we give it an Emailer when we create it.

new ShoppingCart(new MailgunEmailer())

It can then use the emailer without having to know how emails are sent, and you have your separation of concerns.

sendOrder() {
    // interesting domain logic
    this.emailer.send(confirmation)
}

This approach has implications for our tests.

With tests, we prefer not to use real objects for dependencies because we want to be able to control them, so we use something different

Use “Test Doubles”

Not specifically mocks

A test double is what you inject into an "object under test" (OUT) to control a dependency.

Like a stunt double!

Using the precise words for test doubles helps reveal intent and documents your design more effectively

It’s not about being big and clever, it helps you communicate unambiguously. Software development is a team-sport so clear communication will help your team work better.

The different kinds of test doubles

Dummies

Sometimes you have arguments to functions that you don’t care about. Dummies are the arguments you pass in.

const dummyLogger = jest.fn()

Stubs

Some objects will query dependencies for data.

Stub the dependency

const stubUserService = jest.fn(() => 'Miss Jones')

Spies

Sometimes you want to call a method (or more abstractly send a command) to a dependency.

It can be hard to tell in a test this happened from the outside as it’s an internal detail.

If you want to verify these commands, use a spy.

const spyEmailSender = jest.fn()
// do some stuff
expect(spyEmailSender.mock.calls.length).toBe(1)

Fakes

Usually a “real” implementation but constrained.

Like an in-memory database.

Not as popular anymore due to things like Docker which let you spin up local versions of databases without the risk of there being differences between a fake and a real implementation.

Mocks

Mocks are a kind of test double!

Mocks are precise and encapsulate the detail of your interactions more explicitly.

You describe upfront what calls you expect on your test double - Order - Arguments - Specific return values

If the object under test doesn’t call things as expected the mock will error.

So returning to

Have you ever used a mock where you make assertions on the call?

What is being described is just a mock. If you're not worried about the calls, you're talking about a stub.

Why “mocking too much is bad”

The risk is your tests become needlessly precise and coupled to implementation detail.

This is also true of spies as they have a similar nature.

If you're overly specific with your mocks, when you want to refactor things you may find tests failing for annoying reasons that have nothing to do with behaviour.

Why “having too many test doubles is bad”

Listen to your tests if they cause you pain

If you have many test doubles in a test that means your OUT has many dependencies.

Does that sound right to you? It shouldn’t and it means you probably need to take a look at your design.

An anecdote

Yesterday I reviewed a test which had a test double named

getterFn

I had to spend some time reading the test to understand what was going on, it turns out it should've been called

spyCSVFetcher

Once this was ascertained it turned out the writer had actually neglected to spy on the number of calls, which was actually a core part of what was supposed to be tested.

If the double had been named correctly in the first place (which is very little effort) I would know to look for assertions on the way it was called to help me understand what the intent of the code is. Instead, it took some investigation.

Summary

Consistently using the recognised names for test doubles will help you be precise when communicating with other developers. It's a low-effort way to improve the way you work.

For instance if you name something a dummy rather than a mock the readers of your tests can forget about that particular test double and focus on the important collaborators.

Tests should act as documentation so describing your test doubles clearly will help reveal the design of the test for maintainers.

So start naming your test doubles correctly, today!

Quiz

Further reading