Mocking react-router-dom for useHistory hook causes the following error - TS2698: Spread types may only be created from object types
Asked Answered
S

3

25

I am trying to mock react-router-dom in one of my test cases so that the useHistory hook will function in my tests. I decide to use jest.mock to mock the entire module, and jest.requireActual to preserve the other properties that I may not want to mock.

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useHistory: () => ({
    location: {
      pathname: '/list',
    },
  }),
}));

This is actually derived from one of the highly rated solutions to the following question: How to mock useHistory hook in jest?

However, the TypeScript compiler is flagging the following error on the following line ...jest.requireActual('react-router-dom'),

TS2698: Spread types may only be created from object types.

Interestingly, I only face this issue after updating jest and ts-jest to the latest versions (jest v26). I do not face any of these issues when I was using jest 24.x.x.

"@types/jest": "^26.0.4",
"jest": "^26.1.0",
"ts-jest": "^26.1.1",

Does anyone know how to solve this issue for the latest jest versions?

Selfdenial answered 7/7, 2020 at 12:7 Comment(2)
What is being returned from jest.requireActual('react-router-dom') ?Lorrinelorry
Supposedly the other properties/methods from 'react-router-dom' package? The point of me doing the above is to only mock the useHistory hook, rather than the entire package.Selfdenial
A
47

jest.requireActual returns unknown type that cannot be spread.

A correct type is:

import * as ReactRouterDom from 'react-router-dom';

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom') as typeof ReactRouterDom,
  useHistory: ...,
}));

A quick fix is any:

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom') as any,
  useHistory: ...,
}));

It's acceptable because it doesn't impair type safety in this case.

Since react-router-dom is ES module, a more correct way to mock it is:

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom') as any,
  __esModule: true,
  useHistory: ...,
}));
Aligarh answered 7/7, 2020 at 12:44 Comment(7)
Thanks! This solves my issue. Any idea why would this error only appear on the jest 26?Selfdenial
Another point to add, for those who are using the default recommended eslint settings, you will need to add // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion above the ReactRouterDom type assertion, despite eslint telling us that there is no need to assert the type.Selfdenial
Probably you used older TS version that don't have this behaviour, or used another Jest setup with different typings, there's also third-party @types/jest. That's true, I always disable this rule by default.Aligarh
requireActual is generic, so an alternative is: ...jest.requireActual<typeof ReactRouterDom>("react-router-dom").Cateyed
I can confirm that the solution from @JonathanReyes works just as well :)Selfdenial
Hi @wentjun, the disable the eslint doesn't seem to work on my code of ` jest.mock('../../theme/device', () => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion return { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion ...jest.requireActual('../../theme/device'), useMediaQuery: jest.fn().mockReturnValue(false), }; }); `Pule
@AnnieHuang What exactly doesn't work? There is no type assertion (as any) in the code you posted.Aligarh
O
1

You can simply do as object to satisfy the Typescript. It's not ideal as the type is not very specific, but it does its job and is definitely better than as any :))

jest.mock('react-router-dom', () => ({
  ...(jest.requireActual('react-router-dom') as object),
  useHistory: ...
}));
Oligopsony answered 27/4 at 15:55 Comment(0)
P
0

In case anyone comes across this when using it for typescript instead of react-router-dom, what worked for me is:

import ts from 'typescript';

jest.mock('typescript', () => ({
  ...jest.requireActual('typescript') as Record<string, unknown>,
  nodeModuleNameResolver: ...,
}));
Proclus answered 28/9, 2022 at 15:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.