Introduction

When developing a system, it is important to write tests to give you the confidence that the system is working properly and make faster iterations to the system with the faster feedback loop. While end to end tests let you know that a particular feature is working, unit tests will let you know that the individual components are working.

In order to make tests fast and reliable, you must test these components in isolation. This means the component you are testing must not make external calls to databases or APIs. This post will show you how to cover a simple system with unit tests and use Jest Mocks and Spies in order to test in isolation.

The component we are testing for this example is a ProjectController class which has the ability to copy over project members from an older project. This class uses the ProjectMembersRepository class to facilitate this feature, but because we want to test the ProjectController in isolation, therefore we want to mock out the repository.

import ProjectMembersRepository from '../repositories/ProjectMembersRepository';

export default class ProjectController {

  constructor(private projectId: number) { }

  public async addMembersFromProject(otherProjectId: number) {
    const members = await ProjectMembersRepository.getMembers(this.projectId);
    await ProjectMembersRepository.addMembers(otherProjectId, members);
  }

}
export default class ProjectMembersRepository {
  public static async getMembers(projectId: number): Promise<number[]>
  public static async addMembers(projectId: number, userIds: number[]): Promise<void>
}

Automatic Mocks

Mocking is the process of replacing the implementation of another component in our system with dummy code, so that we can force it to always return an expected output. When the component we are testing interacts with the other component, we can ensure it behaves as intended.

jest.mock('../../src/repositories/ProjectMembersRepository')

test('add members from another project', async () => {
	ProjectMembersRepository.getMembers.mockImplementation(() => {
	  return Promise.resolve([2, 10])
	});

	const projectController = new ProjectController(12);
	await projectController.addMembersFromProject(6)

	expect(ProjectMembersRepository.addMembers)
	  .toHaveBeenCalledWith(6, [2, 10])
});

In this example, we are mocking out the project members repository class and replacing the implementation of the getMembers method to always return the same result (an array of 2 and 10). Now in the test we can verify if the addMembersFromProject method is correctly calling the addMembers method and with the parameters fed from ProjectMembersRepository.getMembers().

This approach will mock out every method of the class. But what if you want to mock out a specific method within the ProjectMembersRepository but hit the real implementation for the other methods; this is where Jest Spys can come in handy.

Jest Spys

This time we’re replacing the implementation of the getMembers() method but instead of mocking out the entire class and all of its methods, we’re just spying on the getMembers method. This will mean when ProjectMembersRepository.addMembers() is called, it will call the real implementation, unlike with automatic mocks.

test('add members from another project', async () => {
	const getMembersSpy = jest.spyOn(ProjectMembersRepository, 'getMembers');
	getMembersSpy.mockImplementation(() => {
	  return Promise.resolve([2, 10]);
	})

	const projectController = new ProjectController(12);
	await projectController.addMembersFromProject(6)

	expect(getMembersSpy).toHaveBeenCalledWith(12);
});

Conclusion

This post covers how to test units of code in isolation with the use of Jest mocks and Spys. The code used in this post is made available on my GitHub: https://github.com/MohammedFoysal/Jest-Mocks-Demo.