You’re browsing the documentation for Vue Test Utils for Vue v2.x and earlier.
To read docs for Vue Test Utils for Vue 3, click here.
Testing Asynchronous Behavior
There are two types of asynchronous behavior you will encounter in your tests:
- Updates applied by Vue
- Asynchronous behavior outside of Vue
Updates applied by Vue
Vue batches pending DOM updates and applies them asynchronously to prevent unnecessary re-renders caused by multiple data mutations.
You can read more about asynchronous updates in the Vue docs
In practice, this means that after mutating a reactive property, to assert that change your test has to wait while Vue is performing updates.
One way is to use await Vue.nextTick()
, but an easier and cleaner way is to just await
the method that you mutated the state with, like trigger
.
// inside test-suite, add this test case
it('button click should increment the count text', async () => {
expect(wrapper.text()).toContain('0')
const button = wrapper.find('button')
await button.trigger('click')
expect(wrapper.text()).toContain('1')
})
Awaiting the trigger above is the same as doing:
it('button click should increment the count text', async () => {
expect(wrapper.text()).toContain('0')
const button = wrapper.find('button')
button.trigger('click')
await Vue.nextTick()
expect(wrapper.text()).toContain('1')
})
Methods that can be awaited are:
Asynchronous behavior outside of Vue
One of the most common asynchronous behaviors outside of Vue is API calls in Vuex actions. The following examples shows how to test a method that makes an API call. This example uses Jest to run the test and to mock the HTTP library axios
. More about Jest manual mocks can be found here.
The implementation of the axios
mock looks like this:
export default {
get: () => Promise.resolve({ data: 'value' })
}
The below component makes an API call when a button is clicked, then assigns the response to value
.
<template>
<button @click="fetchResults">{{ value }}</button>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
value: null
}
},
methods: {
async fetchResults() {
const response = await axios.get('mock/service')
this.value = response.data
}
}
}
</script>
A test can be written like this:
import { shallowMount } from '@vue/test-utils'
import Foo from './Foo'
jest.mock('axios', () => ({
get: Promise.resolve('value')
}))
it('fetches async when a button is clicked', () => {
const wrapper = shallowMount(Foo)
wrapper.find('button').trigger('click')
expect(wrapper.text()).toBe('value')
})
This test currently fails because the assertion is called before the promise in fetchResults
resolves. Most unit test libraries provide a callback to let the runner know when the test is complete. Jest and Mocha both use done
. We can use done
in combination with $nextTick
or setTimeout
to ensure any promises are settled before the assertion is made.
it('fetches async when a button is clicked', done => {
const wrapper = shallowMount(Foo)
wrapper.find('button').trigger('click')
wrapper.vm.$nextTick(() => {
expect(wrapper.text()).toBe('value')
done()
})
})
The reason setTimeout
allows the test to pass is because the microtask queue where promise callbacks are processed runs before the task queue, where setTimeout
callbacks are processed. This means by the time the setTimeout
callback runs, any promise callbacks on the microtask queue will have been executed. $nextTick
on the other hand schedules a microtask, but since the microtask queue is processed first-in-first-out that also guarantees the promise callback has been executed by the time the assertion is made. See here for a more detailed explanation.
Another solution is to use an async
function and a package like flush-promises. flush-promises
flushes all pending resolved promise handlers. You can await
the call of flushPromises
to flush pending promises and improve the readability of your test.
The updated test looks like this:
import { shallowMount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Foo from './Foo'
jest.mock('axios')
it('fetches async when a button is clicked', async () => {
const wrapper = shallowMount(Foo)
wrapper.find('button').trigger('click')
await flushPromises()
expect(wrapper.text()).toBe('value')
})
This same technique can be applied to Vuex actions, which return a promise by default.
await button.trigger()
?
Why not just As explained above, there is a difference between the time it takes for Vue to update its components,
and the time it takes for a Promise, like the one from axios
to resolve.
A nice rule to follow is to always await
on mutations like trigger
or setProps
.
If your code relies on something async, like calling axios
, add an await to the flushPromises
call as well.