fbpx

What is mocking?

Are you new to the unit testing world and your colleagues are talking about that strange thing called mocking?

This article will get you started. It focuses on Javascript, and uses specifically JEST syntax, but the concepts apply to most languages and testing frameworks.

What is mocking?

The purpose of mocking is to isolate pieces of code

The word mocking, however, is also often used interchangeably to define different types of techniques which help achieve the goal of isolation. Those techniques are called test doubles and the two most common ones are Stubs and Spies, so these are the ones we’ll focus on in this article. However, other test doubles such as dummies and fakes exist.

 Stubs

Let’s take this function for example:

function isNameValid(name) {
   if (name !== "") {
       // some logic
  }
}

If you want to test its logic, you merely have to call the function with a non-empty string.

No mocking is needed here. You can write a test as simple as this:

expect(isNameValid("pony")).toBe(true)

Let’s now look at another function:

function foo() {
   if (Math.random() > 0.5) {
  // some logic
  }
}

If you want to test the logic within this function, you have to control the return value of Math.random (otherwise, your test would be flaky, which means it would pass sometimes and fail at other times). This is called stubbing.

Here is an example of what a test for this function could look like:

Math.random = () => 1
expect(foo()).toBe(true)

We have updated Math.random so it always returns 1, which to be fair is still pretty random, as illustrated in the famous meme:

Note that the original implementation of Math.random was never called, because it was stubbed (= replaced) instead.

Several frameworks allow you to stub easily, by taking care of implementation details for you. We’ll offer a comparison in a future article.

Spies

Here is another interesting aspect of mocking

If you remember the AAA pattern, then you know that the point of unit testing is to write assertions. But what should you assert on? By default, you can only observe what happens outside the function:

  • You can test the return value
  • You can test that parameters have been modified
  • You can test that the function took less than X milliseconds to complete
  • You can test that a global variable has been modified

However, you might also want to test some logic that happens inside the function itself. 

For example, you might want to make sure the database has been queried in the following function. This is called spying:

function addUser(id) {}
// some logic
   db.query("INSERT INTO table VALUES ($1)", id)
}

In order to achieve this, you could write a unit test that looks like this:

// 1. Have a variable to store whether the database was queried
let called = false
​
// 2. Spy on db.query
db.query = () => { called = true }
​
// 3. Call our function
const result = addUser(1)
​
// 4. Assert on the return value
expect(result).toBe(true)

Observe that we respected the AAA pattern: steps 1 and 2 are Arrange, step 3 is Act, step 4 is Assert.

The fake function that we wrote at line 2 is called a spy.

Note that in practice, unlike stubs, spies don’t prevent the original function from being called. In our example above, the database will be queried in real life. If you don’t want that to happen, you might want to use both a spy and a stub.

Also note that mocks (stubs and spies) should be restored. This means resetting to the original implementation of the function. You don’t want your stubs and spies to influence other test cases that did not expect some functions to be mocked.

Here is an illustration of how restoring works with Math.random:

const originalRandom = Math.random
Math.random = () => 1
expect(foo()).toBe(true)
Math.random = originalRandom

Fortunately, all mocking frameworks expose nicer ways to mock and to restore functions. For example, with Jest you can just write

`Math.random.mockRestore()`

Why mocking?

In a typical unit test, your modules should be  isolated. There should be no interactions between all the parts of your code.

You don’t want a unit test to make API calls, rely on a database or write to your file system whenever it is run – whether from your command line or a CI. Mocking is the magic tool that allows your modules to be isolated.

On the contrary, you are unlikely to find mocking in integration tests.

Mocking also allows you to make assertions on side-effects of your function that could not have been tested otherwise (i.e.: how would you otherwise make sure the database was queried, if there is no database?)

When not to mock / Controversy

Mocking is a precious tool, but it’s not universally loved.

Some common reasons for disliking mocks, are that

  • Mocking undeniably increases the length of your unit tests, making them more difficult to read and to maintain.
  • It also makes your unit tests more dependent on your implementation, which means they might break even though the overall functionality of a unit doesn’t change

In particular, there are some specific scenarios where mocking is not recommended, here are a few: 

1. Integration tests

The reason for this is obvious: integration test should validate the correct interaction between different modules and parts of code as they would happen at the time of running the application. For this reason, isolation through mocking defeats the point of integration tests themselves.

2. Implementation changes

With mocking, your unit test becomes aware of the internal implementation of your function: it knows what is going to be called. This is called whitebox testing.

Say you change the implementation of addUser, and your function now calls mysql2.query instead of db.query. Your unit tests that verifies that will be broken.

Of course it will be easy to change, but without mocking there would have been less maintenance. Writing unit tests that are unaware of the internals of the tested functions is called blackbox testing.

Sometimes you just can’t avoid stubbing in order to run your test , but in particular in the case of spies, you should always ask yourself what it makes sense to assert.

3. Testing an object subset

If you want to make an assertion on an object in which some properties are non-deterministic, it is worth considering adapting the test rather than mocking indiscriminately.

Take, for example, the scenario below.

function foo () {
   return {
  name: "Rainbow",
  value: Math.random(),
}
}

Two options are available to you:

  • Stub Math.random so that the object is always the same;
  • Merely test a subset of the object

You could write a test of this kind, but it would be a little verbose:

// This works but is verbose
Math.random = () => 1
expect(foo()).toEqual({ name: "Rainbow", value: 1 })
​Alternatively, you can simply use one of the following two alternatives, which achieve the same result of giving confidence in your code without the need for a stub.

// This is clean
expect(foo().name).toEqual("Rainbow")
​
// This is also clean
expect(foo().name).toMatchObject({ name: "Rainbow" })

How Ponicode helps

With Ponicode, you can create mocks in one click, without learning any framework. Right click on the function you want to mock, and Tadaam! the mocking code will be generated for you. Learn how it works with our illustrated documentation.

We have presented two common uses of mocking. We believe it is a great testing technique as long as it remains simple.

Do let us know what you think about it, and what your favourite tools, strategies, or libraries for mocking are. Or the worst mocking code you’ve ever seen!

And subscribe to our newsletter for more tips on mocking!

START PONICODE

Leave a Comment

Your email address will not be published. Required fields are marked *