Тестирование onUnmounted

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


Основы использования onUnmounted

onUnmounted импортируется из vue и регистрируется внутри функции setup компонента:

import { ref, onUnmounted } from 'vue';

export default {
  setup() {
    const intervalId = ref(null);

    intervalId.value = setInterval(() => {
      console.log('tick');
    }, 1000);

    onUnmounted(() => {
      clearInterval(intervalId.value);
    });

    return { intervalId };
  },
};

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


Подготовка тестов с Vue Test Utils

Vue Test Utils предоставляет методы mount и shallowMount для создания экземпляров компонентов. Для тестирования onUnmounted используется метод unmount, который симулирует размонтирование компонента и запускает все соответствующие хуки жизненного цикла.

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

jest.useFakeTimers();

describe('ComponentWithUnmount', () => {
  it('вызывает очистку интервала при размонтировании', () => {
    const wrapper = mount(ComponentWithUnmount);
    const intervalId = wrapper.vm.intervalId;

    wrapper.unmount();

    expect(clearInterval).toHaveBeenCalledWith(intervalId);
  });
});

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

  • wrapper.unmount() гарантирует вызов onUnmounted.
  • Для таймеров или асинхронных процессов удобно использовать jest.useFakeTimers().
  • Методы mount и shallowMount поддерживают как обычные компоненты, так и компоненты с Composition API.

Тестирование очистки ресурсов

onUnmounted часто используется для отмены подписок на внешние события:

import { ref, onUnmounted } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const handle = setInterval(() => {
      count.value++;
    }, 1000);

    onUnmounted(() => {
      clearInterval(handle);
    });

    return { count };
  },
};

Тест для проверки корректной очистки:

it('очищает интервал при размонтировании', () => {
  const wrapper = mount(ComponentWithInterval);
  const intervalHandle = wrapper.vm.count;

  wrapper.unmount();

  // Проверка, что интервал остановлен
  jest.advanceTimersByTime(5000);
  expect(wrapper.vm.count).toBe(intervalHandle);
});

Здесь важно понимать, что после размонтирования компонент больше не обновляет состояние, а любые попытки взаимодействия с ref не должны изменять значения.


Работа с внешними эффектами

onUnmounted может использоваться для отмены подписок на события DOM или API. Например, слушатель событий:

import { onUnmounted } from 'vue';

export default {
  setup() {
    const handleClick = () => console.log('clicked');

    window.addEventListener('click', handleClick);

    onUnmounted(() => {
      window.removeEventListener('click', handleClick);
    });
  },
};

Тестирование с Vue Test Utils:

it('удаляет обработчик события при размонтировании', () => {
  const addSpy = jest.spyOn(window, 'addEventListener');
  const removeSpy = jest.spyOn(window, 'removeEventListener');

  const wrapper = mount(ComponentWithEvent);
  wrapper.unmount();

  expect(addSpy).toHaveBeenCalledWith('click', expect.any(Function));
  expect(removeSpy).toHaveBeenCalledWith('click', expect.any(Function));
});

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


Асинхронные операции внутри onUnmounted

В некоторых случаях нужно выполнять асинхронные действия, например отменять подписки на API или сетевые запросы:

import { onUnmounted } from 'vue';

export default {
  setup() {
    const controller = new AbortController();

    fetch('/api/data', { signal: controller.signal });

    onUnmounted(() => {
      controller.abort();
    });
  },
};

Тест с моками:

it('прерывает fetch при размонтировании', () => {
  const abortSpy = jest.spyOn(AbortController.prototype, 'abort');

  const wrapper = mount(ComponentWithFetch);
  wrapper.unmount();

  expect(abortSpy).toHaveBeenCalled();
});

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


Рекомендации по тестированию

  1. Всегда использовать unmount для проверки хуков размонтирования.
  2. Применять моки и шпионы (spy) для внешних эффектов.
  3. Использовать таймеры Jest для контроля интервалов и задержек.
  4. Разделять тесты на синхронные и асинхронные сценарии.
  5. Следить за тем, чтобы после размонтирования компонент не взаимодействовал с DOM или состоянием.

Эффективное тестирование onUnmounted обеспечивает стабильность приложения и предотвращает утечки ресурсов, особенно при работе с внешними сервисами, таймерами и подписками.