Избегание тестирования деталей реализации

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

Принцип “черного ящика”

Тесты должны рассматриваться как взаимодействие с компонентом «черного ящика». Основные точки взаимодействия:

  • Пропсы: проверка того, как компонент реагирует на входные данные.
  • События: проверка эмита событий и реакций на них.
  • Отображение: проверка видимого контента и состояния DOM.
  • Публичные методы: тестирование через API компонента, а не через приватные функции.

Использование Vue Test Utils для поведения

Vue Test Utils предоставляет набор инструментов для работы с компонентами без необходимости обращаться к их внутренним данным. Основные подходы:

  1. Метод mount и shallowMount

    • mount создаёт полный рендер компонента вместе с дочерними компонентами.
    • shallowMount создаёт поверхностный рендер, заменяя дочерние компоненты заглушками, что снижает зависимость теста от внутренних компонентов. Пример:
    import { shallowMount } from '@vue/test-utils';
    import MyComponent from '@/components/MyComponent.vue';
    
    const wrapper = shallowMount(MyComponent, {
      props: { title: 'Тест' }
    });
    
    expect(wrapper.text()).toContain('Тест');

    Здесь тест проверяет поведение компонента через текст, а не через внутренние методы.

  2. Проверка DOM через селекторы и текст Вместо проверки состояния data или приватных методов, тесты должны фокусироваться на том, что пользователь видит:

    expect(wrapper.find('h1').text()).toBe('Тест');

    Такой подход устойчив к внутренним изменениям компонента.

  3. Эмит событий Вместо вызова внутренних методов напрямую, проверяется, что компонент корректно эмитирует события:

    wrapper.find('button').trigger('click');
    expect(wrapper.emitted()).toHaveProperty('submit');

    Это отражает реакцию компонента на действия пользователя.

Пропсы вместо прямого изменения состояния

Изменение состояния компонента через wrapper.setData или прямую модификацию реактивных переменных привязывает тест к реализации. Лучше передавать разные значения через пропсы и проверять результат:

await wrapper.setProps({ active: true });
expect(wrapper.classes()).toContain('is-active');

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

Моки и заглушки

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

const wrapper = shallowMount(ParentComponent, {
  stubs: ['ChildComponent']
});

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

Сценарии и поведение вместо структуры

  • Фокус на пользовательских сценариях: клик, ввод текста, переключение состояния.
  • Проверка реакций на события: эмиты, вызовы методов родителя через события.
  • Отображение контента: проверка текста, классов, атрибутов, наличия элементов в DOM.

Тестирование деталей реализации (например, приватных методов, внутреннего состояния, порядка вызова функций) делает код тестов ненадежным и сложным в поддержке. Любое изменение внутренней структуры, не влияющее на поведение, приводит к необходимости переписывать тесты. Следование принципу «черного ящика» сохраняет тесты стабильными и отражает реальное поведение приложения.

Практическое правило

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