3 Ways to Improve Type Safety in Jest Tests

Kaylie Kwon
May 18 · 3 min read
Photo by marcos mayer on Unsplash

It’s often easier to overlook type safety in unit tests and use type assertions or ts-ignore comments. Over time, this can lead to outdated tests and degrade their ability to catch bugs in production. Here are some ways you can introduce better type safety using Jest as the testing framework.

1. Assign objects to a Partial type before casting

Let’s say we have an object type that has a lot of properties, like config options or props for a complex React component.

An easy way to ignore this would be to cast the test value as the required type.

type Properties = {a: string, b: number, //...}const identity = (properties: Properties) => properties// Error: Argument of type '{}' is not assignable to parameter of type 'Properties'.
identity({});
// No errors with type assertion
expect(identity({} as Properties)).toEqual({})
expect(identity({a: 123} as Properties).toEqual({a: 123})

A better way would be to assign the test object to the version so that for the properties we choose to provide, the values are still type checked.

// Error: Type 'number' is not assignable to type 'string | undefined'.ts(2322)
const testProperties: Partial<Properties> = {a: 123};
expect(identity(testProperties as Properties))
.toEqual(testProperties);

2. Provide a type parameter to jest.Mock generic for classes

Given a function that takes in type and returns the user’s name, we could assign to replace the mock the class.

class User {
public name: string;
}
const getName = (user: User) => user.name
const randomObject = {
name: 12345,
doesNotExist: ''
}
const userMock = jest.fn().mockReturnValue(randomObject)// No errors despite the wrong types
expect(getName(userMock)).toEqual('kaylie')

The last line seems like it should result in a type error, because calling getName directly with will result in one. This is because Jest mock type defaults to any and doesn’t provide the same type safety.

// node_modules/@types/jest/index.d.tsinterface Mock<T = any, Y extends any[] = any> extends Function, MockInstance<T, Y> 
{
new (...args: Y): T;
(...args: Y): T;
}

But when we pass to the generic type parameter, we get better type safety with excess property checks.

const typedUserMock: jest.Mock<User> = jest.fn()// Argument of type '{ name: number; doesNotExist: string; }' is not // assignable to parameter of type 'User'. Types of property 'name' // are incompatible.
typedUserMock.mockReturnValue(randomObject)
typedUserMock.mockReturnValue({
name: 'kaylie'
})
// Type safe, and passes!
expect(getName(typedUserMock)).toEqual('kaylie')

3. Combine ReturnType with jest.Mock for functions

Here’s another simple example, where a function called takes another function as a parameter.

type Predicate = () => booleanconst filter = (predicate: Predicate) => {     predicate()}

Now we want to write a unit test that asserts the predicate is called when filter gets called. We’d want to ensure that has the same signature as the original type definition.

const mockPredicate = jest.fn()// Error: Type 'number' is not assignable to type 'boolean'.ts(2322)
filter(() => 1)
// No errors
filter(mockPredicate.mockReturnValue(1))

Similar to the previous example, we need to pass an argument to the generic parameter, but this time with the correct return type.

type Predicate = () => booleanconst typedMockPredicate: jest.Mock<ReturnType<Predicate>> = 
jest.fn();
// Error: Argument of type '1' is not assignable to parameter of type 'boolean'.ts(2345)
filter(typedMockPredicate.mockReturnValue(1))
// OK!
filter(typedMockPredicate.mockReturnValue(true))
expect(typedMockPredicate).toHaveBeenCalled()

Even though the examples used simple Jest mocks, you should also be able to apply it to Jest spies and module mocks. If you’re looking for a more full-featured approach, check out ts-auto-mock. Thanks for reading!

A note from Plain English

Did you know that we have launched a YouTube channel? Every video we make will aim to teach you something new. Check us out by clicking here, and be sure to subscribe to the channel 😎

JavaScript In Plain English

New articles every day.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

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