introduce
Vue-test-utils is the official vue. js unit Test utility library. It provides a series of apis to make it easy to write unit tests for Vue applications.
There are many mainstream unit Test runners, such as Jest, Mocha, Karma, etc. There are tutorials for these in the Vue-test-utils documentation. Here we will only show examples of vue-test-utils + Jest.
Jest is a testing framework developed by Facebook. Vue describes it as the most fully functional test runner. The configuration required is minimal, JSDOM is installed by default, assertions are built in, and the command line user experience is excellent. However, you need a preprocessor that can import single-file components into the test. We have created the Vue-jest preprocessor to handle the most common single file component features, but it is still not 100% functional for Vue-Loader.
Environment configuration
When creating a new project using scaffolding vue-CLI, if Unit Testing is selected and Jest is selected as the test runner, the environment for Unit Testing will be automatically configured once the project is created. Use vue-test-utils and Jest apis directly to write Test cases.
However, the unit test function was not selected at the beginning of the new project. If it needs to be added later, there are two options:
The first configuration:
Adding an unit-jest plug-in directly to your project will automatically install and configure the required dependencies.
vue add @vue/unit-jest
Copy the code
Second configuration:
This configuration will be a little more troublesome, the following is the specific operation steps.
Install dependencies
-
Install Jest and Vue Test Utils
npm install --save-dev jest @vue/test-utils Copy the code
-
Install Babel-jest, Vue-jest, and Babel-core of version 7.0.0-Bridge.0
NPM install --save-dev babel-jest vue-jest [email protected]Copy the code
-
Install the jest – serializer – vue
npm install --save-dev jest-serializer-vue Copy the code
configurationJest
Jest configuration can be configured in package.json; You can also create a new file, jest.config.js, and place it in the project root directory. Here I chose to configure it in jest.config.js:
module.exports = {
moduleFileExtensions: [
'js'.'vue'].transform: {
'^.+\\.vue$': '<rootDir>/node_modules/vue-jest'.'^.+\\.js$': '<rootDir>/node_modules/babel-jest'
},
moduleNameMapper: {
'^ @ / (. *) $': '<rootDir>/src/$1'
},
snapshotSerializers: [
'jest-serializer-vue'].testMatch: ['**/__tests__/**/*.spec.js'].transformIgnorePatterns: ['<rootDir>/node_modules/']}Copy the code
The configuration items are as follows:
moduleFileExtensions
tellJest
File suffix to be matchedtransform
Match to the.vue
For filesvue-jest
Processing, matching to.js
For filesbabel-jest
To deal withmoduleNameMapper
To deal withwebpack
For example, will@
said/src
directorysnapshotSerializers
Serialize the saved snapshot test results to make them aesthetically pleasingtestMatch
Which files are matched for testingtransformIgnorePatterns
The directory that is not matched
configurationpackage.json
Write a command script to execute the test:
{
"script": {
"test": "jest"}}Copy the code
The first test case
To ensure the consistency of the environment, let’s demonstrate the steps from creating the project.
withvue-cli
Create a project
Currently I am using vue-CLI version 3.10.0. To start creating a project:
vue create first-vue-jest
Copy the code
Select Manually Select Features to configure the manual selection function:
Vue CLI v3.10.0 ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ Update available: 4.0.4 │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘? Please pick a preset: Vue-CLI3 (Vue-router, node-sass, Babel, ESlint) default (Babel, eslint) ❯ Manually select FeaturesCopy the code
Check Babel, Unit Testing:
? Check the features needed foryour project: ◉ Babel Insights into TypeScript insights into Progressive Web App (PWA) Support insights into Router insights into CSS pre-processors insights into Linter/Formatter ◉ Unit Testing insights into TestingCopy the code
Choose Jest:
? Pick a unit testing solution:
Mocha + Chai
❯ Jest
Copy the code
Select In Dedicated Config Files to configure the configuration information In the corresponding config file:
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arrow keys)
❯ In dedicated config files
In package.json
Copy the code
Enter n, do not save the preset:
? Save this as a preset for future projects? (y/N) n
Copy the code
After the project is created, the configuration information of some files is as follows:
babel.config.js
:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset']}Copy the code
Jest.config. js, the configuration of this file is the default plug-in, you can change it to the same configuration in jEST as mentioned above.
module.exports = {
preset: '@vue/cli-plugin-unit-jest'
}
Copy the code
package.json
:
{
"name": "first-vue-jest"."version": "0.1.0 from"."private": true."scripts": {
"serve": "vue-cli-service serve"."build": "vue-cli-service build"."test:unit": "vue-cli-service test:unit"
},
"dependencies": {
"core-js": "^ 3.1.2." "."vue": "^ 2.6.10"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^ 4.0.0"."@vue/cli-plugin-unit-jest": "^ 4.0.0"."@vue/cli-service": "^ 4.0.0"."@vue/test-utils": "29 1.0.0 - beta."."vue-template-compiler": "^ 2.6.10"}}Copy the code
Executing a Test command
When the project created with the above steps is complete, we can see that there is a test:unit in the scripts item of package.json, execute it:
cd first-vue-jest
npm run test:unit
Copy the code
Then the terminal will see the output. PASS means that the test case passed. This is the official unit test example. So let’s write something of our own.
Implement a ToDoList
Looking at the prototype above, there are several specific requirements:
- Enter what you want to do in the input box on the right side of the header. When you hit Enter, the content is displayed in the to do list and the input box is cleared
- When the input field is empty, hit Enter and do not change anything
- The to-be-completed list can be edited, but the completed list cannot be edited
- Each list item has a delete button on the right, using
-
Is clicked to delete the item - The to-do list has buttons marked completed, with
Square root
Is clicked to move the current item to the completed list - The completed list has buttons marked as unfinished, with
x
Number indicates that the current item moves to the incomplete list after clicking - The list number increases from 1
- When the backlog list is empty, the backlog is not displayed
- When the completed list is empty, the completed word is not displayed
Write the top page first
Delete helloworld.vue and testexample.spec.js generated when creating the project before writing the page. Also modify the app. vue file to introduce the ToDoList component:
<template>
<div id="app">
<ToDoList></ToDoList>
</div>
</template>
<script>
import ToDoList from './components/ToDoList'
export default {
components: {
ToDoList
}
}
</script>
Copy the code
Create a new file todolist.vue under SRC /compoents.
<template>
<div class="todolist">
<header>
<h5>ToDoList</h5>
<input class="to-do-text"
v-model="toDoText"
@keyup.enter="enterText"
placeholder="Enter what you plan to do."/>
</header>
<h4 v-show="toDoList.length > 0">pending</h4>
<ul class="wait-to-do">
<li v-for="(item, index) in toDoList" :keys="item">
<p>
<i>{{index + 1}}</i>
<input :value="item" @blur="setValue(index, $event)" type="text" />
</p>
<p>
<span class="move" @click="removeToComplete(item, index)">Square root</span>
<span class="del" @click="deleteWait(index)">-</span>
</p>
</li>
</ul>
<h4 v-show="completedList.length > 0">Has been completed</h4>
<ul class="has-completed">
<li v-for="(item, index) in completedList" :keys="item">
<p>
<i>{{index + 1}}</i>
<input :value="item" disabled="true" type="text" />
</p>
<p>
<span class="move" @click="removeToWait(item, index)">x</span>
<span class="del" @click="deleteComplete(index)">-</span>
</p>
</li>
</ul>
</div>
</template>
Copy the code
<script>
export default {
data() {
return {
toDoText: ' '.toDoList: [].completedList: []}},methods: {
setValue(index, e) {
this.toDoList.splice(index, 1, e.target.value)
},
removeToComplete(item, index) {
this.completedList.splice(this.completedList.length, 0, item)
this.toDoList.splice(index, 1)
},
removeToWait(item, index) {
this.toDoList.splice(this.toDoList.length, 0, item)
this.completedList.splice(index, 1)
},
enterText() {
if (this.toDoText.trim().length > 0) {
this.toDoList.splice(this.toDoList.length, 0.this.toDoText)
this.toDoText = ' '
}
},
deleteWait(index) {
this.toDoList.splice(index, 1)
},
deleteComplete(index) {
this.completedList.splice(index, 1)}}};</script>
Copy the code
When the page is written and the requirements on the prototype are roughly developed, the page will look like this:
Modifying directory Configuration
The next step is to write the unit test file. Before writing the unit test file, change the directory of the test file to __tests__, and change the jest.config.js to the following configuration. Note that testMatch has been changed to match all the.js files in __tests__.
module.exports = {
moduleFileExtensions: [
'js'.'vue'].transform: {
'^.+\\.vue$': '<rootDir>/node_modules/vue-jest'.'^.+\\.js$': '<rootDir>/node_modules/babel-jest'
},
moduleNameMapper: {
'^ @ / (. *) $': '<rootDir>/src/$1'
},
snapshotSerializers: [
'jest-serializer-vue'].testMatch: ['**/__tests__/**/*.spec.js'].transformIgnorePatterns: ['<rootDir>/node_modules/']}Copy the code
Writing test files
In the __tests__/unit/ directory, create a new file todolist.spec.js. We are supposed to test a vue file, so its unit test file is conventionally named as *.spec.js or *.test.js.
import { shallowMount } from '@vue/test-utils'
import ToDoList from '@/components/ToDoList'
describe('test ToDoList', () => {
it('Input box starts with an empty string', () = > {const wrapper = shallowMount(ToDoList)
expect(wrapper.vm.toDoText).toBe(' ')})})Copy the code
The above test file briefly explains:
shallowMount
Will be created to contain the mounted and renderedVue
The component’sWrapper
, only stubs the current component and does not contain child components.describe(name, fn)
This is defining a test suite,test ToDoList
Is the name of the test suite,fn
Is a concrete executable functionit(name, fn)
It’s a test case,The input box starts with an empty string
Is the name of the test case,fn
Are concrete executable functions; Multiple test cases can be protected in a test suite.expect
是Jest
Built-in assertion style. There are other assertion styles in the industry such asShould
,Assert
And so on.toBe
是Jest
Provides the assertion method, more can be toJest Expect
See the usage.
it('The to-do list should start with an empty array.', () = > {const wrapper = shallowMount(ToDoList)
expect(wrapper.vm.toDoList.length).toBe(0)
})
it('Completed lists should start with an empty array', () = > {const wrapper = shallowMount(ToDoList)
expect(wrapper.vm.completedList).toEqual([])
})
Copy the code
Pending and completed lists are lists, so the fields that hold the data must be of type Array. An empty list is an empty Array. If the second test case is changed to:
expect(wrapper.vm.completedList).toBe([])
Copy the code
An error will be reported because the toBe method internally calls object.is (value1, value2) to compare whether two values are equal, which is not the same as == or ===. Obviously object.is ([], []) returns false.
it('toDoText should change when the input field value changes.', () = > {const wrapper = shallowMount(ToDoList)
wrapper.find('.to-do-text').setValue('To accompany my mother to the supermarket in the evening')
expect(wrapper.vm.toDoText).toBe('To accompany my mother to the supermarket in the evening')
})
it('There is no value in the input box. When I type enter, nothing changes.', () = > {const wrapper = shallowMount(ToDoList)
const length = wrapper.vm.toDoList.length
const input = wrapper.find('.to-do-text')
input.setValue(' ')
input.trigger('keyup.enter')
expect(wrapper.vm.toDoList.length).toBe(length)
})
it('The input field has a value. When you enter enter, the list will be added and the input field will be cleared.', () = > {const wrapper = shallowMount(ToDoList)
const length = wrapper.vm.toDoList.length
const input = wrapper.find('.to-do-text')
input.setValue('Go out to dinner')
input.trigger('keyup.enter')
expect(wrapper.vm.toDoList.length).toBe(length + 1)
expect(wrapper.vm.toDoText).toBe(' ')})Copy the code
setValue
You can set the value of a text control and update itv-model
Bound data..to-do-text
Is aCSS
The selector;Vue-Test-Utils
providesfind
Method to return one by looking at the selectorWrapper
; The selector could beCSS
Selector, can beVue
A component can also be an object that contains the component’sname
或ref
Property, for example:wrapper.find({ name: 'my-button' })
wrapper.vm
Is aVue
Instance, onlyVue
Component wrappers are availablevm
This property; throughwrapper.vm
Access to allVue
The properties and methods of the instance. Such as:wrapper.vm.$data
,wrapper.vm.$nextTick()
.trigger
Method can be used to trigger aDOM
Event, the events that are fired here are synchronous, so you don’t have to put an assertion in$nextTick()
To carry out; It also supports passing in an object. When an event is captured, the properties of the passed object can be retrieved. It could be written like this:wrapper.trigger('click', {name: "bubuzou.com"})
it('The toDoList array is updated after editing.', () = > {const wrapper = shallowMount(ToDoList)
wrapper.setData({toDoList: ['Run for half an hour']})
wrapper.find('.wait-to-do li').find('input').setValue('Run around the park three times')
wrapper.find('.wait-to-do li').find('input').trigger('blur')
expect(wrapper.vm.toDoList[0]).toBe('Run around the park three times')})Copy the code
SetData is used to set an initial value for toDoList to render a list item. Then find the list item and set its value with setValue, simulating editing; The list item input field is bound with :value=”item”, so setValue cannot trigger an update; Updates to the toDoList value can only be triggered using the trigger.
it('Click to delete the toDoList list while updating the toDoList array', () = > {const wrapper = shallowMount(ToDoList)
wrapper.setData({toDoList: ['Read an hour before bed']})
expect(wrapper.vm.toDoList.length).toBe(1)
wrapper.find('.wait-to-do li').find('.del').trigger('click')
expect(wrapper.vm.toDoList.length).toBe(0)
})
it('Click the completed button for an item in the backlog, and the data will be updated.', () = > {const wrapper = shallowMount(ToDoList)
wrapper.setData({toDoList: ['Eat an apple after lunch']})
expect(wrapper.vm.toDoList.length).toBe(1)
expect(wrapper.vm.completedList.length).toBe(0)
wrapper.find('.wait-to-do li').find('.move').trigger('click')
expect(wrapper.vm.toDoList.length).toBe(0)
expect(wrapper.vm.completedList.length).toBe(1)
})
it('Click the unfinished button for an item in the completed list to update the data.', () = > {const wrapper = shallowMount(ToDoList)
wrapper.setData({completedList: ['Sang a song']})
expect(wrapper.vm.toDoList.length).toBe(0)
expect(wrapper.vm.completedList.length).toBe(1)
wrapper.find('.has-completed li').find('.move').trigger('click')
expect(wrapper.vm.toDoList.length).toBe(1)
expect(wrapper.vm.completedList.length).toBe(0)
})
it('List sequence increments from 1', () = > {const wrapper = shallowMount(ToDoList)
wrapper.setData({toDoList: ['Do your homework in the morning'.'Go shopping in the afternoon']})
expect(wrapper.vm.toDoList.length).toBe(2)
expect(wrapper.find('.wait-to-do').html()).toMatch('<i>1</i>')
expect(wrapper.find('.wait-to-do').html()).toMatch('<i>2</i>')
})
it('Do not display the backlog when the backlog list is empty', () = > {const wrapper = shallowMount(ToDoList)
wrapper.setData({toDoList: []})
expect(wrapper.find('h4').isVisible()).toBeFalsy()
wrapper.setData({toDoList: ['Climb the North Mountain tomorrow.']})
expect(wrapper.find('h4').isVisible()).toBeTruthy()
})
Copy the code
You can write more than one Expect in a test case to ensure that assertions are accurate.
Asynchronous test
Finally, to simulate asynchronous testing, we added a requirement that the page load request the remote backlog data. Create a new __mocks__ directory at the project root and create axios.js:
const toToList = {
success: true.data: ['Reading in the library in the morning'.'Go down and go shopping']}export const get = (url) = > {
if (url === 'toToList.json') {
return new Promise((resolve, reject) = > {
if (toToList.success) {
resolve(toToList)
} else {
reject(new Error())}}Copy the code
Modify todolist. vue, import axios and add Mounted:
<script>
import * as axios from '.. /.. /__mocks__/axios'
export default {
mounted () {
axios.get('toToList.json').then(res= > {
this.toDoList = res.data
}).catch(err= >{})}};</script>
Copy the code
The test case is written as:
it('When the page is mounted to request data, it should return 2 pieces of data when the request succeeds.', (done) => {
wrapper.vm.$nextTick((a)= > {
expect(wrapper.vm.toDoList.length).toBe(2)
done()
})
})
Copy the code
For asynchronous code, write the assertion in wrapper.vm.$nextTick() and manually call done().
Configuring Test Coverage
If we look at what the coverage is, we need to configure the test coverage. Add configuration in jest.config.js:
collectCoverage: true.collectCoverageFrom: ["**/*.{js,vue}".! "" **/node_modules/**"].Copy the code
Add a configuration in the package.json scripts:
"test:cov": "vue-cli-service test:unit --coverage"
Copy the code
Then we run: NPM run test:cov on the terminal. The result is as follows:
Running the test coverage name will generate the coverage directory in the project root directory, and the browser will open the index. HTML inside: