Тестирование является сложным делом. Тестирование приложений с полным набором еще сложнее. Вы должны иметь дело с фронт-ендом, бак-ендом, базой данных, сетью и многое другое. Во-первых, конечно, вы единолично тестируете свои компоненты, функции и модули в изоляции. Затем вы пишете тесты интеграции, чтобы убедиться, что они хорошо играют вместе. Вы можете даже добавить несколько тестов от конца до конца для всего приложения, чтобы имитировать реальные взаимодействия пользователей. Но затем возникает фактор хаоса: что происходит, когда все идет не так? Что происходит, когда сеть медленна или ненадежна? Что происходит, когда бак-энд не работает? Приложение, которое работает идеально на счастливом пути, все равно может легко сломаться, когда происходит что-то неожиданное.Невозможно предсказать все способы, по которым система может потерпеть неудачу, особенно когда несколько компонентов взаимодействуют сложными способами, но мы можем подготовиться к неудаче, проверив, как наше приложение ведет себя в неблагоприятных условиях. Тестирование на основе хаоса - это подход, который охватывает эту неопределенность, намеренно вводя неудачи в ваши тесты.В этой статье мы рассмотрим, как реализовать тестирование на основе хаоса в приложении Next.js, используя тесты интеграции, которые намеренно нарушают вещи. Апп Ну, сначала нам понадобится приложение для тестирования. Чтобы сосредоточить статью, я создал минимальное полное приложение Next.js, поэтому вам не нужно. Приложение является простым приложением рецептов, где вы можете просматривать список рецептов, просматривать детали рецептов и подобные им. Он использует Tailwind CSS для стиля и TypeScript для безопасности типов. Когда вам нравится рецепт, подобный счет оптимистично обновляется на фронтене, в то время как бак-энд обрабатывает запрос. Если звонок бак-энда не удается, подобный счет возвращается к своему предыдущему состоянию. Если он удается, он возвращает новый подобный счет, но фронт-энд не обновляет его снова, чтобы избежать скачки чисел, если рецепт понравился другому пользователю в то время. Код доступен на . GitHub Проверьте репо, установите зависимости, и вы можете запустить его локально с: git clone cd article-chaos-fetch npm install npm run dev Открытый в вашем браузере. http://localhost:3000 Вы увидите что-то вроде этого: Бак-энд имеет три API маршрута: GET /api/posts — список всех рецептов GET /api/posts/[id] — получить детали рецепта POST /api/posts/[id]/like — increment likes — возвращает новый like count Обратите внимание, что подобные файлы хранятся в памяти и перезагружаются при перезагрузке сервера.Это только для демонстрационных целей; в реальном приложении вы бы использовали базу данных. Такая кнопка используется в Используя React и Он справляется с оптимистическим обновлением, обработкой ошибок и логикой обратной связи. Вероятно, не так, как вы или я бы реализовали его в реальном приложении, но это сделает для этой демонстрации. src/components/LikeButton.tsx useState useEffect Тестирование подразделений с помощью рабочего-служащего Mock Service Worker (MSW) Если вы хотите проверить свой компонент, есть несколько способов сделать это, но один из самых умных способов - использовать Таким образом, вы можете тестировать компонент в изоляции, не полагаясь на фактический backend. Служба Mock Service Worker (MSW) В о Branch, мы создали Как тест-бегун и Для рендеринга компонента и симуляции пользовательских взаимодействий.Мы также настроили MSW для перехвата сетевых запросов и возвращения поддельных ответов. main быстрее Реактивная библиотека тестов содержит единицы испытаний для компонент. Он тестирует следующие сценарии: src/components/LikeButton.test.tsx LikeButton Подобная кнопка отключается во время запроса и обновляется до значения резервного кода по успеху. Подобная кнопка отключается во время запроса и возвращается к ошибке бак-энда. Вы можете выполнить тесты с: npm run test Если мы посмотрим на тест-код, мы можем увидеть, как мы используем MSW, чтобы высмеивать ответы бак-энда. Например, в первом тесте мы пересчитываем высмеивание, чтобы вернуть успешный ответ с новым количеством 42: // test("like button disables during request and updates to backend value" ... server.use( http.post("/api/posts/:id/like", async () => { await new Promise((resolve) => setTimeout(resolve, 100)); return { likes: 42 }; }) ); ... Во втором тесте мы перегружаем выдумку, чтобы вернуть ответ на ошибку: test("like button disables during request and rolls back on backend error" ... server.use( http.post("/api/posts/:id/like", async () => { await new Promise((resolve) => setTimeout(resolve, 100)); return { status: 500 }; }) ); ... Что делает MSW, это перехватывает сетевые запросы, сделанные компонент и возвращает поддельные ответы, которые мы определили в тестах.Таким образом, мы можем проверить, как компонент ведет себя в разных условиях бак-энда, не полагаясь на фактический бак-энд. LikeButton Тесты на интеграцию Технически, мы могли бы изменить MSW, чтобы перенаправить запросы на фактический backend, но это было бы немного хакерским и не совсем тем, для чего MSW предназначен. или для запуска тестов от конца до конца и использования автономного прокси, например Или что-то более простое, как для симуляции сетевых условий - но это было бы немного переубийством для этого простого приложения. Плееры Кипр Токсикоз Хаосный прокси Вот где Это легкая библиотека, которая обволакивает местных жителей. API и позволяет вводить хаос в ваши сетевые запросы.Вы можете симулировать задержку, ошибки, ограничение скорости, шум и даже случайные сбои всего с несколькими строками кода. Хаос Фетч fetch Давайте используем Чтобы создать некоторые тесты интеграции.Если мы хотим проверить полную интеграцию между фронт-ендом и бак-ендом, нам нужно запустить тесты в реальной среде браузера. среда для этого, которая имитирует браузерную среду в Node.js. chaos-fetch jsdom To use `chaos-fetch`, we first need to install it: ```bash npm install @fetchkit/chaos-fetch --save-dev Первое, что мы можем сделать, в целях иллюстрации, это обменять MSW с Это не совсем то, для чего предназначена библиотека, но это работает. , мы заменяем настройку MSW с : chaos-fetch LikeButton.test,tsx chaos-fetch // src/components/LikeButton.test.tsx import { createClient, replaceGlobalFetch, restoreGlobalFetch, } from "@fetchkit/chaos-fetch"; ... describe("LikeButton", () => { afterEach(() => { restoreGlobalFetch(); }); test("like button disables during request and updates to backend value", async () => { // Mock fetch to return success const client = createClient( { global: [ { latency: { ms: 300 } }, ], routes: { "POST /api/posts/:id/like": [ { latency: { ms: 300 } }, { mock: { body: '{ "likes": 43 }' } }, ], }, }, window.fetch ); // Replace global fetch with mock client replaceGlobalFetch(client); // From here on, the test code remains the same ... Как видите, мы создаем клиент, который вместо получения возвращает некоторые поддельные данные и заменяет глобальную Работает с ним, в Hook, мы восстанавливаем оригинал Функция: Остальная часть тестового кода остается прежней. chaos-fetch fetch afterEach fetch Обратите внимание, что мы также добавили некоторую задержку для симуляции реального запроса сети. Компонент деактивирует кнопку во время запроса, и мы хотим проверить это поведение. Без него тест провалился бы, потому что запрос был бы завершен слишком быстро. (И это приводит нас в хрупкий, ненадежный мир тестирования на основе времени, но это тема для другой статьи.) LikeButton Код для второго теста похож; мы просто меняем выдумку, чтобы вернуть ошибку: test("like button disables during request and rolls back on backend error", async () => { // Mock fetch to return success const client = createClient( { global: [], routes: { "POST /api/posts/:id/like": [ { latency: { ms: 300 } }, { mock: { status: 500, body: '{ "error": "Internal Server Error" }' } }, ], }, }, window.fetch ); // Replace global fetch with mock client replaceGlobalFetch(client); ... Код находится на филиала репо. Вы можете проверить его с . tests-with-chaos-fetch git checkout tests-with-chaos-fetch Теперь мы можем выполнять тесты с: npm run test Теперь MSW все еще лучше подходит для единичных испытаний, потому что он предназначен для этой цели. Для этого тоже. chaos-fetch Тесты на интеграцию Chaos-Driven Если мы хотим выйти за рамки единичных испытаний и проверить полную интеграцию между фронт-ендом и бак-ендом, мы можем использовать чтобы ввести хаос в наши сетевые запросы.Таким образом, мы можем проверить, как приложение ведет себя в неблагоприятных условиях. chaos-fetch Во-первых, для тестов интеграции мы должны сделать небольшой рефактор: мы не можем непосредственно отображать компонент асинхронного сервера в наших тестах. Компонент, который содержит и детали рецепта. таким образом, мы можем проверить в наших интеграционных тестах. код для Он в . PostPage LikeButton LikeButton PostView src/components/PostView.tsx В нашем случае это целое приложение, что делает настройку забавной, потому что мы будем тестировать компонент против приложения, в котором он является частью, но вы можете настроить тесты интеграции таким же образом, если backend является отдельным сервисом. Те же тесты, которые мы проводили в Они переписываются в чтобы проверить полную интеграцию между фронт-ендом и бак-ендом.Код похож, но вместо того, чтобы шутить над ответами бак-енда, мы позволяем запросам перейти к фактическому бак-енду. Введение ошибок в запросы. LikeButton.test.tsx PostView.integration.test.tsx chaos-fetch Важным моментом является то, что мы должны установить для URL-объекта, так что может правильно решить относительные URL-адреса. В этой настройке, JSDOM с native fetch, даже не будет работать для относительных URL-адресов! Патчи JSDOM Чтобы это работало. globalThis.location chaos-fetch chaos-fetch location В первую очередь мы устанавливаем : globalThis.location globalThis.location = new URL("http://localhost:3000/posts/1"); Затем мы создаем Клиенты в тестах, которые выходят за пределы родного отбора, и мы можем вводить задержку, ошибки и многое другое. chaos-fetch Для первого теста нам даже не нужно изменять ; мы только перечеркиваем его, чтобы он работал с относительными URL-адресами: fetch test("integration: like button disables during request and re-enables after fetch (real backend)", async () => { replaceGlobalFetch(createClient({})); render(<PostView postId={1} />); // Wait for post to load (like count should be present) const likeCountText = await screen.findByText(/\d+\s*likes/); const initialCount = Number(likeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); const button = await screen.findByRole("button", { name: /like/i }); const user = userEvent.setup(); await user.click(button); // Button should be disabled during request expect(button).toBeDisabled(); // Wait for fetch to complete and UI to update await waitFor(() => expect(button).not.toBeDisabled()); // Check updated like count await waitFor(() => { const updatedLikeCountText = screen.getByText(/\d+\s*likes/); const updatedCount = Number(updatedLikeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); expect(updatedCount).toBe(initialCount + 1); }); restoreGlobalFetch(); }); Для второго теста мы создаем клиент, который имитирует ошибку бак-энда для аналогичного запроса: test("integration: like button disables during request and rolls back on backend error (fail middleware)", async () => { // Configure chaos-fetch to fail the like endpoint replaceGlobalFetch(createClient({ routes: { "POST /api/posts/:id/like": [ { latency: { ms: 300 } }, { fail: { status: 500, body: '{ "error": "fail middleware" }' } }, ], }, })); render(<PostView postId={1} />); // Wait for post to load (like count should be present) const likeCountText = await screen.findByText(/\d+\s*likes/); const initialCount = Number(likeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); const button = await screen.findByRole("button", { name: /like/i }); const user = userEvent.setup(); await user.click(button); // Button should be disabled during request expect(button).toBeDisabled(); // Wait for fetch to complete and UI to update await waitFor(() => expect(button).not.toBeDisabled()); // Check that like count rolls back to original value await waitFor(() => { const rolledBackLikeCountText = screen.getByText(/\d+\s*likes/); const rolledBackCount = Number(rolledBackLikeCountText.textContent.match(/(\d+)/)?.[1] ?? 0); expect(rolledBackCount).toBe(initialCount); }); restoreGlobalFetch(); }); Теперь, технически, во втором тесте мы вообще не называем бак-энд, потому что перехватывает запрос и возвращает ошибку, но дает единый интерфейс для обработки успешных и неудачных запросов одинаково. chaos-fetch Где этот подход действительно блестит, когда вы хотите симулировать более сложные условия сети. middleware. Или вы можете попробовать, что происходит, если ваш backend ограничивает вас Средневековый throttle rateLimit Другая вещь, которую трудно проверить, это то, показан ли задержанный поворотник загрузки во время полета запроса. middleware для симуляции медленной сети и тестирования того, что состояние загрузки отображается правильно. latency Если вы не заботитесь о детерминизме, вы можете даже добавить некоторые случайные сбои, чтобы увидеть, как ваше приложение ведет себя в непредсказуемых условиях. Заключение Тестирование хаоса часто ассоциируется с крупномасштабными распределенными системами, но оно одинаково важно для более мелких приложений.В этой статье мы исследовали легкие тесты на основе хаоса для приложений полного набора с использованием тестов интеграции, которые намеренно разрушают вещи. ввести хаос в наши сетевые запросы и проверить, как наше приложение ведет себя в неблагоприятных условиях. chaos-fetch не является лучшей (или худшей) заменой для MSW или конечных тестов, таких как Playwright или Cypress. Это дополнительный инструмент, который можно настроить и использовать рядом с ними (и Принимая хаос и тестируя, как ваше приложение ведет себя в условиях сбоя, вы можете создавать более устойчивые и надежные приложения. @fetchkit/chaos-fetch @fetchkit/chaos-proxy