Unit testing Vue SFC
The what, why, and how
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)
})
- Fast to run
- Easy to understand
- Only test a single unit of work
- Fast to run
- Easy to understand
- Only test a single unit of work
Unit Tests Should be
Why?
Unit test benefits
- Check components work correctly
- Provide documentation
- Easier debugging (TDD)
- Less bugs
How?
<template>
<div></div>
</template>
<script>
export default {
props: ['visible']
}
</script>
SFC
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
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
$ yarn add -D jest vue-jest ts-jest
// package.json
{
// ..
"jest": {
"transform": {
"^.+\\.ts$": "ts-jest",
"^.+\\.vue$": "vue-jest"
},
}
// ..
}
// package.json
{
// ..
"scripts": {
"unit": "jest"
}
// ..
}
$ yarn unit
vue-test-utils
$ yarn add -D @vue/test-utils
https://vue-test-utils.vuejs.org/
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
<cb-modal-component
:visible="displayModal"
:onClose="closeModal"
>
Hello Chargebee
</cb-modal-component>
test('does not render when not passed visible prop', () => {
})
test('- :visible does not render when not passed visible prop', () => {
const wrapper = mount(Modal)
})
test(':visible - 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(':visible - renders when passed visible prop as true', () => {
const wrapper = mount(Modal, {
propsData: {
visible: true
}
})
})
test(':visible - renders when passed visible prop as true', () => {
const wrapper = mount(Modal, {
propsData: {
visible: true
}
})
expect(wrapper.isEmpty()).toBe(false)
})
test('close - Emits when when button is clicked', () => {
const wrapper = mount(Modal, {
propsData: {
visible: true
}
})
wrapper.find('button').trigger('click')
expect(wrapper.emitted().close).toHaveBeenCalled()
})
test('calls onClose when button is clicked', () => {
const onClose = jest.fn()
})
test('close - Emits when when button is clicked', () => {
const wrapper = mount(Modal, {
propsData: {
visible: true
}
})
})
test('close - Emits when when button is clicked', () => {
const wrapper = mount(Modal, {
propsData: {
visible: true
}
})
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
// 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"
],
// ..
}
// ..
}
$ yarn add -D jest-serializer-vue
$ yarn 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()
})
Resources
- vue-test-utils.vuejs.org
- jestjs.io
- github.com/kulshekhar/ts-jest
- github.com/vuejs/vue-jest
- github.com/eddyerburgh/jest-serializer-vue
- www.manning.com/books/testing-vue-js-applications
Vue Unit Testing
By Bharathvaj Ganesan
Vue Unit Testing
What, why, and how of unit testing Vue components
- 552