Original post can be found on my blog.
Are you like and you are tired of having to manually add types
in your TypeScript
unit tests with ts-jest
?
If that's the case this article may be for you!
Let's dive into the problem.
This presumes that you are writing your unit tests with jest and ts-jest.
Installing dependencies
If you don't have them set up, then add these dependencies to your project
npm i -D jest @types/jest ts-jest
or with yarn
yarn add jest @types/jest ts-jest --dev
Project setup
Let's set up your jest.config.js
file properly
/**
* @type {import('@jest/types').Config.ProjectConfig}
*/
module.exports = {
preset: 'ts-jest',
coverageReporters: ['html', 'lcov', 'text'],
coverageDirectory: '<rootDir>/coverage',
transform: {
'^.+\\.ts': 'ts-jest',
},
testPathIgnorePatterns: ['<rootDir>/node_modules/'],
globals: {
'ts-jest': {
tsconfig: 'tsconfig.spec.json',
},
},
}
With this, we tell jest that we want to use ts-jest
as our transformer for TypeScript files.
The problem
export const myAwesomeFn = (a: number, b: number) => a + b
Let's say you want to mock a function from a random module; ts-jest
won't compile if you want to mock the module. Let's see the example below:
import { myAwesomeFn } from './my-awesome-module'
jest.mock('./my-awesome-module')
describe('my awesome describe', () => {
it('should assert something cool', () => {
myAwesomeFn.mockReturnValue('myAwesomeReturn')
})
})
In the code snippet above, line 7 won't compile since TypeScript cannot properly infer that myAwesomeFn
is mocked by jest
.
Possible solution
One way to resolve this problem is by casting the myAwesomeFn
variable. For example:
import { myAwesomeFn } from './my-awesome-module'
jest.mock('./my-awesome-module')
describe('my awesome describe', () => {
it('should assert something cool', () => {
(myAwesomeFn as jest.Mock).mockReturnValue('myAwesomeReturn')
})
})
This will work but you won't get proper inference on the return type of myAwesomeFn
.
@types/jest provides us with nice mock construct -- jest.MockedFunction, jest.MockedFunction, jest.Mocked and finally jest.Mock
With that in mind, we can improve the typings by using the right mock construct. But wait? How can TypeScript infer which construct to use?
Solution I came up with
With TypeScript
generics and @types/jest
we can come up with a nice way to let TypeScript do the job for us :)
I created this little npm package ts-jest-mock.
This package abstracts away a custom type and a utility function that we can use to improve our DX with ts-jest
.
Example with ts-jest-mock
import { myAwesomeFn } from './my-awesome-module'
import { createMock } from 'ts-jest-mock'
jest.mock('./my-awesome-module')
const myAwesomeFnMock = createMock(myAwesomeFn)
describe('my awesome describe', () => {
beforeEach(() => {
// Type error, TypeScript will complain that we are passing a string instead of a number
myAwesomeFnMock.mockReturnValue('myAwesomeReturn')
})
it('should assert something cool', () => {
// won't get executed because of compiler error
expect(myAwesomeFnMock()).toBe('myAwesomeReturn')
})
})
First on line 2
we import the utility function from ts-jest-mock
. Then on line 11
we use the beforeEach
pattern to properly create and infer our mock with the actual return type from our mocked module.
Since it properly infered the arguments and return types expected, line 11
will get a compiler error because we are trying to return a string
but the actual return type is a number
if you circle back when we defined this function here. Finally, line 16
won't get executed at all and jest will exit with an error code.
Conclusion
That's it folk, that's how you can get proper type inference in a unit test using jest
and ts-jest
with the help of ts-jest-mock
.
Top comments (0)