Мокирование API-вызовов в тестах Vue с помощью Vitest
Vue-компоненты редко существуют изолированно. Большинство из них получают данные, отправляют формы или опрашивают конечные точки. Когда вы тестируете такие компоненты без контроля над сетью, ваши тесты становятся медленными, нестабильными и зависимыми от внешних сервисов. Мокирование API-вызовов в тестах Vue помогает устранить эти проблемы, позволяя контролировать ответы, которые получают ваши компоненты.
В этой статье рассматриваются две практические стратегии мокирования API в Vitest для Vue: прямая замена модуля API с помощью vi.mock и перехват запросов на уровне HTTP с использованием Mock Service Worker (MSW). Вы также узнаете об особенностях обработки асинхронности в Vue, которые делают оба подхода надежными.
Ключевые выводы
- Реальные сетевые вызовы в тестах приводят к нестабильным, медленным, недетерминированным результатам — мокирование дает полный контроль над ответами.
vi.mockзаменяет модуль API на уровне импорта, что делает его идеальным для быстрых, изолированных unit-тестов.- MSW перехватывает запросы на сетевом уровне, позволяя вашей реальной логике fetch или axios выполняться для более реалистичных интеграционных тестов.
flushPromises()из@vue/test-utilsобычно необходим, когда компоненты ожидают завершения асинхронных запросов перед обновлением DOM.
Почему необходимо мокировать API-вызовы при тестировании Vue-компонентов с Vitest
Реальные сетевые вызовы в тестах производят недетерминированные результаты. Один и тот же тест может пройти локально и упасть в CI просто потому, что внешний API работает медленно или недоступен. Мокирование дает контроль над тем, что возвращает сеть, поэтому ваши тесты остаются быстрыми и предсказуемыми.
Стратегия 1: Мокирование модуля API с помощью vi.mock
Самый прямой подход — заменить модуль, ответственный за HTTP-вызовы, прежде чем ваш компонент его увидит.
// quote.service.ts
export async function fetchQuote() {
const response = await fetch('https://api.example.com/quotes/random')
return response.json()
}
// QuoteCard.test.ts
import { mount, flushPromises } from '@vue/test-utils'
import { vi, test, expect } from 'vitest'
import { fetchQuote } from './quote.service'
import QuoteCard from './QuoteCard.vue'
// vi.mock автоматически поднимается выше импортов
vi.mock('./quote.service')
test('displays a quote after mounting', async () => {
vi.mocked(fetchQuote).mockResolvedValue({
id: 1,
quote: 'Test quote',
author: 'Tester',
})
const wrapper = mount(QuoteCard)
// Ожидаем завершения всех промисов и обновлений DOM
await flushPromises()
expect(wrapper.text()).toContain('Test quote')
})
Один критический момент: vi.mock автоматически поднимается в начало файла Vitest, поэтому выполняется перед любыми импортами. Это намеренное поведение, задокументированное в API мокирования Vitest. Это означает, что вы можете безопасно импортировать реальную функцию и все равно получить замокированную версию внутри вашего теста.
Вызов flushPromises() часто требуется в этом паттерне. Vue-компоненты, которые получают данные при монтировании, обновляют свой DOM асинхронно. Утилиты вроде flushPromises гарантируют, что ожидающие промисы разрешатся перед выполнением проверок, предотвращая ситуации, когда тесты проверяют состояние загрузки.
Когда использовать этот подход: Unit-тестирование конкретного компонента или сервиса в изоляции, где вам нужен строгий контроль над возвращаемыми значениями и количеством вызовов.
Discover how at OpenReplay.com.
Стратегия 2: MSW с тестами Vue в Vitest для мокирования на уровне HTTP
Mock Service Worker перехватывает запросы на сетевом уровне, а не заменяет JavaScript-модули. Это делает ваши тесты более реалистичными, потому что реальный вызов fetch или axios все еще выполняется — MSW просто перехватывает его до того, как он покинет процесс.
Установите MSW и настройте его для Node-окружения (по умолчанию для Vitest):
npm install -D msw
// test/server.ts
import { setupServer } from 'msw/node'
import { http, HttpResponse } from 'msw'
export const server = setupServer(
http.get('https://api.example.com/quotes/random', () => {
return HttpResponse.json({ id: 1, quote: 'MSW quote', author: 'MSW' })
})
)
// QuoteCard.test.ts
import { mount, flushPromises } from '@vue/test-utils'
import { test, expect, beforeAll, afterEach, afterAll } from 'vitest'
import { server } from './test/server'
import QuoteCard from './QuoteCard.vue'
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
test('displays a quote fetched via MSW', async () => {
const wrapper = mount(QuoteCard)
await flushPromises()
expect(wrapper.text()).toContain('MSW quote')
})
flushPromises() может все еще понадобиться здесь по той же причине — системе реактивности Vue нужен тик для применения обновлений DOM после разрешения промиса.
Когда использовать этот подход: Интеграционные тесты, где вы хотите проверить, что ваш компонент работает с реалистичным HTTP-поведением, или когда несколько компонентов используют одну и ту же конечную точку. Собственная документация Vitest рекомендует MSW для мокирования на уровне запросов в тестовых окружениях на базе Node.
Выбор между vi.mock и MSW
vi.mock | MSW | |
|---|---|---|
| Перехватывает на уровне | Модуля | Сети |
| Тестирует реальную логику fetch | ❌ | ✅ |
| Контроль для каждого теста | ✅ Легко | ✅ Через server.use() |
| Лучше всего подходит для | Unit-тестов | Интеграционных тестов |
Заключение
Обе стратегии являются валидными инструментами для мокирования API-вызовов в тестах Vue. Используйте vi.mock, когда вам нужны быстрые, изолированные unit-тесты с точным контролем над возвращаемыми значениями. Используйте MSW, когда вы хотите, чтобы ваши тесты проверяли полный путь запроса. В любом случае утилиты вроде flushPromises() помогают гарантировать, что Vue завершит рендеринг асинхронных обновлений перед выполнением проверок.
Часто задаваемые вопросы
Да. Многие команды используют vi.mock для сфокусированных unit-тестов отдельных компонентов и MSW для более широких интеграционных тестов, охватывающих несколько компонентов или сервисов. Держите их в отдельных тестовых файлах, чтобы избежать путаницы относительно того, какой уровень обрабатывает мок.
Vue может все еще рендерить состояние загрузки, когда выполняется ваша проверка. Если ваша проверка случайно совпадает с текстом загрузки, тест проходит по совпадению. Предупреждение в консоли обычно сигнализирует о необработанных отклонениях промисов или отсутствующих await. Вызов flushPromises после монтирования компонентов, которые запускают асинхронные запросы, помогает избежать этих проблем с таймингом.
MSW перехватывает на сетевом уровне, поэтому работает с любым HTTP-клиентом, включая axios, fetch и библиотеки, построенные поверх них, такие как ky или got. Ваш выбор HTTP-клиента не влияет на то, как MSW обрабатывает перехват.
С vi.mock вызовите mockRejectedValue для симуляции выброшенной асинхронной ошибки. С MSW используйте server.use внутри конкретного теста для регистрации обработчика, который возвращает HttpResponse.json со статусом 4xx или 5xx. Оба подхода позволяют проверить обработку ошибок вашим компонентом.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.