Awesome tests with Vue and Jest
Jest is a very neat JavaScript testing library from Facebook. It’s mostly syntax-compatible with Jasmine and needs zero or very less configuration. Code coverage reports are there out-of-the-box and with sandboxed tests and snapshot testing it has some unique features.
Set-up Jest
Vue CLI
You are using Vue CLI? Consider yourself lucky, the set-up of Jest could not be simpler:
yarn add --dev jest @vue/cli-plugin-unit-jest
vue invoke unit-jest
Vue CLI will do the rest and also create an example spec for the HelloWorld component.
DIY
Install all necessary dependencies:
yarn add --dev @vue/test-utils babel-jest jest jest-serializer-vue vue-jest
Create jest.config.js
in your project root directory:
module.exports = {
moduleFileExtensions: ['js', 'json', 'vue'],
transform: {
'^.+\\.vue$': 'vue-jest',
'^.+\\.js?$': 'babel-jest'
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
snapshotSerializers: ['jest-serializer-vue'],
testMatch: ['<rootDir>/src/tests/**/*.spec.js']
}
Please adjust the paths in moduleNameMapper
and testMatch
to your project.
You should also add a modern JavaScript preset to your .babelrc
file:
{
"presets": ["es2015"]
}
Optional step
Add the following line to your .gitignore
file:
/coverage
The set-up is now complete. Let’s write a test file!
Writing tests
Here is a litte example Vue component:
<template>
<ul class="demo">
<li class="demo__item" v-for="value in values" :key="value">{{ value }}</li>
</ul>
</template>
<script>
export default {
name: 'demo',
data () {
return {
values: []
}
},
created() {
this.fetchValues()
},
methods: {
async fetchValues() {
const response = await fetch('/api/demo/values')
this.values = await response.json()
}
}
}
</script>
The component Demo
will fetch some values after its creation and display them in an unordered list. This is example is quite
simple, but testing is a little more complex due to the usage of fetch
, async
and await
.
Of course, there are some tools to help us:
yarn add --dev fetch-mock flush-promises
Now, let’s write a test:
import { shallow } from '@vue/test-utils'
import fetchMock from 'fetch-mock'
import flushPromises from 'flush-promises'
import Demo from '@/components/Demo.vue'
const values = [
'foo',
'bar'
]
describe('Demo.vue', () => {
beforeEach(() => {
fetchMock.get('/api/demo/values', values)
})
it('renders component', async () => {
const wrapper = shallow(Demo)
await flushPromises()
expect(wrapper.vm.values).toEqual(values)
})
afterEach(() => {
fetchMock.restore()
})
})
What’s going on?
-
shallow
from Vue Test Utils creates a wrapper of the rendered and mounted component, any child components will be stubs. If you need child components in your test, please usemount
instead ofshallow
. -
fetchMock
will create a mocked version of the Fetch API. In this case it will return the defined values for a GET request to/api/demo/values
. If you send a request that’s not defined in fetchMock, it will throw an exception and break your tests. -
The test itself is defined as async to use
await
forflushPromises()
. It will wait until the mocked request is finished and the values are stored in the component’s data. -
You can now access the data property
values
and compare the content to the response of the mocked HTTP request.
Conclusion
Setting up Jest for Vue is easy, even if you have to do it manually.
A little warning: setting up Jest for an existing app can be tedious. The current AngularJS app of our customer can’t be tested with Jest, at least for now. The AngularJS HTTP mock does not work and I haven’t figured out the problem yet.
But enough of Angular: the real deal comes with the testing itself. Async/await is a nice and simple way for testing asynchronous behaviour. I don’t think this could be easier and it’s a reliable method with the power of modern JavaScript. Try to imagine what the demo test would look like in ES5 …