Unit Test Vue Apps with Vue Test Utils — Lifecycle and Async Tests

John Au-Yeung - Jan 23 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

With the Vue Test Utils library, we can write and run unit tests for Vue apps easily.

In this article, we’ll look at how to write unit tests with the Vue Test Utils library.

Lifecycle Hooks

When we call mount or shallowMount to mount our components, the lifecycle methods will be run.

However, the beforeDestroy or destroyed methods won’t be triggered unless the component is manually destroyed with wrapper.destroy() .

The component won’t be automatically destroyed at the end of each test, so we have to clean up any dependencies manually.

Writing Asynchronous Tests

When we write tests, we have to be aware that any changes to reactive properties are done asynchronously.

This means that we have to the test as an async function or use then and run our test code that runs after the reactive property is updated in the then callback.

For example, we can write:

import { shallowMount } from '@vue/test-utils'

const Counter = {
  template: `
    <div>
      <button @click="count++">Add up</button>
      <p>clicks: {{ count }}</p>
    </div>
  `,
  data() {
    return { count: 0 }
  }
}

describe('Counter', () => {
  it('renders new number when Add up button is clicked', async () => {
    const wrapper = shallowMount(Counter)
    const button = wrapper.find('button')
    const text = wrapper.find('p')
    await button.trigger('click')
    expect(text.text()).toBe('clicks: 1')
  })
})
Enter fullscreen mode Exit fullscreen mode

to await the trigger method so that we can check the updated value of the count reactive property after the update is done.

We can also write:

import { shallowMount } from '@vue/test-utils'

const Counter = {
  template: `
    <div>
      <button @click="count++">Add up</button>
      <p>clicks: {{ count }}</p>
    </div>
  `,
  data() {
    return { count: 0 }
  }
}

describe('Counter', () => {
  it('renders new number when Add up button is clicked', () => {
    const wrapper = shallowMount(Counter)
    const button = wrapper.find('button')
    const text = wrapper.find('p')
    button.trigger('click')
      .then(() => {
        expect(text.text()).toBe('clicks: 1')
      })
  })
})
Enter fullscreen mode Exit fullscreen mode

which is the same code without async and await.

Asserting Emitted Events

We can check which events are emitted when we do something with the component.

To do this, we can call the emitted method to check which events are emitted.

For instance, we can write:

import { shallowMount } from '@vue/test-utils'

const Foo = {
  template: `
    <div>
      <button @click="$emit('foo')">emit foo</button>
    </div>
  `,
}

describe('Foo', () => {
  it('emits foo event when the emit foo button is clicked', async () => {
    const wrapper = shallowMount(Foo)
    const button = wrapper.find('button');
    await button.trigger('click')
    expect(wrapper.emitted().foo).toBeTruthy()
  })
})
Enter fullscreen mode Exit fullscreen mode

We have the Foo component that calls $emit to emit the 'foo' event when we click it.

Then in the last line of the test, we call wrapper.emitted() to check if the foo event is emitted.

If it’s emitted, then the foo property should be truthy.

We can also check which payload is emitted. For example, we can write:

import { shallowMount } from '@vue/test-utils'

const Foo = {
  template: `
    <div>
      <button @click="$emit('foo', 123)">emit foo</button>
    </div>
  `,
}

describe('Foo', () => {
  it('emits foo event with payload 123 when the emit foo button is clicked', async () => {
    const wrapper = shallowMount(Foo)
    const button = wrapper.find('button');
    await button.trigger('click')
    expect(wrapper.emitted().foo.length).toBe(1)
    expect(wrapper.emitted().foo[0]).toEqual([123])
  })
})
Enter fullscreen mode Exit fullscreen mode

We check the foo property’s length to see how many times it’s emitted.

And the payload is checked by accessing foo ‘s array value and checking the content.

Conclusion

We can check if events are emitted and be aware that lifecycle hooks like destroy aren’t run automatically.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .