preface

A comprehensive introduction to testing a VUE component using JEST. (For those of you who don’t know how to get started with VUE unit testing, check out the previous article VUE Unit Testing — Getting started on the Testing Journey)

Highlights components registered in the form of the vue.extend constructor, including:

  • Test timer function
  • testHTTPrequest
  • Test events

Wait for the introduction of these parts

Like the code at 👍 on Github

The test component

Testing a component is actually testing the method of the component and the module on which the method depends. Testing a component method is simple: call it and assert that it affects the component’s output correctly.

Starting with an example, test a progress bar component

<template>
  <div class="Progress-Bar" :class="{hidden: hidden}">
  </div>
</template>

<script>
export default {
  name: 'ProgressBar',
  data () {
    return {
      hidden: true
    }
  },
  methods: {
    start () {
      this.hidden = false;
    },
    finally () {
      this.hidden = true;
    }
  },
}
</script>
Copy the code

The code is self-explanatory; when the start method is called, the progress bar should be displayed; The progress bar should be masked when the finally method is called.

import { shallowMount } from '@vue/test-utils'; import ProgressBar from '@/views/ProgressBar.vue'; describe('test progress', () => { it('when start is clicked, show the progressBar', () => { const wrapper = shallowMount(ProgressBar); expect(wrapper.classes()).toContain('hidden'); wrapper.vm.start(); wrapper.vm.finally(); expect(wrapper.classes()).toContain('hidden'); })})Copy the code

If yarn test:unit is run, the test passes.

This is a simple component test.

Vue instance adds attributes

In fact, a very common component pattern is to add properties to a Vue instance. As mentioned in the previous article, using the VUE “dynamic” case study component as an example, the specific development of the component is not described, the code is in the code on Github. (Go to the Unitest folder and run YARN Serve.)

Function: Click the button to trigger the handleCheck event, and the alarm popup window will pop up. The popup ID is added by 1 on the basis of primaryId. For example:

describe('test alarm', () => {
  it('when handleCheck is clicked, show the alarm', () => {
    const wrapper = shallowMount(Home);
    const count = wrapper.vm.primaryId;
    wrapper.vm.handleCheck();
    const newCount = wrapper.vm.primaryId;
    expect(newCount).toBe(count + 1)
  })
})
Copy the code

runyarn test:uniErrors occur

This is because the component is mounted directly in the test and the component instance is usedVue.extendFunction created in themain.jsIntroduce and add toVueIn the prototype of. In other words,main.jsIf it is not executed, the component is not created,$alarmProperties are never added.

You need to add attributes to the Vue instance before loading the component into the test. This can be done using Mocks.

shallowMount(Home, {
  mocks: {
    $alarm: () => {}
  }
})
Copy the code

Run YARN test: UNI again and it passes perfectly.

Test timer function

Timer functions, including JavaScript asynchronous functions, are common features in the front end, so you need to test the corresponding code. Instead of waiting for the timer function to run out, replace the global timer function with Jest. UseFakeTimers. After the replacement, you can advance the time with runTimersToTime.

Test setTimeout

Function: After the handleCheck event is triggered, the ALARM component ID will be unshift to the idList array. After 3 seconds, the component will be destroyed and the idList will delete its ID. For example:

It (' When handleCheck is clicked, 3second later alarm would be black ', () => { Replace the global timer function jest. UseFakeTimers (); const wrapper = shallowMount(Home, { mocks: { $alarm: () => {} } }); wrapper.vm.handleCheck(); expect(wrapper.vm.idList.length).toBe(1); Jest. RunTimersToTime (3000); expect(wrapper.vm.idList.length).toBe(0); })Copy the code

Test clearTimeout

Function: When more than one alarm popover occurs, clearTimeout is called to destroy the previous timer. Listen for clearTimeout to be called.

The toHaveBeenCalledWith allocator is used to test whether the Spy is called with the specified parameters. The toHaveBeenCalledWith allocator is used to test whether the Spy is called with the specified parameters.

So in your tests, you need to get the return value of setTimeout, and Jest. MockReturnValue can do this. MockReturnValue setTimeout return values can be set to any value, such as the return value is set to 123: setTimeout. MockReturnValue (123) under the test such as:

it('when handleCheck is clicked and the number of alarm exceeds 1 , The previous alarm disappears immediately', () => {// listen to clearTimeout jest. SpyOn (window, 'clearTimeout') const wrapper = shallowMount(Home, {$alarm: () => {}}}); / / set the setTimeout return to 123 setTimeout. MockReturnValue (123) wrapper. Vm. HandleCheck (); / / set the setTimeout return to 456 setTimeout. MockReturnValue (456) wrapper. Vm. HandleCheck (); expect(window.clearTimeout).toHaveBeenCalledWith(123) })Copy the code

Tests simulate HTTP requests

HTTP requests are outside the scope of unit testing because they slow down unit testing; Reduces the reliability of unit tests because HTTP requests don’t succeed 100% of the time. So you need to emulate the API files in your unit tests so that the fetchAlarmDetail never sends an HTTP request.

Jest provides an API for choosing which files or functions to return when one module imports another. Create a mock file first, rather than introducing the real file directly into the test.

Create a __mocks__ directory in the API directory to create a mock alarmApi file that needs to be tested

// SRC/API /__mocks__/ alarmapi. js export const fetchAlarmDetail = jest.fn(() => promise.resolve (' human machine '));Copy the code

Then add it to the test file

// alarm.spec.js jest.mock('.. /.. /src/api/alarmApi.js');Copy the code

Call the jest. Mock (‘.. /.. / SRC/API/alarmapi.js ‘), Jest will use the mock file created when the module imports SRC/API/alarmapi.js instead of the original file.

For example:

It ('fetch the alarm detail by HTTP ', async () => {// Set the number of assertions, if a promise is rejected, the test will fail expect. Assertions (1); const name = await alarmApi.fetchAlarmDetail(); Expect (name).tobe (' man-machine ')})Copy the code

Setting the number of assertions is a very common approach in asynchronous testing because it ensures that all assertions are executed before the test ends.

Invoke the HTTP request function in the component

In general, HTTP requests are used within components and testing alone is not very useful. So how do you test for loading other asynchronous dependencies into a component?

Start by importing the request file in home.vue

import * as alarmApi from '@/api/alarmApi.js'; // Use async handleCheck () {//... const name = await alarmApi.fetchAlarmDetail(); / /... }Copy the code

When testing calls asynchronous code, it is not always possible to access asynchronous functions that need to wait. This means that you cannot use await in tests to wait for asynchronous functions to finish.

To help, use the flush-Promises library, which waits for asynchronous functions to finish. Such as:

let loading = true;
Promise.resolve().then(() => {
    loading = false;
}
await flushPromise();
expect(loading).toBe(false)
Copy the code

Based on this, modify the previous test case to:

// 2.1 It (' When handleCheck is clicked, show the alarm', async () => {expect. Assertions (1); const wrapper = shallowMount(Home, { mocks }); const count = wrapper.vm.primaryId; AlarmApi. FetchAlarmDetail. MockImplementationOnce (() = > Promise. Resolve (' human ')); wrapper.vm.handleCheck(); await flushPromises(); const newCount = wrapper.vm.primaryId; expect(newCount).toBe(count + 1) })Copy the code

Add an expect. Assertions (1) set the number of assertions, set the result returned by the fetchAlarmDetail function, and finally call await flushPromises(); Wait for all asynchronous functions to finish. (Subsequent test case modifications are not discussed; see the code for details.)

Enter it on the command lineyarn test:uni

Test events

Testing DOM events

Function: Click a button, trigger a click event test case:

it('click the button then the $alarm will be called', () => {
  const wrapper = shallowMount(Home, {
    mocks
  });
  wrapper.find('button.check').trigger('click');
  expect($alarm).toHaveBeenCalled();
})
Copy the code

Each wrapper has a trigger method that is used to distribute an event on the wrapper.

// Keyboard event wrapper.trigger('keydown.up'); Wrapper.trigger ('keydown', {key: 'a'}) // mouse event wrapper.trigger('mouseenter');Copy the code

Test custom events

A VUE custom event is emitted by a component event with the VUE instance $EMIT method. Emits an event in a child component:

// son.vue
this.$emit('eventName', payload);
Copy the code

Receive an event in the parent component:

// father
<son @eventName='handleEvent'></son>
Copy the code

Function: Click the Button element with class Hello in the HelloWorld component to trigger the sayHello event and carry Hello. HandleSayHello, located in the Home component, fires to change the greeting from HI to Hello.

it('click the check button, home.greeting will change to hello', () => {
    const wrapper = shallowMount(Home);
    wrapper.findComponent(Hello).vm.$emit('sayHello', 'hello');
    expect(wrapper.vm.greeting).toBe('hello')
  })
Copy the code

At the end

After reading, if you find it helpful, please give a thumbs-up and support.

For more articles, please go to Github, if you like, please click star, which is also a kind of encouragement to the author.