«Почему большинство разработчиков боятся постоянно вносить изменения в свой код? Боятся, что сломают! Почему они боятся сломать его? Потому что у них нет тестов ».

- Роберт К. Мартин, Чистый кодер: Кодекс поведения для профессиональных программистов

Хороший софт хорошо протестирован. Вот некоторые основы использования React и Jest, которые помогут начать работу с модульным тестом.

Что такое модульное тестирование?

Модульное тестирование - это когда мы тестируем отдельные участки кода. В React это может быть компонент или функция.

Почему мы проводим модульное тестирование?

Модульное тестирование - это самый маленький и простой тест, который мы можем написать. Это позволяет выявлять ошибки на раннем этапе, что означает, что в более поздних тестах (интеграция, регрессия и т. Д.) Требуется гораздо меньше усилий. Мы хотим сосредоточиться на модульных тестах, так как они могут быть написаны на стадии разработки и сохранят эффективность на должном уровне.

Источник: http://www.testingreferences.com/here_be_pyramids.php

Что нам следует тестировать?

Мы хотим протестировать как счастливый, так и несчастливый путь для компонента. Удачный путь предполагает, что все, что делает пользователь, соответствует ожиданиям. Несчастливый путь предполагает, что пользователь пытается сделать что-то неожиданное, например, если пользователь пытается ввести числа в текстовое поле.

Тесты должны проверять то, что видит пользователь, проверять поведение приложения, а не детали реализации. Поведенческое тестирование похоже на проверку того, действительно ли всплывающее окно появляется после нажатия этой кнопки. Детали реализации были бы чем-то вроде проверки состояния этого компонента после нажатия кнопки. Тестирование того, что пользователь действительно видит / использует, гарантирует, что мы лучше охватим его опыт. Автоматические тесты должны подтвердить, что код приложения работает для производственных пользователей. (Https://kentcdodds.com/blog/testing-implementation-details)

После того, как мы напишем тест и довольны результатом, его не нужно будет переписывать. Единственный случай, когда это нужно будет изменить, - это если функциональность изменилась. Нефункциональные требования не должны препятствовать прохождению тестов.

Когда он перестанет быть модульным тестом?

Тест не является модульным, если:

  • Он обращается к базе данных
  • Он общается по сети
  • Это касается файловой системы
  • Его нельзя запускать одновременно с другими модульными тестами.
  • Вы должны делать особые вещи в своей среде (например, редактировать файлы конфигурации), чтобы запустить ее.

(Https://www.artima.com/weblogs/viewpost.jsp?thread=126923)

Это не исчерпывающий список, но несколько хороших примеров того, что НЕ должен охватывать модульный тест. Модульные тесты должны быть автономными и не полагаться друг на друга. Если тест основан на состоянии предыдущего теста или другого компонента, это НЕ модуль.

Покрытие кода

Хотя было бы неплохо иметь 100% покрытие кода, это не цель. Покрытие кода не принимает во внимание, что на самом деле тестируют тесты, и их качество. Покрытие кода дает нам оценку того, насколько хорошо протестирован наш код. Однако мы должны сосредоточиться на функциональности.

Хорошее обсуждение других показателей и покрытия кода: https://stackoverflow.com/questions/90002/what-is-a-reasonable-code-coverage-for-unit-tests-and-why

Тестовые пары

Использование тестовых двойников позволяет нам изолировать желаемое поведение, сосредоточиться на жизненно важных частях теста и смоделировать все остальное. Jest - хороший фреймворк, который мы можем использовать (https://jestjs.io/) для этого.

Например, мы можем высмеивать несущественные методы так:

  • Когда нас не волнует результат, мы можем использовать jest.fn ()
const { getByTestId } = render(
   <MUIPicker
      testid="endDate"
      isDisabled={false}
      onBlur={jest.fn}
   />
);
  • Когда нас действительно волнует результат работы метода, мы можем использовать mockImplementation ().
API.post = jest.fn().mockImplementation(() => {
   return Promise.reject({
      response: {
         data
      }
   });
});

Если мы имитируем что-то, нам также необходимо сбрасывать макеты после каждого теста и предварительно инициализировать их заново. Для этого можно использовать beforeEach () и afterEach ().

beforeEach(() => {
   jest.fn().mockRestore();
});
afterEach(() => {
   jest.fn().mockClear();
});

Дополнительная информация: https://jestjs.io/docs/en/mock-function-api

Зацикливание

Если у вас есть похожие тесты, которые могут требовать только разных входных данных, чтобы следовать принципу СУХОЙ (Не повторяйтесь) чистого кода, мы можем использовать циклы.

it.each`
errorObject     | id          | message
${"success"} | ${"elementID1"}  | ${"successful import"}
${"warning"} | ${"elementID2"}  | ${"import with warnings"}
${"failure"} | ${"elementID3"}  | ${"failed import"}
`(`Should show correct ($errorObject) on error`,
   ({ id, message }) => {
      fireEvent.click(testRender.getByTestID(id));
      expect(testRender.getByTestID("message")).toEqual(message);
   }
);

Асинхронный

Не все тесты должны быть синхронными. Используя async, мы можем дождаться желаемых результатов для таких вещей, как вызовы API или изменения состояния.

test('the data is peanut butter', async () => {
   const data = await fetchData();
   expect(data).toBe('peanut butter');
});

Дополнительная информация: https://jestjs.io/docs/en/asynchronous.html