Testing Vue 3 Apps — Slots and Async Behavior
Tags: vue, javascript, webdev, programming
Published: 2021-01-19 21:31:38

**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>
```

`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>')
})
```

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')
})
```

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')
})
```

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')
})
```

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.