Write ES6 Class mocks for your Jest Unit Tests in TypeScript

Photo by Emil Priver on Unsplash

ES6 Class mocks are nothing new in the Jest world, and indeed the various ways to do them are well documented in the Jest documentation. However, when it comes to TypeScript, things can behave differently, and you might run in some issues. I experienced them and I want to share with you as well as how I worked around them.

Typescript is a superset language of JavaScript. Jest cannot directly unterstand it, and therefore all TypeScript code needs to be transpiled to JavaScript before the test is executed. This transpiration can either be done via the default TypeScript Checker or via Babel. While the former does strict type checking while transpiling your code to JavaScript and breaks if the types are wrong, the latter does not. That makes sense, since Babel is mainly used to convert JavaScript code writing in newer versions (ES6+) into backwards compatible versions of JavaScript that can run on older engines.

Using jest with Babel to transpire your Typescript code is very well explained in the official Jest documentation.

For using the default TypeScript checker to transpile and type check your test code, there is ts-jest.

To configure your project in one way or the other to can follow to official Jest documentation for Babel or the ts-jest documentation to set up ts-jest.

Jest provides four ways to mock ES6 classes.

  • Automatic mock — Just let Jest mock the class, and you can spy on the calls of the class, yet not change the implementation, e.g., when you want specific return values for methods of the class
  • Manual mock — Provide a mock implementation of the class in the __mocks__folder
  • Module factory function — Provide a mock implementation in a factory function, when calling jest.mock()in your test file.
  • Replacing mock implementation in individual tests — Provide or override existing mock implementations via calling the mockImplementation() function on the class mock.

Both the automatic mock and the manual mock work fine with Babel as well as with ts-jest. However, when it comes to the Module factory function, things look a bit differently with ts-jest. And replacing mock implementations in individual tests requires some typing with TypeScript, yet it works fine with Babel and ts-jest.

Providing a mock implementation with the factory function with ts-jest as described in the Jest documentation leads to a type error message that the mocks default method is not a constructor:

jest.mock('./myClass', () => {
return jest.fn().mockImplementation(() => {
return {
mockAttribute: "foobar",
mockMethod: jest.fn().mockReturnValue("mockedValue")
};
});
});

// Error
// TypeError: myClass.default is not a constructor

However, the same implementation will work if you transpile your TypeScript code with Babel instead of ts-node.

Nevertheless, there is a way around it. You can provide the default constructor method as well as the flag that is an ES module in the mock:

jest.mock('./myClass', () => {
return {
__esModule: true,
default: jest.fn().mockImplementation(() => {
return {
mockAttribute: "foobar",
mockMethod: jest.fn().mockReturnValue("mockedValue")
};
})
}
});

With that, the test will and not having a TypeError.

When you want to replace the implementation in an individual test, you need to type your Class as Mock to have mockImplementation function available.

import MyClass from "./myClass";
import Mock = jest.Mock;

jest.mock("./myClass");

describe("Testsuite to test class Mock", () => {

it('should test the mock', () => {
(MyClass as unknown as Mock<MyClass>).mockImplementation(() => {
return {
mockAttribute: "foobar",
mockMethod: jest.fn().mockReturnValue("mockedValue")
};
})
// Test Action
// Test Assertions
});
})

Both approaches, Babel or ts-jest, are good ways to integrate TypeScript with Jest. While Babel seems to be the more straightforward solution, just following the official Jest documentation. ts-jest needs a workaround when using it to mock the class via the mock factory function. However, it provides type checking for your tests, which can be a considerable benefit. Knowing these caveats hopefully helps you to make a conscious decision which solution to choose.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Tobias Meyer

I’m a self-taught web developer who loves React, the Jamstack and serverless architectures. Lives and works in Munich. Opinions are my own.