Проверка изменения computed при изменении data

В Vue.js computed свойства представляют собой реактивные вычисления, которые автоматически обновляются при изменении зависимых данных (data или других реактивных источников). Тестирование computed требует проверки, что при изменении исходных данных значение вычисляемого свойства корректно пересчитывается. Vue Test Utils предоставляет инструменты для изоляции компонентов и проверки реактивного поведения.

Основы computed в Vue

computed свойства:

  • Кешируются до тех пор, пока не изменятся зависимости.
  • Реактивны, что позволяет автоматически обновлять интерфейс при изменении данных.
  • Могут использовать другие computed или props в качестве зависимостей.

Пример компонента:

<template>
  <div>{{ fullName }}</div>
</template>

<script>
export default {
  data() {
    return {
      firstName: 'Иван',
      lastName: 'Иванов'
    };
  },
  computed: {
    fullName() {
      return `${this.firstName} ${this.lastName}`;
    }
  }
};
</script>

Здесь fullName зависит от firstName и lastName. Любое их изменение должно автоматически обновлять fullName.

Подходы к тестированию computed

Основная задача — убедиться, что computed корректно реагирует на изменения данных. В Vue Test Utils доступны методы mount и shallowMount, которые позволяют получить экземпляр компонента (wrapper.vm) и изменять его реактивные свойства напрямую.

Пример теста с Jest:

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

describe('UserName.vue', () => {
  it('обновляет fullName при изменении firstName', async () => {
    const wrapper = mount(UserName);

    expect(wrapper.vm.fullName).toBe('Иван Иванов');

    wrapper.vm.firstName = 'Пётр';
    await wrapper.vm.$nextTick();

    expect(wrapper.vm.fullName).toBe('Пётр Иванов');
  });

  it('обновляет fullName при изменении lastName', async () => {
    const wrapper = mount(UserName);

    wrapper.vm.lastName = 'Петров';
    await wrapper.vm.$nextTick();

    expect(wrapper.vm.fullName).toBe('Иван Петров');
  });
});

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

  • Изменение данных: напрямую через wrapper.vm.
  • Ожидание реактивности: await wrapper.vm.$nextTick() гарантирует, что Vue завершил обновление вычисляемых свойств.
  • Проверка значения: через wrapper.vm.fullName.

Тестирование сложных computed с зависимостями

Если computed зависит от других computed, проверка аналогична, но нужно учитывать цепочку зависимостей. Например:

computed: {
  initials() {
    return `${this.firstName[0]}${this.lastName[0]}`;
  },
  fullNameWithInitials() {
    return `${this.fullName} (${this.initials})`;
  }
}

Тест:

it('обновляет fullNameWithInitials при изменении данных', async () => {
  const wrapper = mount(UserName);

  wrapper.vm.firstName = 'Пётр';
  wrapper.vm.lastName = 'Петров';
  await wrapper.vm.$nextTick();

  expect(wrapper.vm.fullNameWithInitials).toBe('Пётр Петров (ПП)');
});

Важность асинхронного ожидания

Vue использует виртуальный DOM и асинхронные обновления. Без await wrapper.vm.$nextTick() тест может проверять старое значение computed, поскольку обновление еще не произошло. В сложных компонентах с несколькими реактивными цепочками $nextTick() становится критически важным.

Тестирование через рендер

Кроме прямой проверки wrapper.vm, можно тестировать влияние computed на шаблон:

it('отображает обновлённый fullName в шаблоне', async () => {
  const wrapper = mount(UserName);

  wrapper.vm.firstName = 'Алексей';
  await wrapper.vm.$nextTick();

  expect(wrapper.text()).toContain('Алексей Иванов');
});

Такой подход проверяет не только реактивность, но и корректный рендер, что особенно важно для интеграционных тестов.

Рекомендации для сложных компонентов

  1. Разделение тестов: отдельные тесты для каждой зависимости computed.
  2. Использование фабрик данных: создание mount с разными начальными значениями для проверки граничных случаев.
  3. Проверка побочных эффектов: если computed используется в методах или watch, убедиться, что изменения вызывают ожидаемые действия.
  4. Масштабирование тестов: для массивов или объектов рекомендуется проверять реактивность через изменение элементов и свойств объектов, а не только примитивов.

Работа с объектами и массивами

Computed, зависящие от объектов или массивов, требуют особого внимания, так как Vue отслеживает реактивность на уровне свойств. Пример:

data() {
  return {
    items: [{ name: 'apple' }, { name: 'banana' }]
  };
},
computed: {
  itemNames() {
    return this.items.map(item => item.name).join(', ');
  }
}

Тест:

it('обновляет itemNames при добавлении элемента', async () => {
  const wrapper = mount(ComponentWithItems);

  wrapper.vm.items.push({ name: 'orange' });
  await wrapper.vm.$nextTick();

  expect(wrapper.vm.itemNames).toBe('apple, banana, orange');
});

Важно: изменения по ссылке требуют реактивных методов Vue (push, splice) или использования Vue.set в Vue 2.


Проверка computed через изменение data обеспечивает уверенность в правильной работе реактивной системы. Комбинация прямой проверки через wrapper.vm и проверки рендера шаблона покрывает как внутреннее состояние, так и пользовательский интерфейс.