Асинхронное поведение
Возможно, вы заметили некоторые другие части руководства используют await
, когда вызываются некоторые методы у wrapper
, такие как trigger
и setValue
. Что все это значит?
Возможно, вы знаете, Vue обновляется реактивно: когда ты меняешь значение, DOM автоматически изменяется, чтобы отразить последнее изменение. Vue делает эти обновления асинхронно. В отличие от программ тестирования, например, Jest обновляется синхронно. Это может вызвать некоторые неожиданные результаты в тестах.
Давайте посмотрим на некоторые стратегии, чтобы убедиться, что Vue обновляет DOM, как ожидается, когда мы запускаем наши тесты.
Простой пример - Обновление при помощи trigger
Давайте используем компонент <Counter>
из обработка события с одним изменением; мы сейчас отобразим count
в template
.
const Counter = {
template: `
<p>Count: {{ count }}</p>
<button @click="handleClick">Increment</button>
`,
data() {
return {
count: 0
}
},
methods: {
handleClick() {
this.count += 1
}
}
}
Давайте напишем тест, чтобы убедиться в том, что count
увеличиться:
test('increments by 1', () => {
const wrapper = mount(Counter)
wrapper.find('button').trigger('click')
expect(wrapper.html()).toContain('Count: 1')
})
К удивлению, тест не пройден! Причина в том, что, не смотря на увеличение count
, Vue не обновит DOM до следующего event loop тика. По этой причине, проверка (expect()...
) будет вызвана до того, как Vue обновит DOM.
TIP
Если ты хочешь изучить больше об этом базовом JavaScript поведении, прочитайте про Event Loop и его macrotasks и microtasks.
Как мы можем изменить это? Vue предоставляет нам способ дождаться обновления DOM – nextTick
.
import { nextTick } from 'vue'
test('increments by 1', async () => {
const wrapper = mount(Counter)
wrapper.find('button').trigger('click')
await nextTick()
expect(wrapper.html()).toContain('Count: 1')
})
Теперь тест будет пройден, потому что мы убедились в том, что следующий "тик" выполнился и DOM обновился перед запуском дальнейших проверок.
Поскольку await nextTick()
является распространенным, Vue Test Utils предоставляет сокращение. Методы, которые вызывают обновление DOM, такие как trigger
и setValue
, вернут nextTick
, поэтому ты можешь просто указать await
перед ними:
test('increments by 1', async () => {
const wrapper = mount(Counter)
await wrapper.find('button').trigger('click')
expect(wrapper.html()).toContain('Count: 1')
})
Другое асинхронное поведение
nextTick
будет полезным, чтобы убедиться в том, что некоторые изменения в реактивных данных были отображены в DOM до продолжения теста. Однако, иногда вы можете захотеть убедиться в другом, что асинхронное поведение, не связанное с Vue, тоже завершено.
Распространенный пример - это функция, которая возвращает Promise
. Возможно, вы имитировали axios
HTTP-клиент, используя jest.mock
:
jest.spyOn(axios, 'get').mockResolvedValue({ data: 'some mocked data!' })
В данном случае Vue не знает о невыполненном Promise, поэтому вызов nextTick
не будет работать - ваша проверка может выполниться до того, как Promise выполнится. Для таких сценариев Vue Test Utils предоставляет flushPromises
, который заставляет все невыполненные промисы выполниться немедленно.
Давайте посмотрим на пример:
import { flushPromises } from '@vue/test-utils'
import axios from 'axios'
jest.spyOn(axios, 'get').mockResolvedValue({ data: 'some mocked data!' })
test('uses a mocked axios HTTP client and flushPromises', async () => {
// какой-то компонент, который выполняет HTTP запрос,
// вызванный в `created`, используя `axios`
const wrapper = mount(AxiosComponent)
await flushPromises() // axios промис выполнился сразу
// после линии выше axios запрос выполнился с имитированными данными.
})
TIP
Если вы хотите узнать больше о тестировании запросов в компонентах, ознакомьтесь с руководством Выполнение HTTP запросов.
Тестирование асинхронного setup
Если компонент, который вы хотите протестировать, использует асинхронный setup
, тогда вы должны создать компонент внутри Suspense
компонента (как вы делали, когда использовали его в вашем приложении).
Например, этот Async
компонент:
const Async = defineComponent({
async setup() {
// await something
}
})
должно быть протестировано следующим образом:
test('Async component', async () => {
const TestComponent = defineComponent({
components: { Async },
template: '<Suspense><Async/></Suspense>'
})
const wrapper = mount(TestComponent)
await flushPromises();
// ...
})
Обратите внимание: для получения доступа к экземпляру vm
вашего Async
компонента используйте возвращаемое значение у wrapper.findComponent(Async)
. Поскольку новый компонент определен и добавлен в этом сценарии, оболочка, возвращаемая mount(TestComponent)
, содержит его собственный (пустой) vm
.
Заключение
- Vue обновляет DOM асинхронно, но программы тестирования выполняют код синхронно.
- Используйте
await nextTick()
, чтобы удостовериться в том, что DOM обновился до того, как тесты продолжаться. - Функции, которые могут обновлять DOM (например,
trigger
иsetValue
) возвращаютnextTick
, поэтому вам нужно использоватьawait
. - Используйте
flushPromises
из Vue Test Utils, чтобы выполнить любые невыполненные промисы, не связанные с Vue (такие как API запросы). - Используйте
Suspense
для тестирования компонентов с асинхроннымsetup
, указываяasync
для функции теста.