Testing Vue 3 Apps — Slots and Async Behavior

John Au-Yeung - Jan 19 '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 apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue 3 apps by writing a simple app and testing it.

Testing Slots with Render Functions

We can test slots with render functions and single-file components.

For example, we can write:

Header.vue

<template>
  <div>Header</div>
</template>
Enter fullscreen mode Exit fullscreen mode

example.spec.js

import { mount } from 'vue-test-utils'
import { h } from 'vue'
import Header from './Header.vue'

const Layout = {
  template: `
    <div>
      <header>
        <slot name="header" />
      </header>
      <main>
        <slot name="main" />
      </main>
      <footer>
        <slot name="footer" />
      </footer>
    </div>
  `
}

test('layout full page layout', () => {
  const wrapper = mount(Layout, {
    slots: {
      header: Header,
      main: h('div', 'Main Content'),
      footer: '<div>Footer</div>'
    }
  })

  expect(wrapper.html()).toContain('<div>Header</div>')
  expect(wrapper.html()).toContain('<div>Main Content</div>')
  expect(wrapper.html()).toContain('<div>Footer</div>')
})
Enter fullscreen mode Exit fullscreen mode

We have the Layout component with several slots.

And we add a test to test it by population slots with a single file component for the header.

The main slot is populated with a render function.

h is a function to render a component. The first arg is the tag name and the 2nd arg is the content of the div.

The footer has an HTML string as its value.

Then we check its content with the expect calls.

Scoped Slots

We can test scoped slots with Vue Test Utils.

For example, we can write:

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

const ComponentWithSlots = {
  template: `
    <div class="scoped">
      <slot name="scoped" v-bind="{ msg }" />
    </div>
  `,
  data() {
    return {
      msg: 'world'
    }
  }
}

test('scoped slots', () => {
  const wrapper = mount(ComponentWithSlots, {
    slots: {
      scoped: `<template #scoped="params">
        Hello {{ params.msg }}
        </template>
      `
    }
  })
  expect(wrapper.html()).toContain('Hello world')
})
Enter fullscreen mode Exit fullscreen mode

Our ComponentWithSlots component has a slot name scoped .

It exposes the msg property to the parent.

In the test, we render it in the template tag.

And we check the rendered content in the last line of the test.

Asynchronous Behavior

We can test async behavior in our tests.

For example, we can write:

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

const Counter = {
  template: `
    <div>
      <p>Count: {{ count }}</p>
      <button @click="handleClick">Increment</button>
    </div>
  `,
  data() {
    return {
      count: 0
    }
  },
  methods: {
    handleClick() {
      this.count += 1
    }
  }
}

test('increments by 1', async () => {
  const wrapper = mount(Counter)
  await wrapper.find('button').trigger('click')
  expect(wrapper.find('p').text()).toMatch('Count: 1')
})
Enter fullscreen mode Exit fullscreen mode

We mount the Counter component.

Then we get the button and trigger the click event on it.

Then we check the text of the p element to see if it’s what we expect.

Equivalently, we can write:

import { mount } from '@vue/test-utils'
import { nextTick } from 'vue'

const Counter = {
  template: `
    <div>
      <p>Count: {{ count }}</p>
      <button @click="handleClick">Increment</button>
    </div>
  `,
  data() {
    return {
      count: 0
    }
  },
  methods: {
    handleClick() {
      this.count += 1
    }
  }
}

test('increments by 1', async () => {
  const wrapper = mount(Counter)
  wrapper.find('button').trigger('click')
  await nextTick()
  expect(wrapper.find('p').text()).toMatch('Count: 1')
})
Enter fullscreen mode Exit fullscreen mode

We trigger the click event on the button the same way.

But we call nextTick to wait for the latest count to be rendered.

Then we can do the check the same way.

Conclusion

We can test named and scoped slots in Vue 3 components.

Also, we can test async behavior like clicks triggered in our components.

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