Vue 3 представил компонент Suspense,
который позволяет организовывать асинхронную загрузку данных и
отображение fallback-контента до завершения этих операций. При
тестировании таких компонентов важно учитывать асинхронную природу их
рендеринга, правильную обработку Promise и проверку
состояний до и после завершения асинхронной операции.
Suspense используется для оборачивания компонентов,
которые возвращают Promise или используют
async setup. Пример простого компонента:
import { defineComponent, ref, onMounted } from 'vue';
export default defineComponent({
setup() {
const data = ref(null);
const fetchData = async () => {
const result = await new Promise(resolve => setTimeout(() => resolve('Loaded'), 1000));
data.value = result;
};
onMounted(fetchData);
return { data };
},
template: `<div>{{ data ?? 'Loading...' }}</div>`
});
При использовании Suspense:
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Загрузка...</div>
</template>
</Suspense>
До завершения асинхронной операции будет отображаться fallback-контент, после чего рендерится основной компонент.
В Vue Test Utils (VTU) для тестирования
Suspense компонентов используется метод mount
или shallowMount с асинхронным ожиданием рендера. Ключевой
момент — правильно обрабатывать асинхронный рендеринг:
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import AsyncComponent from './AsyncComponent.vue';
test('Suspense показывает fallback и основной контент', async () => {
const wrapper = mount({
template: `
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div class="fallback">Загрузка...</div>
</template>
</Suspense>
`
});
// Проверка, что изначально отображается fallback
expect(wrapper.html()).toContain('Загрузка...');
// Ожидание завершения всех асинхронных операций
await nextTick();
await new Promise(resolve => setTimeout(resolve, 1000));
await nextTick();
// Проверка, что основной компонент рендерится
expect(wrapper.html()).toContain('Loaded');
});
Ключевые моменты:
nextTick() позволяет дождаться завершения текущего
цикла реактивности.Promise или flushPromises.flushPromisesДля удобного тестирования асинхронного кода часто применяется утилита
flush-promises. Она позволяет дождаться завершения всех
промисов без использования setTimeout:
import flushPromises from 'flush-promises';
test('Suspense с flushPromises', async () => {
const wrapper = mount({
template: `
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div class="fallback">Загрузка...</div>
</template>
</Suspense>
`
});
expect(wrapper.html()).toContain('Загрузка...');
await flushPromises();
await nextTick();
expect(wrapper.html()).toContain('Loaded');
});
Использование flushPromises делает код тестов более
читаемым и надежным при множественных асинхронных вызовах.
Suspense поддерживает обработку ошибок через
errorCaptured или глобальные error-handlers. Для проверки
ошибок в VTU:
import { defineComponent, h } from 'vue';
const FailingComponent = defineComponent({
async setup() {
throw new Error('Ошибка загрузки');
},
render() {
return h('div', 'Этот текст не рендерится');
}
});
test('Suspense обрабатывает ошибки', async () => {
const wrapper = mount({
template: `
<Suspense>
<template #default>
<FailingComponent />
</template>
<template #fallback>
<div class="fallback">Загрузка...</div>
</template>
</Suspense>
`
});
await flushPromises();
await nextTick();
// Ошибки нужно ловить через errorCaptured или глобальный обработчик
const error = wrapper.vm.$.appContext.config.errorHandler;
expect(error).toBeDefined();
});
flushPromises для
ожидания всех асинхронных операций.nextTick() после промисов,
чтобы обновления реактивности точно отобразились.setTimeout для ожидания
рендера, если возможно — использовать
flushPromises, это делает тесты стабильнее.Для Suspense компонентов можно использовать стандартные VTU методы:
expect(wrapper.find('.fallback').exists()).toBe(true);
expect(wrapper.text()).toContain('Loaded');
Дополнительно можно проверять динамическое изменение состояния через реактивные свойства:
const dataWrapper = wrapper.findComponent(AsyncComponent);
expect(dataWrapper.vm.data).toBe('Loaded');
При работе с Jest важно помнить:
jest.useFakeTimers()) могут влиять
на асинхронные промисы.async и
использовать await.flushPromises +
nextTick для корректного рендера.Использование Vue Test Utils с Suspense позволяет создавать надежные и детализированные тесты, проверяя не только конечный результат, но и процесс асинхронной загрузки, отображение fallback-контента и обработку ошибок. Правильная организация ожиданий и применение утилит для промисов делают тесты стабильными и воспроизводимыми.