Понимание v-model механизма

v-model в Vue представляет собой синтаксический сахар для двусторонней привязки данных между родительским компонентом и дочерним компонентом. В тестировании компонентов с использованием Vue Test Utils важно понимать, как эта привязка работает под капотом, чтобы корректно имитировать пользовательские события и проверять обновления состояния.


Основы v-model

В Vue 3 v-model работает через два ключевых элемента:

  1. Пропс (prop) — значение, которое передается в дочерний компонент.
  2. Событие (emit) — событие, которое сообщает родителю об изменении значения.

По умолчанию:

  • Пропс называется modelValue.
  • Событие называется update:modelValue.
// Дочерний компонент
export default {
  props: {
    modelValue: String
  },
  emits: ['update:modelValue'],
  methods: {
    updateValue(newValue) {
      this.$emit('update:modelValue', newValue);
    }
  }
}

Использование v-model в родительском компоненте:

<ChildComponent v-model="text" />

Это эквивалентно:

<ChildComponent :modelValue="text" @update:modelValue="text = $event" />

Тестирование v-model с Vue Test Utils

Монтирование компонента

Vue Test Utils предоставляет функцию mount для создания экземпляра компонента в тестовой среде. Чтобы проверить v-model, нужно передать значение через пропс и отследить событие update:modelValue.

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

test('должен корректно работать v-model', async () => {
  const wrapper = mount(ChildComponent, {
    props: {
      modelValue: 'initial'
    }
  });

  // Проверка начального значения
  expect(wrapper.props('modelValue')).toBe('initial');
  
  // Эмуляция изменения значения
  await wrapper.vm.updateValue('updated');

  // Проверка эмита события
  expect(wrapper.emitted()['update:modelValue']).toBeTruthy();
  expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['updated']);
});

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

  • wrapper.props() возвращает текущие пропсы.
  • wrapper.vm.$emit() позволяет вручную вызвать событие.
  • wrapper.emitted() хранит все события, которые были вызваны компонентом.

Симуляция пользовательского ввода

В реальном приложении v-model чаще всего обновляется через элементы формы, такие как <input> или <textarea>. Vue Test Utils позволяет симулировать эти события:

const wrapper = mount(ChildComponent, {
  props: { modelValue: '' }
});

const input = wrapper.find('input');
await input.setValue('новое значение');

expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['новое значение']);

Особенности:

  • setValue автоматически вызывает событие input и обновляет привязку v-model.
  • Если компонент оборачивает <input> в другой метод, важно вызывать метод updateValue через vm или симулировать кастомное событие.

Тестирование кастомных v-model имен

Vue 3 позволяет создавать кастомные модификаторы v-model с собственными именами:

<ChildComponent v-model:title="pageTitle" />

В компоненте:

props: {
  title: String
},
emits: ['update:title']

Тестирование аналогично стандартному v-model, только нужно учитывать кастомное имя:

await wrapper.vm.$emit('update:title', 'Новый заголовок');
expect(wrapper.emitted()['update:title'][0]).toEqual(['Новый заголовок']);

Проверка реактивности

Важно тестировать, как изменения пропсов родителя влияют на компонент с v-model:

const wrapper = mount(ChildComponent, {
  props: { modelValue: 'initial' }
});

await wrapper.setProps({ modelValue: 'changed' });
expect(wrapper.props('modelValue')).toBe('changed');

Особенности реактивности:

  • setProps обновляет пропсы и вызывает рендер компонента заново.
  • Событие update:modelValue не вызывается при изменении пропса извне — оно вызывается только внутри компонента.

Частые ошибки при тестировании v-model

  1. Прямое изменение пропса: wrapper.props().modelValue = 'новое' не сработает, так как props в Vue readonly. Использовать нужно setProps.

  2. Игнорирование эмита события: Многие тесты проверяют только внутреннее состояние, не учитывая, что родитель получает изменения через update:modelValue.

  3. Несоответствие имен: Кастомные v-model требуют точного соответствия имен событий и пропсов.

  4. Асинхронность: Вызовы методов и симуляция событий часто асинхронны, поэтому важно использовать await для setValue, setProps и vm методов.


Интеграционное тестирование v-model

В сложных компонентах v-model часто комбинируется с computed свойствами или watch. В этом случае тестирование включает:

  • Проверку эмита событий при изменении внутренних данных.
  • Проверку реактивного обновления пропсов от родителя.
  • Проверку side-effect функций, которые срабатывают при обновлении модели.
const wrapper = mount(ParentComponent);

const input = wrapper.findComponent(ChildComponent).find('input');
await input.setValue('новое');

expect(wrapper.vm.parentValue).toBe('новое');

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


Механизм v-model в Vue Test Utils становится прозрачным, когда осознаются два ключевых аспекта: пропсы + эмит события. Именно через правильное управление ими обеспечивается корректная проверка двусторонней привязки в любых компонентах.