Setting the file. One moment.
Skill 16 · Vue Testing Best Practices
Subchapter 16.3
reference/testing-async-await-flushpromises.mdMarkdown5 KBView on GitHub
Impact: HIGH - Vue updates the DOM asynchronously. Without properly awaiting these updates, tests may assert against stale DOM state, causing intermittent failures and false negatives.
Also bundled
LICENSEUse await with triggers and setValue, use nextTick for reactive updates, and use flushPromises for external async operations like API calls.
trigger() and setValue() callsawait nextTick() after programmatic reactive state changesawait flushPromises() for external async operations (API calls, timers)nextTick calls - use flushPromises insteadwaitFor from testing-library for polling assertionsIncorrect:
import { mount } from '@vue/test-utils'
import SearchComponent from './SearchComponent.vue'
// BAD: Not awaiting trigger - assertion runs before DOM updates
test('search filters results', () => {
const wrapper = mount(SearchComponent)
wrapper.find('input').setValue('vue') // Missing await!
wrapper.find('button').trigger('click') // Missing await!
// This assertion likely fails - DOM hasn't updated yet
expect(wrapper.findAll('.result').length).toBe(3)
})
// BAD: Using nextTick for API calls
test('loads data from API', async () => {
const wrapper = mount(DataLoader)
await nextTick() // This won't wait for the API call!
// Assertion runs before fetch completes
expect(wrapper.find('.data').text()).toBe('Loaded data')
})Correct:
import { mount, flushPromises } from '@vue/test-utils'
import { nextTick } from 'vue'
import SearchComponent from './SearchComponent.vue'
import DataLoader from './DataLoader.vue'
// CORRECT: Await trigger and setValue
test('search filters results', async () => {
const wrapper = mount(SearchComponent)
await wrapper.find('input').setValue('vue')
await wrapper.find('button').trigger('click')
expect(wrapper.findAll('.result').length).toBe(3)
})
// CORRECT: Use flushPromises for API calls
test('loads data from API', async () => {
const wrapper = mount(DataLoader)
// Wait for all pending promises to resolve
await flushPromises()
expect(wrapper.find('.data').text()).toBe('Loaded data')
})// These methods return nextTick internally
await wrapper.find('button').trigger('click')
await wrapper.find('input').setValue('new value')
await wrapper.find('form').trigger('submit')import { nextTick } from 'vue'
test('reflects programmatic state changes', async () => {
const wrapper = mount(Counter)
// Direct state modification (when testing with exposed internals)
wrapper.vm.count = 5
await nextTick() // Wait for Vue to update DOM
expect(wrapper.find('.count').text()).toBe('5')
})import { flushPromises } from '@vue/test-utils'
test('displays fetched data', async () => {
const wrapper = mount(UserProfile, {
props: { userId: 1 }
})
// Wait for component's API call to complete
await flushPromises()
expect(wrapper.find('.username').text()).toBe('John')
})
// Sometimes you need multiple flushPromises for chained async operations
test('processes data after fetch', async () => {
const wrapper = mount(DataProcessor)
await flushPromises() // Wait for fetch
await flushPromises() // Wait for processing triggered by fetch
expect(wrapper.find('.processed').exists()).toBe(true)
})test('submits form and shows success', async () => {
const wrapper = mount(ContactForm)
// Fill form (awaiting each interaction)
await wrapper.find('#name').setValue('John')
await wrapper.find('#email').setValue('john@example.com')
// Submit form
await wrapper.find('form').trigger('submit')
// Wait for API submission to complete
await flushPromises()
// Assert success state
expect(wrapper.find('.success-message').exists()).toBe(true)
})import { flushPromises } from '@vue/test-utils'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
const server = setupServer(
rest.get('/api/user', (req, res, ctx) => {
return res(ctx.json({ name: 'John' }))
})
)
test('displays user data', async () => {
const wrapper = mount(UserCard)
// MSW might require multiple flushPromises
await flushPromises()
await flushPromises()
expect(wrapper.find('.name').text()).toBe('John')
})