Как издеваться над useRef с помощью библиотеки тестирования jest.mock и react

#reactjs #unit-testing #jestjs #react-hooks #react-testing-library

#reactjs #модульное тестирование #jestjs #реагирующие хуки #react-testing-library

Вопрос:

У меня есть тестовый пример, в котором мне нужно издеваться над useRef, чтобы вернуть фиктивное текущее значение. Я попробовал jest.mock, но вместо этого он возвращает HTMLDivElement.

код:

    const ref = useRef<HTMLDivElement | null>(null);
 

тест:

   jest.mock('react', () => {
     const originReact = jest.requireActual('react');
       return {
          ...originReact,
          useRef: jest.fn(),
       };
  });



  React.useRef.mockReturnValue({ current: {offsetWith: 100} }); 
 

Макет возвращает

 [ { type: 'return', value: { current: [HTMLDivElement] } } ]
 

Комментарии:

1. Не издевайтесь ни над одним из API React, он вам не принадлежит. Проверьте поведение компонента, а не реализацию .

2. я вижу. есть ли способ проверить, изменяется ли текущее значение ссылки, например offsetWidth. Спасибо

Ответ №1:

предложение @jonrsharpe — это общий принцип тестирования компонентов, но ваш вопрос является частным случаем, связанным с некоторыми свойствами и методами DOM, такими как offsetWidth и getBoundingClientRect() методы. Среда выполнения jsdom не может вернуть, а механизм рендеринга в среде выполнения браузера возвращает результат, который приводит к offsetWidth тому, что значения свойств, возвращаемые getBoundingClientRect() методом, всегда будут 0 .

Итак, здесь нам нужен макет ref .

Макет ref здесь тоже особенный. В дополнение к использованию jest.mock() для частичного издевательства, ref.current свойство будет присвоено react после монтирования компонента. Чтобы перехватить эту операцию, нам нужно создать макет ref объекта со current свойством object , использовать Object.defineProperty() метод для определения getter и setter для этого ref.current свойства и перехватить назначение свойства.

jest.spyOn метод принимает необязательный третий аргумент AccessType, который может быть либо ‘get’, либо ‘set’, что оказывается полезным, когда вы хотите шпионить за получателем или установщиком соответственно.

Например.

index.tsx :

 import React, { useEffect, useRef } from 'react';

export default function App() {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    console.log(ref.current?.offsetWidth);
  }, [ref]);
  return <div ref={ref}>app</div>;
}
 

index.test.tsx :

 import { render } from '@testing-library/react';
import React, { useRef } from 'react';
import { mocked } from 'ts-jest/utils';
import App from './';

jest.mock('react', () => {
  return {
    ...jest.requireActual<typeof React>('react'),
    useRef: jest.fn(),
  };
});

const useMockRef = mocked(useRef);

describe('66332902', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });
  afterAll(() => {
    jest.resetAllMocks();
  });
  test('should mock ref and offsetWidth', () => {
    const ref = { current: {} };
    Object.defineProperty(ref, 'current', {
      set(_current) {
        if (_current) {
          jest.spyOn(_current, 'offsetWidth', 'get').mockReturnValueOnce(100);
        }
        this._current = _current;
      },
      get() {
        return this._current;
      },
    });
    useMockRef.mockReturnValueOnce(ref);
    render(<App />);
  });
});
 

результат теста: (см. Журналы)

  PASS  examples/66332902/index.test.tsx (9.518 s)
  66332902
    ✓ should mock ref and offsetWidth (39 ms)

  console.log
    100

      at examples/66332902/index.tsx:6:13

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        10.106 s
 

Комментарии:

1. Это не сработает, если внутри <App> других компонентов вызывается useRef. Есть идеи, как решить эту проблему @slideshowp2?

2. @philk — я также ищу способ издеваться над свойствами ссылки для данного компонента… не весь хук useRef!