Búsqueda de sitios web

reactjs - ¿Cómo simular el hook useHistory en jest?


Estoy usando el hook UseHistory en react router v5.1.2 con TypeScript. ¿Al ejecutar la prueba unitaria, he tenido un problema?

TypeError: No se puede leer la propiedad 'history' de 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();
    });
});

También he intentado usar jest.mock('react-router',() =>({ useHistory: jest.fn() })); pero todavía no funciona.

Respuesta #1

Necesitaba lo mismo al hacer shallow a un componente funcional de React que usa useHistory.

Resuelto con el siguiente mock en mi archivo de prueba:

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

Respuesta #2

Este funcionó para mí:

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

Respuesta #3

Poniéndome en el papel de político, me atreveré a afirmar que estás haciendo la pregunta equivocada.

No es useHistory lo que quieres simular. En su lugar, solo querrías alimentarlo con un objeto de historial que controles.

Esto también te permite verificar las invocaciones de push, al igual que las 2 respuestas principales (al momento de escribir esto).

Si ese es realmente el caso, createMemoryHistory te respalda:

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()
})

Respuesta #4

Aquí hay un ejemplo más detallado, tomado de código de prueba en funcionamiento (ya que tuve dificultades para implementar el código anterior):

Component.js

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

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

Componente.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');
  });

Respuesta #5

En el repositorio de react-router de Github encontré que el hook useHistory utiliza un contexto singleton, y que puedes usar un MemoryRouter para proporcionar ese contexto en pruebas.

import { MemoryRouter } from 'react-router-dom';
const tree =  mount(
    <MemoryRouter>
        // Add the element using history here.
    </MemoryRouter>
);

Artículos relacionados