Тестирование с fetch API

В современных приложениях Vue взаимодействие с сервером часто осуществляется через fetch API. Тестирование таких компонентов требует особого подхода, так как напрямую обращаться к реальному API в юнит-тестах нежелательно. Основная цель — изолировать компонент и проверять его поведение в ответ на разные сценарии запросов.


Монтирование компонента и подготовка окружения

Для начала используется функция mount из Vue Test Utils:

import { mount } from '@vue/test-utils'
import MyComponent from '@/components/MyComponent.vue'

const wrapper = mount(MyComponent)

Если компонент делает запрос к серверу при монтировании, важно замокать fetch. Обычно это делается через jest:

global.fetch = jest.fn()

Это позволяет полностью контролировать поведение fetch без реальных сетевых вызовов.


Замоканный fetch: успешный ответ

Чтобы протестировать успешный сценарий получения данных:

const mockData = { id: 1, name: 'Test Item' }

global.fetch.mockResolvedValueOnce({
  ok: true,
  json: async () => mockData
})

await wrapper.vm.loadData()  // метод, который вызывает fetch
await wrapper.vm.$nextTick()

expect(wrapper.vm.item).toEqual(mockData)
expect(wrapper.find('.item-name').text()).toBe('Test Item')

Ключевые моменты:

  • mockResolvedValueOnce позволяет задать конкретный ответ для одного вызова fetch.
  • Вызов await wrapper.vm.$nextTick() необходим для ожидания реактивного обновления DOM после изменения состояния компонента.
  • Проверка состояния компонента (wrapper.vm.item) и отображения в шаблоне (.item-name) гарантирует правильную интеграцию данных.

Замоканный fetch: обработка ошибок

Обработка ошибок сервера или сетевых сбоев также должна тестироваться:

global.fetch.mockRejectedValueOnce(new Error('Network Error'))

await wrapper.vm.loadData()
await wrapper.vm.$nextTick()

expect(wrapper.vm.error).toBe('Network Error')
expect(wrapper.find('.error-message').exists()).toBe(true)

Особое внимание уделяется:

  • Перехвату исключений при fetch.
  • Обновлению состояния ошибки в компоненте.
  • Проверке отображения ошибки в шаблоне.

Проверка нескольких последовательных вызовов fetch

Если компонент делает несколько последовательных запросов, jest позволяет задать их ответы по очереди:

global.fetch
  .mockResolvedValueOnce({ ok: true, json: async () => ({ id: 1 }) })
  .mockResolvedValueOnce({ ok: true, json: async () => ({ id: 2 }) })

await wrapper.vm.loadAllData()
await wrapper.vm.$nextTick()

expect(wrapper.vm.items).toHaveLength(2)

Это позволяет моделировать сценарии последовательного получения данных без обращения к реальному API.


Асинхронные методы и таймеры

Иногда в компоненте используются setTimeout или setInterval для повторных запросов. В тестах нужно контролировать таймеры через Jest:

jest.useFakeTimers()

wrapper.vm.pollData()
jest.advanceTimersByTime(5000)

await wrapper.vm.$nextTick()
expect(wrapper.vm.data).toBeDefined()

jest.useRealTimers()

Преимущество — тест становится предсказуемым и быстрым, без реального ожидания времени.


Проверка вызовов fetch с конкретными параметрами

Важно убедиться, что fetch вызывается с правильным URL, методами и заголовками:

global.fetch.mockResolvedValueOnce({
  ok: true,
  json: async () => ({ success: true })
})

await wrapper.vm.sendData({ name: 'Item' })

expect(global.fetch).toHaveBeenCalledWith('/api/items', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'Item' })
})

Ключевой момент: тестирование параметров запроса обеспечивает правильную интеграцию компонента с API.


Использование утилит для упрощения моков

Для крупных проектов удобно создавать отдельные утилиты для моков fetch:

export function mockFetchSuccess(data) {
  global.fetch = jest.fn().mockResolvedValue({
    ok: true,
    json: async () => data
  })
}

export function mockFetchFailure(errorMessage) {
  global.fetch = jest.fn().mockRejectedValue(new Error(errorMessage))
}

Это уменьшает повторяемость кода и повышает читаемость тестов.


Подытоживание методологии

При тестировании компонентов Vue с fetch API:

  1. Всегда мокать fetch, чтобы избежать реальных запросов.
  2. Разделять сценарии на успешные ответы и ошибки.
  3. Использовать await wrapper.vm.$nextTick() для ожидания обновлений DOM.
  4. Проверять как внутреннее состояние компонента (data, computed), так и отображение в шаблоне.
  5. Контролировать таймеры для повторных или отложенных вызовов fetch.
  6. В крупных проектах выносить моки в утилиты для повторного использования.

Такой подход обеспечивает надежные, изолированные и предсказуемые тесты компонентов Vue, работающих с fetch API.