Test mocking in practice with Sinon

Imagine that you coded a new controller that provides a new endpoint for your REST API to create some register (for example, a new Pokemon) that uses a service to create this new register. That service, in turn, does a request to your database sending the input data to save it, and works perfectly as you imagined. But you need to write a unit test for this feature, so you ask yourself: “How can I test this controller that uses a service that makes a communication with my current database connection?”. You can answer: “Is simple, just need to create a test database instance only for my test environment and erase its data after the tests end.” Well, that is it!

Wait, wait, wait!!! This is a valid suggestion, but wouldn’t you agree that your test controller scope does not need to do a real request to your test database through your register service? Well, I would. That being the case, you can apply a very useful strategy for tests: Mock.

What is mock?

So, mocking is a good solution for the case described above. When you need to test an object that relies on an expensive or complicated resource, you can create a fake version of this resource so that its output can be controlled and constant, avoiding situations like the same test returning different results on each execution.

Do you know Sinon?

Let’s do some practical examples implementing mock with JavaScript using Sinon. Sinon is a standalone test spy, stubs, and mocks library for JavaScript that works with any unit testing framework, like Jest or Mocha.

Take a look at the code below:

const db = require("./create-user");

async function createUser({ name, age }) {
    try {
        return await db.persistUser({ name, age });
    } catch (error) {
        throw error;
    }
}

module.exports = { createUser };

This code is very simple. It’s a service responsible for calling the database layer, persisting a user, and, in the case of an error, throwing the error’s exception.

As a curiosity about the database layer, the persistUser method is responsible for returning just the name and age params passed with id (random UUID) and createdAt attributes.

const crypto = require("crypto");

async function persistUser({ name, age }) {
    try {
        return await Promise.resolve({
            id: crypto.randomUUID(),
            name,
            age,
            createdAt: new Date(),
        });
    } catch {
        throw new Error("transaction error");
    }
}

module.exports = { persistUser };

So, how could we write a test using a mock concept and apply it with Sinon?

First of all, we need to use the lifecycle method beforeEach that will set up the test to call the Sinon method restore before each test case, the main goal is to clear the mock state to avoid any remaining mock setup and resources from previous tests

In the first test case, we want to test the happy path, ie. create a new user successfully. For that, we need to create a mock for persistUser method from db module that returns static user data. Then, we can do the assertion of our test using: expect(result).toStrictEqual(user);, because with the mock we can avoid having different random UUIDs and dates on every test run, keeping our test consistent.

Again, take a look at the code below:

    it("returns user", async () => {
        const user = {
            id: "uuid",
            name: "Juan",
            age: 23,
            createdAt: new Date("02/04/2022"),
        };

        sinon.mock(db).expects("persistUser").once().resolves(user);

        const result = await service.createUser({ name: "Juan", age: 23 });

        expect(result).toStrictEqual(user);
    });

Now, the error case test below is a good scenario to see how mocking is useful. Because imagine that we do not mock the persistUser method, how do we force an exception to simulate an error case? We would need to do some workaround to force that exception. But using Sinon to mock persistUser method, simulating an exception scenario is easy. We just need to write:

sinon.mock(db).expects("persistUser").once().throws()

This code deals with getting the persistUser method, mocking it, and, just one time, throwing an exception. With that, we can do an assertion that expects an exception to be captured by service.createUser. Quite easy huh?

    describe("When has an error", () => {
        it("throws error", async () => {
            sinon.mock(db).expects("persistUser").once().throws();

            await expect(
                service.createUser({ name: "Juan", age: 23 })
            ).rejects.toThrow();
        });
    });

Putting everything together:

const service = require("./index");
const db = require("./create-user");
const sinon = require("sinon");

describe("create user", () => {
    beforeEach(sinon.restore);

    it("returns user", async () => {
        const user = {
            id: "uuid",
            name: "Juan",
            age: 23,
            createdAt: new Date("02/04/2022"),
        };

        sinon.mock(db).expects("persistUser").once().resolves(user);

        const result = await service.createUser({ name: "Juan", age: 23 });

        expect(result).toStrictEqual(user);
    });

    describe("When has an error", () => {
        it("throws error", async () => {
            sinon.mock(db).expects("persistUser").once().throws();

            await expect(
                service.createUser({ name: "Juan", age: 23 })
            ).rejects.toThrow();
        });
    });
});

A Dependency Injection (DI) perspective

In some situations, we don’t have control over the results that our code generates, like when we need to test something that works with Dates or Random numbers. Using DI can help us a lot. We can define DI simply as a way of transferring the task of instantiating an object to someone else and using this dependency is called dependency injection. In other words, we can create, by hand, our mock implementation for objects/methods that we don’t have control of the output.

Take o look at this article written by Henrique Yuji, to know more about this concept and how to apply it in your tests: Dependency Injection in JS/TS – Part 1

Conclusions

Using mocks can be a very useful strategy in many cases in our development life to avoid wasting time setting complex configurations like databases, external APIs, etc. Tools like Sinon can help us be more productive by making the tests easier to understand, lighter when running, and without side effects.

References

jestjs.io
sinonjs.org/releases/v14
Mocks Aren’t Stubs – Martin Fowler
Book, Test-Driven Development By Example – Kent Beck. Chapter: Testing Techniques

We want to work with you. Check out our "What We Do" section!