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?

wentjun
  • 40,384
  • 10
  • 95
  • 107
  • What is being returned from `jest.requireActual('react-router-dom')` ? – Chris Jul 07 '20 at 12:14
  • 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. – wentjun Jul 07 '20 at 12:18

2 Answers2

45

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: ...,
}));
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 1
    Thanks! This solves my issue. Any idea why would this error only appear on the jest 26? – wentjun Jul 08 '20 at 02:57
  • 2
    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. – wentjun Jul 08 '20 at 02:59
  • 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. – Estus Flask Jul 08 '20 at 06:18
  • 9
    `requireActual` [is generic](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/9c680c4/types/jest/index.d.ts#L184-L189), so an alternative is: `...jest.requireActual("react-router-dom")`. – Jonathan Reyes Aug 25 '20 at 21:20
  • I can confirm that the solution from @JonathanReyes works just as well :) – wentjun Jan 06 '21 at 02:25
  • 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), }; }); ` – Annie Huang Jan 19 '22 at 05:47
  • @AnnieHuang What exactly doesn't work? There is no type assertion (`as any`) in the code you posted. – Estus Flask Jan 19 '22 at 06:01
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: ...,
}));
Ambrown
  • 361
  • 2
  • 8