[Перевод] Тестирование нескольких экземпляров одного и того же мок компонента
Привет, Хабр. Стартовал набор учащихся на курс «Автоматизация тестирования на JavaScript». В связи с этим приглашаем всех желающих посетить бесплатный демо-урок по теме: «Что нужно знать о JS тестировщику».
А сейчас делимся с вами продолжением серии полезных переводов.
Мок React компоненты с Testing Library (5 частей серии):
-
Проверка дочерних элементов передаваемых в мок React компонента
-
Тестирование нескольких экземпляров одного и того же мок компонента
-
Держаться подальше от неприятностей
Это четвертая часть серии по тестированию React с мок компонентами. Во второй части мы рассмотрели базовую форму мок компонентов. В третьей части мы добавили возможность передачи дочерних компонентов. Теперь мы рассмотрим самую сложную часть головоломки: работу с несколькими экземплярами одного и того же мока.
Все примеры кода для этого поста доступны в следующем обзоре.
dirv / мок-react-компоненты
Пример использования заглушки React компонентов.
Продолжим работу с новым компонентом TopFivePostsPage
, который, пожалуй, неудивительно отображает пять лучших постов.
import { PostContent } from "./PostContent"
export const TopFivePostsPage = () => (
<ol>
<PostContent id="top1" />
<PostContent id="top2" />
<PostContent id="top3" />
<PostContent id="top4" />
<PostContent id="top5" />
</ol>
);
Для тестирования мы используем queryAllByTestId
в сочетании с матчером toHaveLength
.
describe("BlogPage", () => {
it("renders five PostContent components", () => {
render(<TopFivePostsPage />)
expect(screen.queryAllByTestId("PostContent"))
.toHaveLength(5)
})
})
И для нашего второго теста, мы можем использовать пять операторов ожидания, каждый с различными значениями свойства.
it("constructs a PostContent for each top 5 entry", () => {
render(<TopFivePostsPage />)
expect(PostContent).toHaveBeenCalledWith(
{ id: "top1" }, expect.anything())
expect(PostContent).toHaveBeenCalledWith(
{ id: "top2" }, expect.anything())
expect(PostContent).toHaveBeenCalledWith(
{ id: "top3" }, expect.anything())
expect(PostContent).toHaveBeenCalledWith(
{ id: "top4" }, expect.anything())
expect(PostContent).toHaveBeenCalledWith(
{ id: "top5" }, expect.anything())
})
Но в этом есть что-то не совсем правильное. Мы не тестировали порядок рендеринга. Матчер ToHaveBeenCalledWith
не заботится о порядке.
Вместо этого мы можем использовать .mock.calls
.
it("renders PostContent items in the right order", () => {
render(<TopFivePostsPage />)
const postContentIds = PostContent.mock.calls.map(
args => args[0].id)
expect(postContentIds).toEqual([
"top1", "top2", "top3", "top4", "top5"
])
})
Если вы попробуете запустить его после первых двух тестов для TopFivePostsPage
, вы получите странную ошибку, что PostContent
на самом деле вызывался пятнадцать раз! Это из-за того, что нам нужно очистить мок между каждым тестом.
Мы делаем это, добавляя свойство clearMocks
в нашу конфигурацию Jest. Вот мой package.json
для сравнения.
"jest": {
"transform": {
"^.+\.jsx?$": "babel-jest"
},
"setupFilesAfterEnv": ["./jest.setup.js"],
"clearMocks": true
}
Обратите внимание, что последний тест, фактические делает предыдущий тест ненужным, так что вы можете безопасно удалить его.
Когда этого недостаточно: мок экземпляр идентификации
Иногда понадобится больше, чем это. Например, вам нужно проверить передаваемые дочерние элементы, и у вас также есть несколько экземпляров. В этом случае можно использовать один из свойств компонента, чтобы дать уникальный идентификатор теста для вашего экземпляра компонента.
jest.mock("../src/PostContent", () => ({
PostContent: jest.fn(({ children, id }) => (
<div data-testid={`PostContent-${id}`}>
{children}
</div>
))
}))
Лично мне это очень не нравится. Это сложно, и сложнее, чем мне кажется. Но это существует, и иногда ее нужно использовать.
Помните о том, что моки существуют для того, чтобы ускорить тестирование, а тестирование — для того, чтобы ускорить разработку. Когда моки становятся слишком сложными, приходится тратить больше времени на их прочтение и поддержание в рабочем состоянии, поэтому они замедляют вашу работу. Об этом я расскажу подробнее в следующей части.
Еще больше уроков
Что мы изучили?
-
Используйте
queryAllByTestId
при тестировании нескольких экземпляров мок компонента. -
Используйте
.mock.calls
для проверки упорядочения вызовов или для тестирования свойств рендера. -
Используйте настройку конфигурации Jest’s
clearMocks
, чтобы убедиться, что ваши заглушки очищены перед каждым тестом. -
Если все остальное не удается, вы можете использовать свойства в результатах рендеринга, чтобы дать уникальные значения
data-testid
для каждого экземпляра. -
Следите за тем, чтобы моки были максимально простыми!
Вот и все. В заключительной части мы рассмотрим, почему моки могут доставить вам неприятности, и как их избежать.
Держаться подальше от неприятностей
Это заключительная часть серии о мок React компонентах. Мы закончим ее с кратким описанием, а затем посмотрим на некоторые общие трудности, с которыми вы столкнетесь.
Все примеры кода для этого поста доступны в следующем обзоре.
dirv / мок-react-компоненты
Пример того, как использовать заглушку React компонентов.
Моки — чрезвычайно сложный тестовый раздел обеспечения. Вот почему некоторые преподаватели не используют его и не учат ему.
Но освоение моков даст вам дополнительное оружие для борьбы с неустойчивыми, затянутыми во времени тестами.
Так как же вы можете быть уверены, что вы будете в безопасности с моками? Просто: придерживайтесь шаблонов!
Если вы будете придерживаться шаблонов, которые я вам покажу в этой серии примеров, у вас не должно быть проблем.
Начните с базовой мок функции, которая генерирует a div
с прикрепленным data-testid
. Мы смотрели на это во второй части.
jest.mock("../src/PostContent", () => ({
PostContent: jest.fn(() => (
<div data-testid="PostContent" />
))
}))
Если понадобится, вы тоже можете сгенерировать дочерние элементы. Это было описано в третьей части.
jest.mock("../src/PostContent", () => ({
PostContent: jest.fn(({ children }) => (
<div data-testid="PostContent">{children}</div>
))
}))
Если вам действительно это нужно, вы можете использовать значение свойства, чтобы сделать уникальные data-testids
. Но это часто бывает ненужной сложностью. Это было в четвертой части.
jest.mock("../src/PostContent", () => ({
PostContent: jest.fn(({ children, id }) => <div data-testid={`PostContent-${id}`}>{children}</div>)
}))
Я не люблю давать советы, чего избегать: каждая техника имеет свое место. Но если бы я назвал хоть что-то, с чем надо быть осторожным, я бы сказал, что это будет создание фиктивных объектов и, в частности, использование функции Jest mockImplementation
.
Почему? Ну, основная причина использования фиктивных объектов и заглушек заключается в том, чтобы помочь построить независимые тестовые решения, которые не будут вас тормозить.
Важным способом сделать это является ограничение вашего кода небольшим количеством шаблонов. Это немного похоже на набор стандартов кодирования, но на более высоком уровне.
Когда вы начинаете строить фиктивные объекты и сложные мок реализации, вы отходите от этой цели, потому что теперь в ваших тестах есть логика: вы не можете смотреть на них и сразу же знать, как они работают. И любое изменение в коде готового программного продукта требует от вас переосмысления фиктивного кода реализации, прежде чем неизбежно изменять и его.
Что, если ни один из шаблонов не подходит для ваших тестовых решений?
Если вы застряли, первый вопрос, который вы должны задать себе: насколько тестируем мой код готового программного продукта?
Потому что не моки причиняют боль, а код готового программного продукта, который не структурирован для тестирования.
Улучшить тестируемость вашего кода
Проблема номер один, которую я вижу в кодовых базах React, это очень большие компоненты, которые выражают много разных идей. Часто новые функции просто наваливаются друг на друга, вместо того, чтобы тратить время на разделение абстракций или на поиск логической организационной структуры.
Так что хорошим началом будет разделение ваших больших компонентов.
Насколько велик размер? Размер файла часто является хорошей метрикой для использования: для меня подозрительным является нечто большее, чем 100 строк. И многие из моих компонентов имеют размер менее 10 строк!
Что, если не понятно, как разделить компонент? Начните с принципа единой ответственности: каждый компонент должен делать только одно и только одно.
Конечно, понятие одной «вещи» оставляет вам достаточно веревки, чтобы повеситься. Поиск элегантных «вещей» — это большая сложность в проектировании программного обеспечения.
Если вас интересует эта тема, то я бы посоветовал узнать о связях, сцеплении и сопряжении, все это относится к компонентам React, даже если вы не часто будете слышать, как о них говорят преподаватели React.
Куда дальше?
В этой серии я показал вам очень специфический способ тестирования React компонентов. Если вас интересует более подробная теория и история этих методик, тогда взгляните на мою книгу “Освоение разработки, основанной на тестировании React компонентов“. В ней не используется библиотека React Testing Library, а исследуется тестирование с первых принципов. Это позволит вам глубже понять, что такое успешное React тестирование.
Узнать подробнее о курсе «Автоматизация тестирования на JavaScript».
Записаться на открытый вебинар «Что нужно знать о JS тестировщику».