Ошибки при запросах

При тестировании компонентов Vue часто возникает необходимость проверять обработку ошибок при выполнении асинхронных запросов. Vue Test Utils предоставляет инструменты для эмуляции поведения компонентов в условиях, когда запросы к API завершаются с ошибкой. Основная цель таких тестов — убедиться, что компонент корректно реагирует на неудачные запросы, отображает сообщения об ошибке и не ломает пользовательский интерфейс.

Эмуляция ошибок с помощью jest.mock

Для симуляции ошибок чаще всего используется мокирование модулей, отвечающих за сетевые запросы. Например, если используется axios, можно создать мок:

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

jest.mock('axios');

test('отображает сообщение об ошибке при неудачном запросе', async () => {
  axios.get.mockRejectedValue(new Error('Network Error'));

  const wrapper = mount(MyComponent);
  await wrapper.vm.fetchData();

  expect(wrapper.text()).toContain('Ошибка загрузки данных');
});

В этом примере ключевой момент — использование mockRejectedValue, которое позволяет имитировать отклонение промиса и протестировать обработку ошибок внутри компонента.

Проверка состояния компонента после ошибки

После возникновения ошибки важно убедиться, что компонент находится в ожидаемом состоянии. Для этого проверяется:

  • наличие сообщения об ошибке;
  • сброс загрузочного состояния;
  • невозможность выполнения повторных действий до повторного запроса, если это предусмотрено логикой.
expect(wrapper.vm.isLoading).toBe(false);
expect(wrapper.find('.error-message').exists()).toBe(true);

Тестирование пользовательских уведомлений

Если компонент использует уведомления, такие как Toast или Snackbar, их также нужно проверять. Для этого можно мокировать соответствующий модуль или метод:

jest.mock('@/plugins/toast', () => ({
  showError: jest.fn(),
}));

import { showError } from '@/plugins/toast';

test('вызывает уведомление при ошибке', async () => {
  axios.get.mockRejectedValue(new Error('API Error'));

  const wrapper = mount(MyComponent);
  await wrapper.vm.fetchData();

  expect(showError).toHaveBeenCalledWith('Не удалось загрузить данные');
});

Асинхронные ошибки внутри watch и computed

Ошибки могут возникать не только при прямых вызовах API, но и внутри реактивных вычислений, завязанных на watch или computed. В таких случаях тест должен имитировать изменение данных и проверять обработку исключений:

test('обрабатывает ошибку при изменении свойства', async () => {
  const wrapper = mount(MyComponent);
  wrapper.setProps({ userId: 123 });

  await wrapper.vm.$nextTick();

  expect(wrapper.find('.error-message').text()).toBe('Ошибка при загрузке пользователя');
});

Использование flushPromises для корректного ожидания ошибок

Поскольку ошибки часто возникают асинхронно, стандартного await wrapper.vm.method() может быть недостаточно. Для гарантированного завершения всех промисов используют утилиту flush-promises:

import flushPromises from 'flush-promises';

axios.get.mockRejectedValue(new Error('Server Error'));
const wrapper = mount(MyComponent);
wrapper.vm.fetchData();
await flushPromises();

expect(wrapper.find('.error-message').exists()).toBe(true);

Это позволяет тесту корректно дождаться обработки всех асинхронных операций, прежде чем проверять DOM.

Разделение логики обработки ошибок и UI

Рекомендуется выносить обработку ошибок в отдельные методы или composables. Это упрощает тестирование, поскольку можно проверять логику отдельно от визуальной части:

test('метод handleError устанавливает сообщение и сбрасывает загрузку', () => {
  const wrapper = mount(MyComponent);
  wrapper.vm.handleError(new Error('Fail'));

  expect(wrapper.vm.error).toBe('Fail');
  expect(wrapper.vm.isLoading).toBe(false);
});

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

Комбинирование нескольких источников ошибок

В сложных компонентах ошибки могут возникать одновременно в нескольких местах: при загрузке данных, отправке форм или подписке на веб-сокеты. В этих случаях тест должен имитировать все источники ошибок и проверять, что состояние компонента остаётся предсказуемым.

axios.get.mockRejectedValueOnce(new Error('Load Error'));
axios.post.mockRejectedValueOnce(new Error('Submit Error'));

const wrapper = mount(MyComponent);
await wrapper.vm.fetchData();
await wrapper.vm.submitForm();

expect(wrapper.vm.error).toContain('Load Error');
expect(wrapper.vm.error).toContain('Submit Error');

Такой подход обеспечивает полное покрытие сценариев, когда один и тот же компонент может столкнуться с множественными сбоями одновременно.

Итоговое наблюдение

Ключ к надёжному тестированию ошибок — тщательная эмуляция всех асинхронных действий и проверка реакции компонента на каждый сценарий. Использование моков, flush-promises, изолированных методов обработки ошибок и проверка состояния UI обеспечивает стабильные и предсказуемые тесты даже в сложных компонентах Vue.