Тестирование directive'ов

В Vue.js directive — это механизм для расширения HTML-атрибутов с пользовательской логикой. Они позволяют управлять поведением DOM-элементов напрямую. Стандартные директивы включают v-if, v-show, v-model, но часто используются пользовательские директивы для реализации специфического функционала. Тестирование таких директив важно для обеспечения корректной работы приложения и предотвращения регрессий.


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

Пользовательская директива представляет собой объект с хуками жизненного цикла:

const myDirective = {
  mounted(el, binding) {
    el.style.color = binding.value;
  },
  updated(el, binding) {
    el.style.color = binding.value;
  }
};
  • mounted вызывается при монтировании элемента.
  • updated вызывается при обновлении привязанных данных.
  • Дополнительно существуют beforeMount и beforeUpdate.

Тестирование сосредоточено на проверке того, как директива изменяет DOM при различных состояниях.


Подключение директивы в компоненте

Vue Test Utils позволяет легко интегрировать директивы в тестируемый компонент:

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

const TestComponent = {
  template: `<div v-my-directive="color"></div>`,
  props: ['color']
};

const wrapper = mount(TestComponent, {
  global: {
    directives: {
      myDirective
    }
  },
  props: {
    color: 'red'
  }
});
  • global.directives используется для регистрации директив в тестовой среде.
  • Передача пропсов позволяет проверять реакцию директивы на разные значения.

Проверка эффектов директив

После монтирования компонента важно убедиться, что директива корректно изменила DOM:

expect(wrapper.element.style.color).toBe('red');

Обновление пропсов позволяет протестировать реакцию updated:

await wrapper.setProps({ color: 'blue' });
expect(wrapper.element.style.color).toBe('blue');

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


Тестирование директив с аргументами и модификаторами

Директивы могут принимать аргументы и модификаторы:

const focusDirective = {
  mounted(el, binding) {
    if (binding.modifiers.autofocus) {
      el.focus();
    }
  }
};

Тестирование модификаторов включает проверку состояния элемента после монтирования:

const wrapper = mount({ template: `<input v-focus.autofocus />` }, {
  global: { directives: { focus: focusDirective } }
});

expect(document.activeElement).toBe(wrapper.element);

Для аргументов используется структура binding.arg, что позволяет динамически менять поведение:

const colorDirective = {
  mounted(el, binding) {
    el.style[binding.arg] = binding.value;
  }
};

const wrapper = mount({ template: `<div v-color:background="bgColor"></div>` }, {
  global: { directives: { color: colorDirective } },
  props: { bgColor: 'yellow' }
});

expect(wrapper.element.style.background).toBe('yellow');

Изоляция логики директив

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

import { nextTick } from 'vue';

test('директива изменяет текст', async () => {
  const el = document.createElement('div');
  myDirective.mounted(el, { value: 'Hello' });
  expect(el.textContent).toBe('Hello');

  myDirective.updated(el, { value: 'World' });
  await nextTick();
  expect(el.textContent).toBe('World');
});
  • Прямое тестирование позволяет изолировать директиву от компонентов.
  • Использование nextTick гарантирует, что все реактивные обновления завершены.

Особенности работы с асинхронными эффектами

Если директива выполняет асинхронные действия, например, работу с API или таймером, важно использовать async/await и Vue Test Utils методы flushPromises:

import flushPromises from 'flush-promises';

const asyncDirective = {
  async mounted(el, binding) {
    const result = await fetchData(binding.value);
    el.textContent = result;
  }
};

await flushPromises();
expect(wrapper.element.textContent).toBe('данные');
  • Асинхронные действия должны быть завершены перед проверкой DOM.
  • flushPromises гарантирует обработку всех промисов в тестовой среде.

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

  • Проверка всех хуков: mounted, updated, beforeMount, beforeUpdate, unmounted (если есть логика очистки).
  • Тестирование модификаторов и аргументов: отдельные случаи для каждого.
  • Реактивность: изменение пропсов, состояния Vuex или provide/inject.
  • Асинхронность: корректное завершение промисов и таймеров.
  • Изоляция: тестирование директив отдельно для уменьшения зависимости от компонентов.

Использование spies и моков

Vue Test Utils совместим с Jest, поэтому можно легко проверять вызовы методов или побочные эффекты:

const spy = jest.fn();
const directive = {
  mounted: spy
};

mount({ template: `<div v-directive />` }, {
  global: { directives: { directive } }
});

expect(spy).toHaveBeenCalled();
  • Полезно для сложных директив с вызовом внешних функций.
  • Позволяет отслеживать количество вызовов и передаваемые аргументы.

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