69 votes

Comment simuler le hook useHistory en jest ?

J'utilise le hook UseHistory dans react router v5.1.2 avec typescript ? Lors de l'exécution du test unitaire, j'ai eu un problème.

Erreur de type : Impossible de lire la propriété 'history' d'undefined.

import { mount } from 'enzyme';
import React from 'react';
import {Action} from 'history';
import * as router from 'react-router';
import { QuestionContainer } from './QuestionsContainer';

describe('My questions container', () => {
    beforeEach(() => {
        const historyHistory= {
            replace: jest.fn(),
            length: 0,
            location: { 
                pathname: '',
                search: '',
                state: '',
                hash: ''
            },
            action: 'REPLACE' as Action,
            push: jest.fn(),
            go: jest.fn(),
            goBack: jest.fn(),
            goForward: jest.fn(),
            block: jest.fn(),
            listen: jest.fn(),
            createHref: jest.fn()
        };//fake object 
        jest.spyOn(router, 'useHistory').mockImplementation(() =>historyHistory);// try to mock hook
    });

    test('should match with snapshot', () => {
        const tree = mount(<QuestionContainer />);

        expect(tree).toMatchSnapshot();
    });
});

J'ai également essayé d'utiliser jest.mock('react-router', () =>({ useHistory: jest.fn() })); mais cela ne fonctionne toujours pas.

93voto

Proustibat Points 505

J'ai eu besoin de la même chose lors de la création d'un composant fonctionnel react qui utilise useHistory .

Résolu avec la simulation suivante dans mon fichier de test :

jest.mock('react-router-dom', () => ({
  useHistory: () => ({
    push: jest.fn(),
  }),
}));

2 votes

Pour ceux qui utilisent TypeScript, cette approche peut provoquer l'erreur "React.createElement : type is invalid - expected a string" si le composant utilise Link y useHistory en même temps. L'approche d'Erhan ne posera pas ce problème.

16 votes

Y a-t-il un moyen de capturer useHistory().push() invocations ?

8 votes

Mais comment espionner la fonction useHistory ?

58voto

Erhan Points 440

Celui-là a marché pour moi :

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

1 votes

Cette approche préserve les autres fonctions de react-router-dom que vous ne souhaitez peut-être pas simuler.

7 votes

@Erhan J'ai fait la même chose, mais je reçois à nouveau l'erreur : TypeError : Cannot read property 'history' of undefined. any suggestion ?

1 votes

Cela ne fonctionnera pas en TypeScript, car cela donnera l'erreur suivante : TS2698: Spread types may only be created from object types.

34voto

targumon Points 344

Avec ma casquette de politicien, j'ose affirmer que vous posez la mauvaise question.

Ce n'est pas useHistory dont vous voulez vous moquer. Au lieu de cela, vous voulez juste l'alimenter avec un objet historique que vous contrôlez.

Cela vous permet également de vérifier push comme les deux meilleures réponses (au moment où j'écris ces lignes).

Si c'est effectivement le cas, createMemoryHistory Je vous couvre :

import {Router} from 'react-router-dom'
import {createMemoryHistory} from 'history'

test('QuestionContainer should handle navigation', () => {
  const history = createMemoryHistory()
  const pushSpy = jest.spyOn(history, 'push') // or 'replace', 'goBack', etc.
  render(
      <Router history={history}>
        <QuestionContainer/>
      </Router>
  )
  userEvent.click(screen.getByRole('button')) // or whatever action relevant to your UI
  expect(pushSpy).toHaveBeenCalled()
})

0 votes

J'ai essayé celui-ci, mon onClick fonctionne (vérifié par le journal de la console) mais history.push n'est pas déclenché, pouvez-vous m'aider à le déboguer ?

0 votes

@RanjanKumar difficile de déboguer sans voir le code... mais je vais quand même essayer : est-ce que vous espionnez le 'push' comme dans mon exemple et pourtant l'attente toHaveBeenCalled échoue ?

1 votes

Je pense que vous avez raison de porter ce chapeau. Je ne voulais pas vraiment me moquer de l'histoire. Je voulais l'erreur que j'ai eu en appelant useHistory de s'en aller ! Rendu intérieur <Router history={createMemoryHistory()}> a bien fait l'affaire pour moi !

29voto

Alex W Points 11502

Voici un exemple plus verbeux, tiré d'un code de test fonctionnel (puisque j'ai eu des difficultés à mettre en œuvre le code ci-dessus) :

Composant.js

  import { useHistory } from 'react-router-dom';
  ...

  const Component = () => {
      ...
      const history = useHistory();
      ...
      return (
          <>
              <a className="selector" onClick={() => history.push('/whatever')}>Click me</a>
              ...
          </>
      )
  });

Composant.test.js

  import { Router } from 'react-router-dom';
  import { act } from '@testing-library/react-hooks';
  import { mount } from 'enzyme';
  import Component from './Component';
  it('...', () => {
    const historyMock = { push: jest.fn(), location: {}, listen: jest.fn() };
    ...
    const wrapper = mount(
      <Router history={historyMock}>
        <Component isLoading={false} />
      </Router>,
    ).find('.selector').at(1);

    const { onClick } = wrapper.props();
    act(() => {
      onClick();
    });

    expect(historyMock.push.mock.calls[0][0]).toEqual('/whatever');
  });

19voto

Ivan Martinyuk Points 20

Dans le repo react-router de github j'ai trouvé que le hook useHistory utilise un contexte singleton, quand j'ai commencé à l'utiliser dans le mount MemoryRouter il a trouvé le contexte et a commencé à fonctionner. Donc je le corrige

import { MemoryRouter } from 'react-router-dom';
const tree =  mount(<MemoryRouter><QuestionContainer {...props} /> </MemoryRouter>);

0 votes

S'il vous plaît laissez-nous savoir comment nous allons obtenir ...props valeur ? ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X