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

onUpdated — это один из жизненных циклов композиционного API во Vue 3, вызывающийся после того, как компонент и его шаблон были обновлены в результате изменения реактивных данных. Тестирование поведения, связанного с этим хуком, позволяет убедиться, что логика, завязанная на обновления DOM, работает корректно.


Принципы работы onUpdated

onUpdated вызывается каждый раз после обновления DOM, которое произошло вследствие изменения реактивного состояния. Важно понимать:

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

Подготовка тестовой среды

Для тестирования используется библиотека Vue Test Utils (VTU) в сочетании с Jest или Vitest. Основные шаги:

  1. Импорт Vue Test Utils:
import { mount } from '@vue/test-utils'
import { nextTick, defineComponent, ref, onUpdated } from 'vue'
  1. Создание компонента с хуком onUpdated:
const TestComponent = defineComponent({
  setup() {
    const count = ref(0)
    const updatedCalls = ref(0)

    onUpdated(() => {
      updatedCalls.value += 1
    })

    const increment = () => {
      count.value += 1
    }

    return { count, updatedCalls, increment }
  },
  template: `
    <div>
      <span class="count">{{ count }}</span>
      <span class="updates">{{ updatedCalls }}</span>
      <button @click="increment">Increment</button>
    </div>
  `
})
  • count — реактивное состояние, изменение которого вызывает обновление.
  • updatedCalls — счётчик вызовов onUpdated, который используется для проверки работы хука.

Тестирование с помощью Vue Test Utils

  1. Проверка начального состояния:
test('initial state', () => {
  const wrapper = mount(TestComponent)
  expect(wrapper.find('.count').text()).toBe('0')
  expect(wrapper.find('.updates').text()).toBe('0')
})
  1. Проверка срабатывания onUpdated после изменения состояния:
test('onUpdated is called after state change', async () => {
  const wrapper = mount(TestComponent)
  await wrapper.find('button').trigger('click')
  
  await nextTick() // Ждем следующего рендера
  
  expect(wrapper.find('.count').text()).toBe('1')
  expect(wrapper.find('.updates').text()).toBe('1')
})

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

  • await nextTick() необходим для ожидания завершения обновления DOM.
  • Счётчик updatedCalls отражает точное количество вызовов onUpdated.
  1. Проверка множественных обновлений:
test('onUpdated is called multiple times', async () => {
  const wrapper = mount(TestComponent)

  await wrapper.find('button').trigger('click')
  await nextTick()
  
  await wrapper.find('button').trigger('click')
  await nextTick()

  expect(wrapper.find('.count').text()).toBe('2')
  expect(wrapper.find('.updates').text()).toBe('2')
})

Тестирование побочных эффектов DOM

onUpdated часто используется для операций с DOM, например, изменение стилей:

const DomComponent = defineComponent({
  setup() {
    const text = ref('Hello')
    const highlight = ref(false)

    onUpdated(() => {
      if (highlight.value) {
        document.querySelector('#message').classList.add('highlight')
      }
    })

    const toggleHighlight = () => {
      highlight.value = !highlight.value
    }

    return { text, highlight, toggleHighlight }
  },
  template: `
    <div>
      <p id="message">{{ text }}</p>
      <button @click="toggleHighlight">Toggle Highlight</button>
    </div>
  `
})

Тестирование DOM-эффектов:

test('onUpdated applies class after state change', async () => {
  const wrapper = mount(DomComponent)
  const button = wrapper.find('button')
  
  await button.trigger('click')
  await nextTick()
  
  expect(wrapper.find('#message').classes()).toContain('highlight')
})

Рекомендации по написанию тестов с onUpdated

  1. Использовать nextTick() после любого изменения реактивного состояния.
  2. Изолировать тестируемый эффект, создавая отдельный компонент для конкретной логики.
  3. Не полагаться на асинхронные таймеры, лучше использовать реактивность и nextTick.
  4. Счётчики вызовов и реактивные флаги помогают точно отслеживать, сколько раз срабатывал хук.

Особенности тестирования сложных сценариев

  • Если обновлений несколько и они зависят друг от друга, можно использовать последовательность await nextTick() после каждого изменения.
  • В компонентах с v-for и условным рендерингом важно проверять, что обновления DOM отражают именно актуальные элементы.
  • Для сторонних библиотек или плагинов, которые взаимодействуют с DOM в onUpdated, можно использовать моки и спаи для проверки вызовов методов.

onUpdated позволяет тестировать точку, когда компонент полностью обновлён. Комбинация Vue Test Utils и nextTick делает это надёжным и предсказуемым процессом, позволяя уверенно проверять реактивные обновления и побочные эффекты на DOM.