Vue test utils - тестируем (уровень junior+/ middle)

vue test utils

Задание 1: Тестирование Composition API с ref, computed, watch

<template>
  <div>
    <input v-model="inputValue" data-testid="input" />
    <p data-testid="computed">{{ upperCaseValue }}</p>
    <p data-testid="watch-triggered">{{ watchTriggered }}</p>
  </div>
</template>

<script setup>
import { ref, computed, watch } from 'vue'

const inputValue = ref('')
const upperCaseValue = computed(() => inputValue.value.toUpperCase())
const watchTriggered = ref(false)

watch(inputValue, (newValue) => {
  if (newValue.length > 5) {
    watchTriggered.value = true
  }
})
</script>

Задание:

  1. Проверить, что computed свойство upperCaseValue корректно отображает введённое значение в верхнем регистре.
  2. Проверить, что watch срабатывает и устанавливает watchTriggered в true, когда длина inputValue становится больше 5 символов.
  3. Проверить, что watch не срабатывает, если длина inputValue меньше или равна 5 символам.

Решение:

import { mount } from '@vue/test-utils';
import { it, describe, expect } from 'vitest';
import { nextTick } from 'vue'
import Test1 from '@/components/test1.vue'

describe('test1.vue', () => {
    it("computed свойство upperCaseValue", async () => {
        const wrapper = mount(Test1);

        const input = wrapper.get('[data-testid="input"]');
        await input.setValue('test');

        expect(wrapper.find('[data-testid="computed"]').text()).toBe('TEST');
    });

    it("тестируем watch > 5", async () => {
        const wrapper = mount(Test1);

        const input = wrapper.get('[data-testid="input"]');
        await input.setValue('tester>6');
        await nextTick();

        expect(wrapper.find('[data-testid="watch-triggered"]').text()).toBe('true');
    });

    it("тестируем watch < 5", async () => {
        const wrapper = mount(Test1);

        const input = wrapper.get('[data-testid="input"]');
        await input.setValue('teste');
        await nextTick();

        expect(wrapper.find('[data-testid="watch-triggered"]').text()).toBe('false');
    });
})


Задание 2: Тестирование async логики и v-model с defineProps / defineEmits

<template>
  <div>
    <input v-model="localValue" @blur="validate" data-testid="input" />
    <p v-if="error" data-testid="error">{{ error }}</p>
    <p v-else data-testid="success">OK</p>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

const props = defineProps({
  validateAsync: { type: Function, required: true }
})

const emit = defineEmits(['update:modelValue'])

const localValue = ref('')
const error = ref('')

// Синхронизируем локальный ref с внешним modelValue
watch(
  () => props.modelValue,
  (newValue) => {
    localValue.value = newValue
  },
  { immediate: true }
)

// Синхронизируем локальный ref обратно к внешнему modelValue
watch(localValue, (newValue) => {
  emit('update:modelValue', newValue)
})

async function validate() {
  if (!localValue.value) {
    error.value = 'Поле обязательно'
    return
  }
  try {
    error.value = await props.validateAsync(localValue.value)
  } catch (e) {
    error.value = 'Ошибка валидации'
  }
}
</script>

Задание:

Что протестировать:

  1. Проверить, что при потере фокуса (blur) вызывается переданная функция validateAsync с параметром из input.
  2. Проверить, что при validateAsync, возвращающей ошибку, она отображается в элементе с data-testid="error".
  3. Проверить, что при изменении localValue эмитится событие update:modelValue.

Решение:

import { mount } from '@vue/test-utils';
import { it, describe, expect, vi } from 'vitest';
import { nextTick } from 'vue'
import Test2 from '@/components/test2.vue'

describe('test2.vue', () => {
    it("вызывает validateAsync при blur", async () => {
        const validateAsyncMock = vi.fn(()=> Promise.resolve('') )
        const wrapper = mount(Test2,{
            props:{
                validateAsync:validateAsyncMock
            }
        });

        await wrapper.get(['[data-testid="input"]']).setValue('test');
        await wrapper.get(['[data-testid="input"]']).trigger('blur');

        expect(validateAsyncMock).toHaveBeenCalledWith('test');        
    });
    it("Мок ошибки", async () => {
        const validateAsyncMock = vi.fn(()=>Promise.resolve('Ошибка валидации'));
        const wrapper = mount(Test2,{
            props:{
                validateAsync:validateAsyncMock
            }
        });

        await wrapper.get(['[data-testid="input"]']).setValue('test');
        await wrapper.get(['[data-testid="input"]']).trigger('blur');
        await nextTick();

        expect(wrapper.find('[data-testid="error"]').text()).toBe('Ошибка валидации')

    });
})