Unit testing Vue components
The what, why, and how
Edd Yerburgh
@eddyerburgh
What
// sum.js
function sum (a, b) {
return a + b
}
export default sum
// sum.spec.js
import sum from './sum'
test('returns sum of input', () => {
expect(sum(1,3)).toBe(4)
})
Why?
Unit test benefits
- Check components work correctly
- Provide documentation
- Easier debugging
- Less bugs
How?
<template>
<div></div>
</template>
<script>
export default {
props: ['visible']
}
</script>
module.exports = {
render: function () {
var _vm=this;
var _h=_vm.$createElement;
var _c=_vm._self._c||_h;
return _c('div')
}
staticRenderFns: [],
props: ['visible']
}
compiled SFC
SFC
Compilation process
Modal.vue
Modal.spec.js
Compiler
Test runner
github.com/eddyerburgh/vue-unit-test-perf-comparison
Test runner | 1000 | 5000 |
---|---|---|
tape | 9.s | 38s |
jest | 22s | 91s |
mocha-webpack | 11s | 38s |
karma-mocha | 33s | 119s |
ava | 625s | 7161s |
Test runner comparison
Compilers
- Webpack (vue-loader)
- Jest (vue-jest)
Jest
vue-test-utils.vuejs.org/en/guides/testing-SFCs-with-jest.html
$ npm install --save-dev jest vue-jest babel-jest
// package.json
{
// ..
"jest": {
"transform": {
"^.+\\.js$": "babel-jest",
"^.+\\.vue$": "vue-jest"
},
}
// ..
}
// package.json
{
// ..
"scripts": {
"unit": "jest"
}
// ..
}
$ npm run unit
compiled SFC
module.exports = {
render: function () {
var _vm=this;
var _h=_vm.$createElement;
var _c=_vm._self._c||_h;
return _c('div')
}
staticRenderFns: [],
props: ['visible']
}
vue-test-utils
$ npm install --save-dev @vue/test-utils
mount
import { mount } from '@vue/test-utils'
import Modal from '../Modal.vue'
import { mount } from '@vue/test-utils'
import Modal from '../Modal.vue'
const wrapper = mount(Modal)
wrapper.vm
wrapper.element
import { mount } from '@vue/test-utils'
import Modal from '../Modal.vue'
const wrapper = mount(Modal)
??
Component contract
Input
Output
Component
Input
- User action
- Props
- Store
Output
- Rendered output
- Vue events
- Function calls
Modal component
<modal-component
:visible="displayModal"
:onClose="closeModal"
>
Some content
</modal-component>
test('does not render when not passed visible prop', () => {
})
test('does not render when not passed visible prop', () => {
const wrapper = mount(Modal)
})
test('does not render when not passed visible prop', () => {
const wrapper = mount(Modal)
expect(wrapper.isEmpty()).toBe(true)
})
test('renders when passed visible prop as true', () => {
})
test('renders when passed visible prop as true', () => {
const wrapper = mount(Modal, {
propsData: {
visible: true
}
})
})
test('renders when passed visible prop as true', () => {
const wrapper = mount(Modal, {
propsData: {
visible: true
}
})
expect(wrapper.isEmpty()).toBe(false)
})
test('calls onClose when button is clicked', () => {
const onClose = jest.fn()
const wrapper = mount(Modal, {
propsData: {
visible: true,
onClose
}
})
wrapper.find('button').trigger('click')
expect(onClose).toHaveBeenCalled()
})
test('calls onClose when button is clicked', () => {
const onClose = jest.fn()
})
test('calls onClose when button is clicked', () => {
const onClose = jest.fn()
const wrapper = mount(Modal, {
propsData: {
visible: true,
onClose
}
})
})
test('calls onClose when button is clicked', () => {
const onClose = jest.fn()
const wrapper = mount(Modal, {
propsData: {
visible: true,
onClose
}
})
wrapper.find('button').trigger('click')
})
test('calls onClose when button is clicked', () => {
})
test('renders slot content')
<template>
<div v-if="visible">
<button @click="onClose" />
</div>
</template>
<script>
export default {
props: ['visible', 'onClose']
}
</script>
<template>
<div v-if="visible" class="modal is-active">
<div class="modal-background"></div>
<div class="modal-content">
<div class="box">
<button
@click="onClose"
class="delete"
aria-label="close"></button>
<slot />
</div>
</div>
</div>
</div>
</template>
Snapshot tests
test('renders correctly', () => {
const wrapper = mount(Modal, {
propsData: {
visible: true
}
})
})
test('renders correctly', () => {
})
test('renders correctly', () => {
const wrapper = mount(Modal, {
propsData: {
visible: true
}
})
expect(wrapper.html()).toMatchSnapshot()
})
Does previous
snapshot exist?
create snapshot
compare to previous
snapshot
test passes
No
Yes
does output
match
snapshshot?
create snapshot file
test fails
test passes
Compare to previous snapshot
Yes
No
First run
// Modal.spec.js.snap
exports[`renders correctly 1`] = `"<div class=\\"modal is-active\\"><div class=\\"modal-background\\"></div> <div class=\\"modal-content\\"><div class=\\"box\\"><button aria-label=\\"close\\" class=\\"delete\\"></button> </div></div></div>"`;
// package.json
{
// ..
"jest": {
// ..
"snapshotSerializers": [
"jest-serializer-vue"
],
// ..
}
// ..
}
$ npm install --save-dev jest-serializer-vue
$ npm run unit -- -u
Overwrite snapshot
test('renders correctly', () => {
})
test('renders correctly', () => {
const wrapper = mount(Modal, {
slots: {
default: '<p>some content</p>'
}
})
})
test('renders correctly', () => {
const wrapper = mount(Modal, {
slots: {
default: '<p>some content</p>'
},
propsData: {
visible: true
}
})
})
test('renders correctly', () => {
const wrapper = mount(Modal, {
slots: {
default: '<p>some content</p>'
},
propsData: {
visible: true
}
})
expect(wrapper.html()).toMatchSnapshot()
})
Get started with the docs
vue-test-utils.vuejs.org/
Learn more with my book
40% off with code:
ctwvuejsamst18
Unit Testing Vue components
By Edd Yerburgh
Unit Testing Vue components
What, why, and how of unit testing Vue components
- 7,602