recorded decision · public · no signup

accepted2026-06-23vuejs/rfcs

Summary

What was chosen

  • Vue Test Utils APIs that trigger re-renders will be awaitable to simplify asserting changes after re-renders.adr
  • Actions that trigger async changes will return a promise that resolves on `nextTick`.adr
  • Methods like `trigger`, `setValue`, `setChecked`, `setData`, `setProps`, and `setSelected` will return a promise.adr
  • A `tick` helper will be added to Vue Test Utils exports to provide an official way to await renders for actions not returning a promise.adr

Constraints

  • Vue 3's removal of `nextTick` from the VM instance requires users to import it directly from Vue.adr
  • The removal of `sync` mode in Vue Test Utils `beta-29` necessitates awaiting re-renders before making assertions.adr
  • The proposal assumes that `async/await` syntax will be used within tests.adr

Consequences

  • Users might incorrectly `await` all DOM interactions, necessitating clear documentation and guides.adr
  • Users will need to remove existing `$nextTick` and `flushPromises` calls when migrating to the new API.adr
  • Each `trigger` calling `nextTick()` might negatively impact performance.adr

The recorded why

  • Start Date: 2020.02.01
  • Target Major Version: VTU beta-0.3x/1.x
  • Reference Issues:
  • Implementation PR:

Summary

Allows the Vue Test Utils APIs that trigger re-renders to be awaited. This makes asserting changes after re-renders easier.

Basic example

    const wrapper = mount(Component)
    await wrapper.find('.element').trigger('click')
    expect(wrapper.emitted('event')).toBeTruthy()

Motivation

With the removal of sync mode in Vue Test Utils beta-29, we now need to await for things like watchers or the template to re-render, before we make assertions.

This proposal aims to make it easier for developers to work with async tests, by allowing you to await calls like trigger or those that use it internally.

The proposal assumes the usage of async/await inside tests.

Detailed design

The general idea is to return a promise resolving on nextTick, from actions that trigger async changes.

At this moment we need to manually await for the component to update. This leads to extra boilerplate, and is harder to grasp for beginners.

    const wrapper = mount(Component)
    wrapper.find('.element').trigger('click')
    // we need to wait for the component to render
    await wrapper.vm.$nextTick()
    expect(wrapper.emitted('event')).toBeTruthy()

The new API should look like:

    const wrapper = mount(Component)
    await wrapper.find('.element').trigger('click')
    expect(wrapper.emitted('event')).toBeTruthy()

With more complicated tests, the benefits are obvious:

    await wrapper.find('.button').trigger('click')
    expect(wrapper.emitted('event')).toBeTruthy()
    
    await wrapper.find('.country-option').setValue('US')
    expect(wrapper.find('.state').exists()).toBe(true)
    
    await wrapper.find('.radio-option').setChecked()
    expect(wrapper.find('.finish').attributes('disabled')).toBeFalsy()  

Methods that should return a promise, resolving on next tick:

  • trigger
  • setValue
  • setChecked
  • setData
  • setProps
  • setSelected
  • setValue

Most of the above helpers rely on trigger internally, so updating the majority of listed methods would be easy.

Additional helpers

Currently we have seen 3 ways to await for changes:

    import flushPromises from 'flush-promises'
    await flushPromises()
    // or the more preferred
    await wrapper.vm.$nextTick()
    await Vue.nextTick()

In Vue 3 nextTick will be removed from the VM instance, so users will have to migrate over to importing it from Vue directly import { nextTick } from 'vue'.

A tick helper can be added to the VTU exports. That way users have everything in one place, and an official way to await for renders, from actions that cannot directly return a promise.

Such a case is triggering a custom Vue event.

    import { mount, tick } from '@vue/test-utils'
    
    it('test' => {
    	const wrapper = mount(Component)
     	wrapper.find('.input').vm.$emit('input', 'Newly added note')	
    	await tick()
    
    	expect(wrapper).toMatchSnapshot()
    })

Or we could be focusing an element on mounted, which is usually done on next tick

    const wrapper = mount(Component)
    // await data to be focused on mounted
    await tick()
    let input = wrapper.find('input').element
    expect(input).toBe(document.activeElement)

Drawbacks

Each trigger will now call nextTick() , which I am not sure if it can hurt performance.

Users may try to await anything that interacts with the DOM, so it is a matter of writing good DOCs and guides on the topic.

Alternatives

Adoption strategy

Vue Test Utils beta-30+

Users would have to make sure async/await is working in their testing env.

Users just have to remove extra $nextTick and flushPromises calls and use the new api.

Improve docs on the topic.

Vue Test Utils prior to beta-30

Before the removal of sync mode it was not necessary to await for renders.

Unresolved questions

Source: active-rfcs/0032-vtu-improve-async-workflow.md