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.