Обработка ошибок с errorCaptured

Vue предоставляет механизм глобальной и локальной обработки ошибок через хуки жизненного цикла компонентов, одним из которых является errorCaptured. Этот хук позволяет перехватывать ошибки, возникающие в дочерних компонентах, предотвращая их всплытие до глобального обработчика. В контексте тестирования компонентов с использованием Vue Test Utils, errorCaptured становится мощным инструментом для проверки устойчивости компонентов к ошибкам.


Принцип работы errorCaptured

Хук errorCaptured вызывается автоматически при возникновении ошибки в любом дочернем компоненте. Он принимает три аргумента:

  1. err — объект ошибки, выброшенной внутри дочернего компонента.
  2. vm — экземпляр компонента, в котором произошла ошибка.
  3. info — строка, описывающая фазу жизненного цикла, в которой возникла ошибка.

Возвращаемое значение хука определяет дальнейшую судьбу ошибки:

  • true — ошибка считается обработанной, дальнейшее всплытие прекращается.
  • false или отсутствие возврата — ошибка продолжает всплывать и может быть поймана глобальным обработчиком Vue.

Пример использования внутри компонента:

export default {
  name: 'ParentComponent',
  errorCaptured(err, vm, info) {
    console.error('Ошибка поймана в ParentComponent:', err.message, info);
    return true; // предотвращает дальнейшее всплытие
  }
}

Тестирование перехвата ошибок с Vue Test Utils

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

  1. Монтирование родительского компонента с хуком errorCaptured.
  2. Создание дочернего компонента, который намеренно выбрасывает ошибку.
  3. Проверка, что хук errorCaptured был вызван и корректно обработал ошибку.

Пример теста с использованием Jest:

import { mount } from '@vue/test-utils';
import ParentComponent from '@/components/ParentComponent.vue';
import ChildComponent from '@/components/ChildComponent.vue';

const ErrorThrowingChild = {
  template: '<div></div>',
  mounted() {
    throw new Error('Test error');
  }
};

test('errorCaptured перехватывает ошибку дочернего компонента', () => {
  const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
  
  const wrapper = mount(ParentComponent, {
    global: {
      components: {
        ChildComponent: ErrorThrowingChild
      }
    }
  });

  expect(consoleSpy).toHaveBeenCalledWith(
    expect.stringContaining('Ошибка поймана в ParentComponent:'),
    expect.stringContaining('Test error'),
    expect.any(String)
  );

  consoleSpy.mockRestore();
});

В данном примере:

  • Используется заглушка ErrorThrowingChild, выбрасывающая ошибку при монтировании.
  • С помощью jest.spyOn контролируется вызов console.error, что позволяет убедиться, что ошибка была перехвачена.
  • Хук errorCaptured предотвращает глобальный сбой теста, возвращая true.

Обработка асинхронных ошибок

Ошибки в асинхронных методах, таких как mounted с async/await, также можно перехватывать через errorCaptured. Для корректного тестирования таких случаев необходимо использовать flushPromises для ожидания завершения всех промисов.

Пример:

import { mount, flushPromises } from '@vue/test-utils';

const AsyncChild = {
  template: '<div></div>',
  async mounted() {
    await Promise.reject(new Error('Async error'));
  }
};

test('errorCaptured перехватывает асинхронные ошибки', async () => {
  const wrapper = mount(ParentComponent, {
    global: {
      components: { AsyncChild }
    }
  });

  await flushPromises();

  expect(wrapper.vm.errorHandled).toBe(true); // предполагается, что родитель устанавливает флаг
});

Совмещение с глобальными обработчиками ошибок

Vue позволяет задавать глобальный обработчик ошибок через app.config.errorHandler. В тестах важно различать локальный и глобальный обработчик:

  • Локальный errorCaptured вызывается первым.
  • Если локальный хук возвращает true, глобальный обработчик не срабатывает.
  • Если локальный хук возвращает false или отсутствует, ошибка передается глобальному обработчику.

Это поведение позволяет в тестах проверять как локальную обработку ошибок, так и взаимодействие с глобальной системой логирования или уведомлений.


Практические рекомендации

  • Для компонентов с критически важной функциональностью обязательно добавлять errorCaptured, чтобы предотвращать падение всего приложения при ошибках дочерних компонентов.
  • В тестах удобно использовать искусственно генерируемые ошибки для проверки логики перехвата.
  • Проверка аргументов err, vm и info позволяет убедиться, что ошибка обработана корректно и информация о ней сохраняется.
  • Асинхронные ошибки требуют использования flushPromises или аналогичных механизмов ожидания завершения промисов.

Проверка поведения при возврате false

Тестирование случая, когда errorCaptured возвращает false, важно для проверки передачи ошибки глобальному обработчику:

test('ошибка продолжает всплывать при return false', () => {
  const globalHandler = jest.fn();
  const wrapper = mount(ParentComponent, {
    global: {
      config: {
        errorHandler: globalHandler
      },
      stubs: {
        ChildComponent: {
          template: '<div></div>',
          mounted() { throw new Error('Bubble error'); }
        }
      }
    }
  });

  wrapper.vm.errorCaptured = () => false;

  expect(globalHandler).toHaveBeenCalledWith(
    expect.any(Error),
    expect.any(Object),
    expect.any(String)
  );
});

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


Выводы о применении в тестах

Использование errorCaptured совместно с Vue Test Utils позволяет:

  • Проверять устойчивость компонентов к ошибкам.
  • Контролировать локальное и глобальное логирование ошибок.
  • Тестировать как синхронные, так и асинхронные ошибки.
  • Убедиться, что критические ошибки не приводят к падению всего приложения в реальном окружении.

Это делает errorCaptured ключевым инструментом при разработке надежных и тестируемых Vue-компонентов.