Методы с побочными эффектами

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

Определение побочного эффекта

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

  • Изменение data или props компонента.
  • Вызов глобальных функций или API.
  • Манипуляции с DOM вне виртуального дерева Vue.
  • Отправка событий через $emit или глобальные события.
  • Использование таймеров (setTimeout, setInterval) или асинхронных операций.

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

Тестирование изменения состояния компонента

Vue Test Utils позволяет контролировать внутреннее состояние компонента через wrapper.vm. Пример метода с побочным эффектом:

export default {
  data() {
    return { count: 0 };
  },
  methods: {
    increment() {
      this.count += 1;
    }
  }
}

Тестирование изменения состояния выполняется напрямую:

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

test('increment увеличивает count', () => {
  const wrapper = mount(Counter);
  wrapper.vm.increment();
  expect(wrapper.vm.count).toBe(1);
});

Ключевой момент: использование wrapper.vm позволяет обращаться к экземпляру компонента и проверять значения data без необходимости рендерить DOM.

Тестирование методов с $emit

Методы, вызывающие события, часто используются для уведомления родителя о состоянии компонента:

methods: {
  submitForm() {
    this.$emit('submit', { valid: true });
  }
}

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

test('submitForm вызывает событие submit', () => {
  const wrapper = mount(MyForm);
  wrapper.vm.submitForm();
  expect(wrapper.emitted('submit')).toBeTruthy();
  expect(wrapper.emitted('submit')[0]).toEqual([{ valid: true }]);
});

Vue Test Utils сохраняет историю вызовов событий, что позволяет проверять аргументы и количество вызовов.

Мокирование зависимостей и API

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

methods: {
  async fetchData() {
    const response = await this.$api.getData();
    this.items = response.data;
  }
}

Тестирование с моками:

test('fetchData обновляет items', async () => {
  const mockData = { data: [1, 2, 3] };
  const wrapper = mount(MyComponent, {
    global: {
      mocks: {
        $api: {
          getData: jest.fn().mockResolvedValue(mockData)
        }
      }
    }
  });

  await wrapper.vm.fetchData();
  expect(wrapper.vm.items).toEqual([1, 2, 3]);
  expect(wrapper.vm.$api.getData).toHaveBeenCalled();
});

Использование global.mocks позволяет заменять глобальные зависимости и отслеживать их вызовы, не затрагивая реальные сервисы.

Контроль асинхронных методов

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

methods: {
  async delayedIncrement() {
    await new Promise(resolve => setTimeout(resolve, 100));
    this.count += 1;
  }
}

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

test('delayedIncrement увеличивает count асинхронно', async () => {
  jest.useFakeTimers();
  const wrapper = mount(Counter);
  wrapper.vm.delayedIncrement();
  jest.advanceTimersByTime(100);
  await wrapper.vm.$nextTick();
  expect(wrapper.vm.count).toBe(1);
  jest.useRealTimers();
});

Использование таймеров и $nextTick() гарантирует, что побочные эффекты завершились до проверки состояния.

Методы с побочными эффектами в жизненном цикле

Методы, вызываемые в хуках жизненного цикла (mounted, created), требуют отдельного подхода. Vue Test Utils позволяет изолировать вызовы через shallowMount и мокирование зависимостей:

mounted() {
  this.loadInitialData();
}

Тестирование с моками:

test('loadInitialData вызывается при монтировании', () => {
  const loadSpy = jest.fn();
  shallowMount(MyComponent, {
    methods: { loadInitialData: loadSpy }
  });
  expect(loadSpy).toHaveBeenCalled();
});

shallowMount предотвращает рендеринг дочерних компонентов, что снижает влияние побочных эффектов и ускоряет тесты.

Практика разделения логики

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

  1. Чистые функции, возвращающие результат без изменений состояния.
  2. Обертки, вызывающие чистые функции и изменяющие data, вызывающие $emit или API.

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


Методы с побочными эффектами в Vue требуют внимательного подхода: использование wrapper.vm, $emit, глобальных моков и контроля асинхронности позволяет изолировать и проверять каждое действие, сохраняя тесты надежными и предсказуемыми.