How to do unit testing in the Vue project of the front end grab job series

How to do unit testing in the Vue project of the front end grab job series

Regarding unit testing, the most common question should be "Is front-end unit testing necessary?" Through this article, you will understand the necessity of unit testing and how to fully and reliably test the components we wrote in the Vue project .

This article was first published on the public account

Front-end Reading
For more exciting content, please pay attention to the latest news on the official account.

The need for unit testing

Generally, in our impression, unit testing is the work of test engineers, and the front-end is responsible for the code; Baidu searches for Vue unit testing, and all the associated words are

"Is unit testing necessary?"
"What does unit testing do?"
Although we usually have test engineers to test our pages in our usual projects, but according to my observation, general test engineers do not cover all business logic, and there are some deep-level code logic test engineers in It is impossible to trigger at all without understanding the code. Therefore, in this case, we cannot completely rely on test engineers to test our projects, and unit testing of front-end projects is very necessary.

And unit testing can also help us save a large part of the cost of self-testing. If we have an order display component, we will display the corresponding copy based on the order status and other business logic; we want to view the copy on the page If the display is correct, you need to fill in the order information before you can view it; if you add some new logical judgments the next day (the order you placed the day before has expired), then you have three choices , The first option is to fill in the order and pay again cumbersomely (and provide financial support to the boss), the second option is to ask the back-end colleague to change the order status for you (the back-end colleague will give you a blank eye to yourself) Experience), the third option is to proxy the interface or use mock data (you need to compile the entire project and run it for testing).

At this time, unit testing provides a fourth lower-cost test method. Write a test case to test our components and determine whether the copy is displayed in the way we expect; this method does not require dependency. The assistance of the end does not require any changes to the project, which can be said to save time and effort.

Test framework and assertion library

Speaking of unit testing, let's first introduce popular testing frameworks, mainly mocha and jest. Let me briefly introduce mocha, which is translated into Chinese

Mocha
(People are a kind of coffee! Not matcha), the origin of the name is guessed because developers like to drink mocha coffee, just like the Java name is also derived from coffee, the logo of mocha is also a cup of mocha coffee:

Compared with jest, the main difference between the two is that jest has a built-in assertion library with a relatively high level of integration.

expect.js
, And mocha needs to be equipped with an additional assertion library, generally the more popular ones are chosen
chai
As an assertion library, the assertion library has been mentioned here, so what is an assertion library? Let s first look at how mocha tests the code. 1. we wrote a
addNum function
, But not sure whether it returns the result we want, so we need to test this function:

//src/index.js function addNum ( a, b ) { return a + b; } module .exports = addNum; copy the code

Then we can write our test files. All test files are placed in the test directory. Generally, the test file and the source file to be tested will have the same name to facilitate the correspondence. When running mocha, all js files in the test directory will be automatically changed. carry out testing:

//test/index.test.js var addNum = require ( "../src/index" ); describe( "Test addNum function" , () => { it( "The result of adding two numbers is the sum of two numbers" , () => { if (addNum( 1 , 2 ) !== 3 ) { throw new Error ( "The result of adding two numbers is not two numbers " ); } }); }); Copy code

The above code is the syntax of the test script, a test script will include one or more

describe
Blocks, each
describe
Includes one or more
it
Block here
describe
Called
Test suite
(Test suite), represents a set of related tests, it contains two parameters, the first parameter is this
Test suite
The name of the second parameter is the function that is actually executed.

and

it
Called
Test case
, Which means a single test, is the smallest unit of test, it also contains two parameters, the first parameter is
Test case
The name of the second parameter is the function that is actually executed.

it
The block is the code we need to test. If the running result is not what we expected, an exception will be thrown; after the above test case is written, we can run the test.

The result of the operation is passed, which is the result we want, indicating that our function is correct; but every time we judge by throwing an exception, it is somewhat tedious, and the assertion library appears; the purpose of the assertion is to run the test code Then compare with our expectations. If it is consistent with expectations, it means that there is no problem with the code; if it is inconsistent with expectations, there is a problem with the code; each test case will have an assertion at the end to judge, if there is no assertion, the test is meaningless Up.

As mentioned above, mocha generally matches the Chai assertion library, and chai has several assertion styles. The more common styles are should and expect. Let s look at these two assertions separately:

var chai = require ( "chai" ), expect = chai.expect, should = chai.should(); describe( "Test addNum function" , () => { it( "1+2" , () => { addNum( 1 , 2 ).should.equal( 3 ); }); it( "2+3" , () => { expect(addNum( 2 , 3 )).to.be.equal( 5 ); }); }); Copy code

Here should is the post-position, after the variable is asserted, and the expect is the pre-position. As the beginning of the assertion, the two styles are purely a matter of personal preference; we find that here expect is a function obtained from chai, while should is direct Call, this is because should actually expands all objects

getter
Attributes
should
, So we can use it on the variable
.should
Way to make an assertion.

Unlike chai's multiple assertion styles, jest has a built-in assertion library expect, and its syntax is somewhat different:

describe( "Test addNum function" , () => { it( "1+2" , () => { expect(addNum( 1 , 2 )).toBe( 3 ); }); it( "2+3" , () => { expect(addNum( 2 , 3 )).toBe( 5 ); }); }); Copy code

The expect in jest passes directly

toBe
The syntax of is more concise than mocha in form; the two frameworks are very similar in use, for example, both support in asynchronous code
done
Callback and
async/await
Keywords, assertion syntax and other usages are somewhat different; both have the same hook mechanism, even the names are the same beforeEach and afterEach; when creating a project in vue cli scaffolding, you can also choose one of the two frameworks, here we are Mainly use jest for testing.

Jest

Jest is a testing framework produced by Facebook. Compared with other testing frameworks, the biggest feature is that it has built-in commonly used testing tools, such as its own assertion and test coverage tools, which can be used out of the box. This is also the same as its official slogan matches.

Jest is a delightful JavaScript testing framework that focuses on

Concise
.

Jest is almost zero configuration, it will automatically identify some commonly used test files, such as

*.spec.js
with
*.test.js
Suffix test script, all test scripts are placed in
tests
or
__tests__
Directory; we can install jest globally or locally, and then specify the test script in packages.json:

{ "scripts" : { "test" : "jest" } } Copy code

When we run

npm run test
All test files in the test directory will be automatically run at time to complete the test; we may also see test cases written through the test function on the jest official website:

test( "1+2" , () => { expect(addNum( 1 , 2 )).toBe( 3 ); }); Copy code

Same as it function, test function also represents a test case, mocha only supports

it
, And jest supports
it
with
test
, Here in order to maintain unity with jest official website, the following code is used uniformly
test
function.

Matcher

We often need to perform a matching test on the value returned by the test code. The above code

toBe
It is the simplest matcher to test whether two values are the same.

test( "test tobe" , () => { expect( 2 + 2 ).toBe( 4 ); expect( true ).toBe( true ); const val = "team" ; expect(val).toBe( "team" ); expect( undefined ).toBe( undefined ); expect( null ).toBe( null ); }); Copy code

The toBe function is used internally

Object.is
For exact matching, its characteristics are similar to
===
; For common types of values can be compared, but for complex types such as object arrays, you need to use
toEqual
Come to compare:

test( "expect a object" , () => { var obj = { a : "1" , }; obj.b = "2" ; expect(obj).toEqual({ a : "1" , b : "2" }); }); test( "expect array" , () => { var list = []; list.push( 1 ); list.push( 2 ); expect(list).toEqual([ 1 , 2 ]); }); Copy code

We sometimes need to match undefined, null and other types or the true and false of expressions in conditional statements. Jest also has five functions to help us:

  • toBeNull: only match null
  • toBeUndefined: only match undefined
  • toBeDefined: Contrary to toBeUndefined, it is equivalent to .not.toBeUndefined
  • toBeTruthy: match any if statement is true
  • toBeFalsy: match any if statement is false
test( "null" , () => { const n = null ; expect(n).toBeNull(); expect(n).not.toBeUndefined(); expect(n).toBeDefined(); expect(n).not.toBeTruthy(); expect(n).toBeFalsy(); }); test( "0" , () => { const z = 0 ; expect(z).not.toBeNull(); expect(z).not.toBeUndefined(); expect(z).toBeDefined(); expect(z).not.toBeTruthy(); expect(z).toBeFalsy(); }); test( "undefined" , () => { const a = undefined ; expect(a).not.toBeNull(); expect(a).toBeUndefined(); expect(a).not.toBeDefined(); expect(a).not.toBeTruthy(); expect(a).toBeFalsy(); }); Copy code

toBeTruthy and toBeFalsy are used to determine whether the expression in the if statement is true, equivalent to `if(n)

with
if(!n)'' judgment.

For numerical data, we can sometimes judge by greater than or less than:

test( "number" , () => { const val = 2 + 2 ; //greater than expect(val).toBeGreaterThan( 3 ); //greater than or equal to expect(val).toBeGreaterThanOrEqual( 3.5 ); //less than expect( val).toBeLessThan( 5 ); //less than or equal to expect(val).toBeLessThanOrEqual( 4.5 ); //fully judge expect(val).toBe( 4 ); expect(val).toEqual( 4 ); }); Copy code

Although we can also use toBe and toEqual to compare floating-point data, if we encounter some special floating-point data calculations, such as 0.1+0.2, there will be problems. We can pass

toBeCloseTo
To judge:

test( "float" , () => { //expect(0.1 + 0.2).toBe(0.3); error report expect( 0.1 + 0.2 ).toBeCloseTo( 0.3 ); }); Copy code

For data of iterable types such as arrays, sets, or strings, you can pass

toContain
To judge whether there is a certain item inside:

Test ( "Expect Iterable" , () => { const ShoppingList = [ "Diapers" , "Kleenex" , "Trash Bags" , "Paper Towels" , "Milk" , ]; expect(shoppingList).toContain( "milk" ); expect( new Set (shoppingList)).toContain( " diapers " ); expect( "abcdef" ).toContain( "cde" ); }); Copy code

Asynchronous code

Asynchronous code is often involved in our projects. For example, setTimeout, interface requests, etc. will involve asynchrony, so how can these asynchronous codes be tested? Suppose we have a function that fetches data asynchronously

fetchData
:

export function fetchData ( cb ) { setTimeout ( () => { cb( "res data" ); }, 2000 ); } Copy code

After 2 seconds, a string is returned through the callback function. We can use one in the function of the test case

done
Jest will wait for the done callback before completing the test:

test( "callback" , ( done ) => { function cb ( data ) { try { expect(data).toBe( "res data" ); done(); } catch (error) { done(); } } fetchData(cb); }); Copy code

We pass a callback function to fetchData, and make an assertion on the returned data in the callback function. After the assertion ends, we need to call done; if done is not called at the end, then Jest does not know when it will end, and an error will be reported; in our daily code , Will get data through promises, and transfer our

fetchData
Rewrite:

export function fetchData () { return new Promise ( ( resolve, reject ) => { setTimeout ( () => { resolve( "promise data" ); }, 2000 ); }); } Copy code

Jest supports returning a promise directly in the test case, and we can make an assertion in the then:

test( "promise callback" , () => { return fetchData().then( ( res ) => { expect(res).toBe( "promise data" ); }); }); Copy code

In addition to returning fetchData directly, we can also use it in the assertion

.resolves/.rejects
Matching characters, Jest will also wait for the promise to end:

test( "promise callback" , () => { return expect(fetchData()).resolves.toBe( "promise data" ); }); Copy code

In addition, Jest also supports

async/await
, But we need to add
async modifier
Means:

test( "async/await callback" , async () => { const data = await fetchData(); expect(data).toBe( "promise data" ); }); Copy code

Global mount and unmount

Global mounting and unmounting are somewhat similar to Vue-Router's global guards, which do some operations before and after each navigation is triggered; there are also in Jest, for example, we need to initialize some data before each test case, or in each test Clear the data after the use case, you can use

beforeEach
with
afterEach
:

let cityList = [] beforeEach( () => { initializeCityDatabase(); }); afterEach( () => { clearCityDatabase(); }); test( "city data has suzhou" , () => { expect(cityList).toContain( "suzhou" ) }) test( "city data has shanghai" , () => { expect(cityList).toContain( "suzhou" ) }) Copy code

In this way, init will be called before each test case is tested, and clear will be called after each end; we may

test
Change the data in cityList, but in
beforeEach
After the initialization operation, the cityList data obtained by each test case is guaranteed to be the same; the same as the asynchronous code in the previous section, in
beforeEach
with
afterEach
We can also use asynchronous code to initialize:

let cityList = [] beforeEach( () => { return initializeCityDatabase().then( ( res )=> { cityList = res.data }); }); //Or use async/await beforeEach( async () => { cityList = await initializeCityDatabase(); }); Copy code

with

beforeEach
with
afterEach
The corresponding is
beforeAll
with
afterAll
, The difference is
beforeAll
with
afterAll
Will only be executed once;
beforeEach
with
afterEach
The default will be applied to each test, but we may wish to only target certain tests, we can pass
describe
Put these tests together so that they only apply to
describe
The test in the block:

beforeEach( () => { //apply to all tests }); describe( "put test together" , () => { beforeEach( () => { //Only apply the test in the current describe block }); test( "test1" , ()=> {}) test( "test2" , ()=> {}) }); Copy code

Simulation function

In a project, a function of a module often calls a function of another module. In unit testing, we may not need to care about the execution process and results of internally called functions, but only want to know whether the function of the called module is called correctly, and even specify the return value of the function, so it is necessary to simulate the function.

If we are testing a function forEach, its parameters include a callback function that acts on each element of the array:

export function forEach ( items, callback ) { for ( let index = 0 ; index <items.length; index++) { callback(items[index]); } } Copy code

In order to test this forEach, we need to build a simulation function to check whether the simulation function is called as expected:

test( "mock callback" , () => { const mockCallback = jest.fn( ( x ) => 42 + x); forEach([ 0 , 1 , 2 ], mockCallback); expect(mockCallback.mock.calls.length).toBe( 3 ); expect(mockCallback.mock.calls[ 0 ][ 0 ]).toBe( 0 ); expect(mockCallback.mock.calls[ 1 ][ 0 ]).toBe( 1 ); expect(mockCallback.mock.calls[ 2 ][ 0 ]).toBe( 1 ); expect(mockCallback.mock.results[ 0 ].value).toBe( 42 ); }); Copy code

We found that there is a special

.mock
Property, it saves the information that the simulation function is called; let's print it out to see:

It has four attributes:

  • calls: call parameters
  • instances: this points to
  • invocationCallOrder: function call order
  • results: call result

There is one in the above attributes

instances
Attribute, which indicates the this point of the function, we can also pass
bind
Function to change the this of our simulated function:

test( "mock callback" , () => { const mockCallback = jest.fn( ( x ) => 42 + x); const obj = { a : 1 }; const bindMockCallback = mockCallback.bind(obj); forEach([ 0 , 1 , 2 ], bindMockCallback); expect(mockCallback.mock.instances[ 0 ]).toEqual(obj); expect(mockCallback.mock.instances[ 1 ]).toEqual(obj); expect(mockCallback.mock.instances[ 2 ]).toEqual(obj); }); Copy code

After changing the this of the function through bind, we can use

instances
To perform detection; the simulation function can inject the return value at runtime:

const myMock = jest.fn(); //undefined console .log(myMock()); myMock .mockReturnValueOnce( 10 ) .mockReturnValueOnce( "x" ) .mockReturnValue( true ); //10 x true true console .log(myMock(), myMock(), myMock(), myMock()); myMock.mockReturnValueOnce( null ); Null to true to true// Console .log (myMock (), myMock (), myMock ()); duplicated code

We execute myMock for the first time, since no return value is injected, and then pass

mockReturnValueOnce
with
mockReturnValue
For return value injection, Once will only be injected once; it is very useful for analog functions to use injection when the continuity function passes the return value:

const filterFn = jest.fn(); filterFn.mockReturnValueOnce( true ).mockReturnValueOnce( false ); const result = [ 2 , 3 ].filter( ( num ) => filterFn(num)); expect(result).toEqual([ 2 ]); Copy code

We can also make an assertion on the call of the simulated function:

const mockFunc = jest.fn(); //Assert that the function has not been called expect(mockFunc).not.toHaveBeenCalled(); mockFunc( 1 , 2 ); mockFunc( 2 , 3 ); //Assert that the function is called at least once expect(mockFunc).toHaveBeenCalled(); //Assert function call parameters expect(mockFunc).toHaveBeenCalledWith( 1 , 2 ); the Expect (mockFunc) .toHaveBeenCalledWith ( 2 , 3 ); the last parameter of the function call//assert the Expect (mockFunc) .toHaveBeenLastCalledWith ( 2 , 3 ); Copy the code

In addition to simulating functions, Jest also supports intercepting axios return data. If we have an interface to obtain users:

///src/api/users const axios = require ( "axios" ); function fetchUserData () { return axios .get( "/user.json" ) .then( ( resp ) => resp.data); } module .exports = { fetchUserData, }; Copy code

Now we want to test

fetchUserData
The function gets the data but does not actually request the interface, we can use
jest.mock
To simulate the axios module:

const users = require("../api/users"); const axios = require("axios"); jest.mock("axios"); test("should fetch users", () => { const userData = { name: "aaa", age: 10, }; const resp = { data: userData }; axios.get.mockResolvedValue(resp); return users.fetchUserData().then((res) => { expect(res).toEqual(userData); }); });

Once we simulate the module, we can use the get function to provide a mockResolvedValue method to return the data we need to test; after the simulation, axios does not actually send a request to get it.

/user.json
The data.

Vue Test Utils

Vue Test Utils is the official unit test utility library of Vue.js, which can test the Vue components we write.

Mount components

In Vue we pass

import
Introduce the components, and then
components
It can be used after registration; in unit testing, we use
mount
To mount the component; if we write a counter component
counter.js
, Used to display count, and there is a button to operate count:

<!-- Counter.vue --> <template> <div class="counter"> <span class="count">{{ count }}</span> <button id="add" @click="add"> </button> </div> </template> <script> export default { data() { return { count : 0 , }; }, methods : { add () { this .count++; }, }, }; </script > copy code

After the component is mounted, a wrapper is obtained. The wrapper exposes many convenient methods for encapsulating, traversing, and querying its internal Vue component instances.

Import {} Mount from "@ VUE/Test-utils" ; Import Counter from "@/Components/Counter" ; const warpper = Mount (Counter); const VM = wrapper.vm; duplicated code

We can pass

wrapper.vm
To access the Vue instance of the component, and then get the methods and data on the instance; through the wrapper, we can make assertions about the rendering of the component:

//test/unit/counter.spec.js describe("Counter", () => { const wrapper = mount(Counter); test("counter class", () => { expect(wrapper.classes()).toContain("counter"); expect(wrapper.classes("counter")).toBe(true); }); test("counter has span", () => { expect(wrapper.html()).toContain("<span class="count">0</span>"); }); test( "counter has btn" , () => { expect(wrapper.find( "button#add" ).exists()).toBe( true ); expect(wrapper.find( "button#add" ).exists()).not.toBe( false ); }); }); Copy code

We can guess what the above functions do based on their names:

  • classes: Get the wrapper class and return an array
  • html: Get component rendering html structure string
  • find: Returns the wrapper that matches the child element
  • exists: Assert whether the wrapper exists

What find returns is the first DOM node found, but in some cases we want to be able to manipulate a set of DOM, we can use

findAll
function:

const wrapper = mount(Counter); //return a set of wrappers const divList = wrapper.findAll( 'div' ); divList.length //find the first div, it returns warpper const firstDiv = divList.at ( 0 ); duplicated code

props slots provide/inject mount

const wrapper = mount(Component, { // data data data() { return { foo: "bar" } }, // props propsData: { msg: "hello" }, //vue localVue, // mocks: { $route }, // // slot // slots: { default: SlotComponent, foo: "<div/>", bar: "<my-component/>", baz: "" }, //Used to register custom component stubs : { "my-component" : MyComponent, "el-button" : true , }, //Set the $attrs object of the component instance. attrs : {}, //Set the $listeners object of the component instance. listeners : { click: jest.fn() }, //Pass the attribute for injection to the component provide : { foo() { return "fooValue" } } }) Copy code

stubs
Mainly used to process custom components registered globally, such as our commonly used component library Element, etc., directly use
el-button
,
el-input
Component, or vue-router registered in the global
router-view
Components, etc.; when we introduce in the unit test, we will be prompted that the corresponding component can not be found, then we can pass this
stubs
To avoid errors.

When we unit test a component, we hope to test only a single component to avoid the side effects of sub-components; for example, we are

ParentComponent
When judging whether there is a div, it happens to be a child component
ChildComponent
The div is also rendered, then it will cause some interference to our test; we can use
shallowMount
Mount function, meet more than mount, shallowMount will not render sub-components:

import { shallowMount } from '@vue/test-utils' const wrapper = shallowMount(Component)

data Form

<template> <div class="form"> <div class="title">{{ title }}</div> <div> <span> </span> <input type="text" id="name-input" v-model="name"/> <div class="name">{{ name }}</div> </div> <div> <span> </span> <input type="radio" name="sex" v-model="sex" value="f" id=""/> <input type="radio" name="sex" v-model="sex" value="m" id=""/> </div> <div> <span> </span> footbal <input type="checkbox" name="hobby" v-model="hobby" value="footbal" /> basketball <input type="checkbox" name="hobby" v-model="hobby" value="basketball" /> ski <input type="checkbox" name="hobby" v-model="hobby" value="ski"/> </div> <div> <input :class="submit ? 'submit' : ''" type="submit" value=" " @click="clickSubmit" /> </div> </div> </template> <script> export default { name: "Form", props: { title: { type: String, default: " ", }, }, data() { return { name: "", sex: "f", hobby: [], submit: false, }; }, methods: { clickSubmit() { this.submit = !this.submit; }, }, }; </script>

Form title input radio checkbox props

propsData
setProps
props

const wrapper = mount(Form, { propsData: { title: "form title", }, }); const vm = wrapper.vm; test("change prop", () => { expect(wrapper.find(".title").text()).toBe("form title"); wrapper.setProps({ title: "new form title", }); // expect(wrapper.find(".title").text()).toBe("new form title"); });

Vue prop data dom

$nextTick
nextTick DOM

test("change prop1", async () => { expect(wrapper.find(".title").text()).toBe("new form title"); wrapper.setProps({ title: "new form title1", }); await Vue.nextTick(); // vm nextTick //await wrapper.vm.nextTick(); expect(wrapper.find(".title").text()).toBe("new form title1"); }); test("change prop2", (done) => { expect(wrapper.find(".title").text()).toBe("new form title1"); wrapper.setProps({ title: "new form title2", }); Vue.nextTick(() => { expect(wrapper.find(".title").text()).toBe("new form title2"); done(); }); });

Jest

done
async/await
props
setData
wrapper data

test("test set data", async () => { wrapper.setData({ name: "new name", }); expect(vm.name).toBe("new name"); await Vue.nextTick(); expect(wrapper.find(".name").text()).toBe("new name"); });

input textarea select

test("test input set value", async () => { const input = wrapper.find("#name-input"); await input.setValue("change input by setValue"); expect(vm.name).toBe("change input by setValue"); expect(input.element.value).toBe("change input by setValue"); }); // test("test input trigger", () => { const input = wrapper.find("#name-input"); input.element.value = "change input by trigger"; // input.element.value trigger input.trigger("input"); expect(vm.name).toBe("change input by trigger"); });

input.element.value
setValue
v-model vm data
input.element.value
input

radio checkbox

setChecked(Boolean)
v-model

test("test radio", () => { expect(vm.sex).toBe("f"); const radioList = wrapper.findAll('input[name="sex"]'); radioList.at(1).setChecked(); expect(vm.sex).toBe("m"); }); test("test checkbox", () => { expect(vm.hobby).toEqual([]); const checkboxList = wrapper.findAll('input[name="hobby"]'); checkboxList.at(0).setChecked(); expect(vm.hobby).toEqual(["footbal"]); checkboxList.at(1).setChecked(); expect(vm.hobby).toEqual(["footbal", "basketball"]); checkboxList.at(0).setChecked(false); expect(vm.hobby).toEqual(["basketball"]); });

trigger

test("test click", async () => { const submitBtn = wrapper.find('input[type="submit"]'); await submitBtn.trigger("click"); expect(vm.submit).toBe(true); await submitBtn.trigger("click"); expect(vm.submit).toBe(false); });

$emit
Form submit

{ methods: { clickSubmit() { this.$emit("foo", "foo1", "foo2"); this.$emit("bar", "bar1"); }, }, }

$emi

wrapper.vm
vm
this

wrapper.vm.$emit("foo", "foo3");

$emit

wrapper.emitted()

{ foo: [ [ 'foo1', 'foo2' ], [ 'foo3' ] ], bar: [ [ 'bar1' ] ] }

emitted()
length emit

test("test emit", async () => { // emit await wrapper.find('input[type="submit"]').trigger("click"); wrapper.vm.$emit("foo", "foo3"); await vm.$nextTick(); //foo expect(wrapper.emitted().foo).toBeTruthy(); //foo expect(wrapper.emitted().foo.length).toBe(2); // foo expect(wrapper.emitted().foo[0]).toEqual(["foo1", "foo2"]); //baz expect(wrapper.emitted().baz).toBeFalsy(); });

emitted()
emitted

expect(wrapper.emitted('foo')).toBeTruthy(); expect(wrapper.emitted('foo').length).toBe(2);

emit vm emit

import { mount } from '@vue/test-utils' import ParentComponent from '@/components/ParentComponent' import ChildComponent from '@/components/ChildComponent' describe('ParentComponent', () => { it("emit", () => { const wrapper = mount(ParentComponent) wrapper.find(ChildComponent).vm.$emit('custom') }) })

Vue-Router

Vue-Router
Api Header

<template> <div> <div @click="jump">{{ $route.params.id }}</div> <router-link :to="{ path: '/detail' }"></router-link> <router-view></router-view> </div> </template> <script> export default { data() { return {}; }, mounted() {}, methods: { jump() { this.$router.push({ path: "/list", }); }, }, }; </script>

router-link
router-view
$route
Vue.use(VueRouter)
Vue
createLocalVue
Vue Vue

import { shallowMount, createLocalVue } from '@vue/test-utils' import VueRouter from 'vue-router' import Header from "@/components/Header"; // Vue const localVue = createLocalVue() localVue.use(VueRouter) // const routes = [] const router = new VueRouter({ routes }) shallowMount(Header, { localVue, router })

createLocalVue
localVue
import Vue
localVue.use
Vue VueRouter
Vue.use
router
shallowMount

mocks
stubs
mocks
route route router
Vue.prototype
stubs

import { mount } from "@vue/test-utils"; import Header from "@/components/Header"; describe("header", () => { const $route = { path: "/home", params: { id: "111", }, }; const $router = { push: jest.fn(), }; const wrapper = mount(Header, { stubs: ["router-view", "router-link"], mocks: { $route, $router, }, }); const vm = wrapper.vm; test("render home div", () => { expect(wrapper.find("div").text()).toBe("111"); }); });

$route

Vuex

vuex

store
count vuex

<template> <div> <div class="number">{{ number }}</div> <div class="add" @click="clickAdd">add</div> <div class="sub" @click="clickSub">sub</div> </div> </template> <script> import { mapState, mapGetters } from "vuex"; export default { name: "Count", computed: { ...mapState({ number: (state) => state.number, }), }, methods: { clickAdd() { this.$store.commit("ADD_COUNT"); }, clickSub() { this.$store.commit("SUB_COUNT"); }, }, }; </script>

vuex

mutations
number

export default new Vuex.Store({ state: { number: 0, }, mutations: { ADD_COUNT(state) { state.number = state.number + 1; }, SUB_COUNT(state) { state.number = state.number - 1; }, } });

store
Vue-Router
createLocalVue
Vue

import { mount, createLocalVue } from "@vue/test-utils"; import Count from "@/components/Count"; import Vuex from "vuex"; const localVue = createLocalVue(); localVue.use(Vuex); describe("count", () => { const state = { number: 0, }; const mutations = { ADD_COUNT: jest.fn(), SUB_COUNT: jest.fn(), }; const store = new Vuex.Store({ state, mutations }); test("render", async () => { const wrapper = mount(Count, { store, localVue, }); expect(wrapper.find(".number").text()).toBe("0"); wrapper.find(".add").trigger("click"); expect(mutations.ADD_COUNT).toHaveBeenCalled(); expect(mutations.SUB_COUNT).not.toHaveBeenCalled(); }); });

VueRouter

localVue
new Vuex.Store
store state mutations mutations mutations mutations

store
store Vuex Vuex
store/list.js

export default { state: { list: [], }, getters: { joinList: (state) => { return state.list.join(","); }, }, mutations: { PUSH(state, payload) { state.list.push(payload); }, }, };
import { createLocalVue } from "@vue/test-utils"; import Vuex from "vuex"; import { cloneDeep } from "lodash"; import listStore from "@/store/list"; describe("list", () => { test("expect list", () => { const localVue = createLocalVue(); localVue.use(Vuex); const store = new Vuex.Store(cloneDeep(listStore)); expect(store.state.list).toEqual([]); store.commit("PUSH", "1"); expect(store.state.list).toEqual(["1"]); }); test("list getter", () => { const localVue = createLocalVue(); localVue.use(Vuex); const store = new Vuex.Store(cloneDeep(listStore)); expect(store.getters.joinList).toBe(""); store.commit("PUSH", "1"); store.commit("PUSH", "3"); expect(store.getters.joinList).toBe("1,3"); }); });

store store commit getters