2 Comments

Mocking in JavaScript Unit Tests Using Sinon.JS

Lately, I’ve been using Sinon.JS for mocking in my unit tests. By using mocks for dependencies inside functions, I can write unit tests that are resilient to a changing codebase. Functions with mocks can also be developed without worrying about the chain of dependencies that could affect the logic inside the functions.

An Example

Let’s use this simple “greetUser” function to try out Sinon.JS.


import * as UserRepository from 'project/repos';

export async function greetUser(userId) {
	const user = await UserRepository.findById(userId);
  if (!user) {
    throw new Error(`No user found for id ${id}`);
  }
  if (user.name) {
    return `Hello, ${user.name}!`;
  }
	return 'Hello!';
}

This function is dependent on the implementation of UserRepository. Based on the result of the findById method, greetUser will throw an error, create a message with the user’s name, or return a simple greeting.

Let’s start with a test that assumes we can find a user:


describe('greetUser', () => {
  it('greets the user if one is found', async () => {
    // We'll need to do some setup here soon...

    const message = await greetUser(2);
    expect(message).to.equal('Hello, Andy!');
  });
});

In order for this test to pass, UserRepository.findById needs to return a user. However, we don’t necessarily know (or even care) how UserRepository.findById is implemented at this point. This unit test is concerned with the logic within the greetUser function.

Instead of figuring out the implementation details of UserRepository.findById, we can use Sinon.JS to create a stub:


describe('greetUser', () => {
  it('greets the user if one is found', async () => {
    sinon.stub(UserRepository, 'findById').returns({ name: 'Andy' });

    const userId = 2;
    const message = await greetUser(userId);
    expect(message).to.equal('Hello, Andy!');
  });
});

Stubs let us force a specific behavior on the UserRepository, allowing us to exercise the different logical branches in the greetUser function. With a stub, the development of the UserRepository is decoupled from the development of this particular function. We can build and fully unit-test this function without writing a single line of code for the UserRepository</code.

Let’s force another behavior that’ll exercise a different logical branch of greetUser. In this example, we can test what happens if the user doesn’t have a name, like this:


it('generates a generic message if name is not specified', async () => {
  sinon.stub(UserRepository, 'findById').returns({ name: null });

  const userId = 2;
  const message = await greetUser(userId);
  expect(message).to.equal('Hello!');
});

We can also test the case when no user is found:


it('throws an error when no user is found', async () => {
  sinon.stub(UserRepository, 'findById').returns(null);

  const userId = 2;
  expect(await greetUser(userId)).to.be.rejected;
});

With these tests, we’ve exercised all the branches of our code without worrying about the implementation of UserRepository.

A Few Considerations

Since this test is decoupled from the implementation of UserRepository, we should add another test that does not mock out anything. This helps ensure that everything is wired together correctly.

Mocking prevents this suite of greetUser tests from failing whenever UserRepository is broken, but the test suite is vulnerable to false positives if there are no tests around UserRepository and a system level test that uses both without mocks.

Mocks allow us to write unit tests that only test the internals of a particular function. As a codebase grows, these tests won’t have to change if the logic inside the function remains unchanged.

What other methods do you rely on for unit testing?