Wait, a book on Testing Vue with Jest? Yeah, this article is just the first chapter đ
vue-test-utils, the official VueJS testing library and based on avoriaz, is just around the corner. @EddYerburgh is indeed doing a very good job creating it. It provides all necessary tooling for making easy to write unit test in a VueJS application.
Jest, on the other side, is the testing framework developed at Facebook, which makes testing a breeze, with awesome features such as:
- Almost no config by default
- Very cool interactive mode
- Run tests in parallel
- Spies, stubs and mocks out of the box
- Built in code coverage
- Snapshot testing
- Module mocking utilities
Probably youâve already written test without this tools, and just by using karma + mocha + chai + sinon + âŚ, but youâll see how much easier it can be đ.
Set up a vue-test sample project
Letâs start by creating a new project using vue-cli
answering NO to all yes/no questions:
npm install -g vue-cli
vue init webpack vue-test
cd vue-test
Then weâll need to install some dependencies:
# Install dependencies
npm i -D jest jest-vue-preprocessor babel-jest
jest-vue-preprocessor
is needed for making jest understand .vue
files, and babel-jest
for the integration with Babel.
As per vue-test-utils
, it hasnât been released yet, but for now you can add it to your :package.json
from the source
Update (2017/10/10): it can be installed already from npm, since beta.1
has been published.
npm i -D vue-test-utils
Letâs add the following Jest configuration in the package.json
:
...
"jest": {
"moduleNameMapper": {
"^vue$": "vue/dist/vue.common.js"
},
"moduleFileExtensions": [
"js",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/jest-vue-preprocessor"
}
}
Testing a Component
Iâll be using Single File Components here, and I havenât checked if it works by splitting them in their own html
, css
or js
files, so letâs assume youâre doing that as well.
First create a MessageList.vue
component under src/components
:
<template>
<ul>
<li v-for="message in messages"> {{ message }} </li>
</ul>
</template>
<script>
export default {
name: 'list',
props: ['messages']
}
</script>
And update App.vue
to use it, as follows:
<template>
<div id="app">
<MessageList messages="messages" />
</div>
</template>
<script>
import MessageList from './components/MessageList'
export default {
name: 'app',
data: () => ({
messages: ['Hey John', 'Howdy Paco']
}),
components: { MessageList }
}
</script>
We have already a couple of components that we can test. Letâs create a test
folder under the project root, and a App.test.js
:
import Vue from 'vue'
import App from '../src/App'
describe('App.test.js', () => {
let cmp, vm
beforeEach(() => {
cmp = Vue.extend(App) // Create a copy of the original component
vm = new cmp({
data: { // Replace data value with this fake data
messages: ['Cat']
}
}).$mount() // Instances and mounts the component
})
it('equals messages to ["Cat"]', () => {
expect(vm.messages).toEqual(['Cat'])
})
})
Right now, if we run npm test
(or npm t
as a shorthand version), the test should run and pass. Since weâre modifying the tests, letâs better run it in watch mode :
npm t -- --watch
The problem with nested components
This test is too simple. Letâs check that the output is the expected as well. For that we can use the amazing Snapshots feature of Jest, that will generate a snapshot of the output and check it against in the upcoming runs. Add after the previous it
in App.test.js
:
it('has the expected html structure', () => {
expect(vm.$el).toMatchSnapshot()
})
That will create a test/ __snapshots__ /App.test.js.snap
file. Letâs open it and inspect it:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App.test.js has the expected html structure 1`] = `
<div
id="app"
>
<ul>
<li>
Cat
</li>
</ul>
</div>
`;
In case you havenât noticed, there is a big problem here: the MessageList
component has been rendered as well. Unit tests must be tested as an independent unit , meaning that in App.test.js
we wanna test App
component and donât care at all about anything else.
This can be the reason of several problems. Imagine for example, that the children components (MessageList
in this case) perform side effect operations on the created
hook, such as calling fetch
, a Vuex action or state changes? Thatâs something we definitely donât want.
Luckily, Shallow Rendering solves this nicely.
What is Shallow Rendering?
Shallow Rendering is a technique that assures your component is rendering without children. This is useful for:
- Testing only the component you want to test (thatâs what Unit Test stands for)
- Avoid side effects that children components can have, such as making HTTP calls, calling store actionsâŚ
Testing a Component with vue-test-utils
vue-test-utils
provide us with Shallow Rendering among other features. We could rewrite the previous test as follows:
import { shallow } from 'vue-test-utils'
import App from '../src/App'
describe('App.test.js', () => {
let cmp
beforeEach(() => {
cmp = shallow(App, { // Create a shallow instance of the component
data: {
messages: ['Cat']
}
})
})
it('equals messages to ["Cat"]', () => {
// Within cmp.vm, we can access all Vue instance methods
expect(cmp.vm.messages).toEqual(['Cat'])
})
it('has the expected html structure', () => {
expect(cmp.element).toMatchSnapshot()
})
})
And now, if youâre still running Jest in watching mode, youâll see the test still pass, but the Snapshot doesnât match. Press u
to regenerate it. Open and inspect it again:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App.test.js has the expected html structure 1`] = `
<div
id="app"
>
<!-- -->
</div>
`;
You see? Now no children have been rendered and we tested the App
component fully isolated from the component tree. Also, if you have any created
or whatever hooks in the children components, they havenât been called either đ.
If youâre curious about how shallow render is implemented , check out the source code and youâll see that basically is stubbing the components
key, the render
method and the lifecycle hooks.
In the same vein, you can implement the MessageList.test.js
test as follows:
import { shallow } from 'vue-test-utils'
import MessageList from '../src/components/MessageList'
describe('MessageList.test.js', () => {
let cmp
beforeEach(() => {
cmp = shallow(MessageList, {
// Beaware that props is overriden using `propsData`
propsData: {
messages: ['Cat']
}
})
})
it('has received ["Cat"] as the message property', () => {
expect(cmp.vm.messages).toEqual(['Cat'])
})
it('has the expected html structure', () => {
expect(cmp.element).toMatchSnapshot()
})
})
Find the full example on Github.
Originally published at alexjoverm.github.io on August 21, 2017
Top comments (0)