As a well-documented and well-known front-end development framework, vue.js official documents respectively in “Tutorial – Tools – Unit Testing”, “Cookbook- Unit Testing of Vue components” on the unit testing method of Vue components have been introduced. Vue Test Utils is an official unit Test utility library. Even the documentation for Vuex, a state management tool, includes a chapter on Testing.
So what makes the vue.js team so focused on unit testing, and pushing it into a framework that’s also marketed as easy to use?
The official documentation is very clear:
Unit testing of components has many benefits: - provides documentation describing the behavior of components - saves time on manual testing - reduces bugs when developing new features - improves design - facilitates refactoring of automated testing so that developers in large teams can maintain complex base code.Copy the code
This article, a companion article to Unit Testing the React Component, will be an attempt to get started with unit testing in the Vue. Js stack for beginners and advanced developers.
I. Introduction to unit testing
Unit testing refers to the examination and verification of the smallest testable units in software.
Simply put, a unit is the smallest artificially defined functional module under test. Unit testing is the lowest level of testing activity to be performed during software development, where individual units of software are tested in isolation from the rest of the program.
The diagram above is one of the most common divisions of testing in development activities: from bottom to top, unit tests -> integration tests -> end-to-end tests, with decreasing automation as they become more integrated.
End-to-end (in the browser in real scenarios such as a general function and the application as a black box testing) and integration testing (set multiple test unit test together) feedback and repair cycle is long, slow speed, the test run is not stable, because most of the time depends on manual, maintenance costs are very high. Unit tests, on the other hand, are targeted at a specific method or API, accurately positioned, mock, very fast (millisecond), executed locally by the developer, with timely feedback fixes and low cost.
Most of the use cases that can be covered by unit testing are covered by unit testing, and only those that can’t be covered by unit testing are covered by end-to-end and integration testing.
Before I get into the specifics of unit testing, let’sTip a chestnutIntuitive understanding:
For example, we have a module that exposes two methods to do some processing on the menu path:
// src/menuChecker.js
export function getRoutePath(str) {
let to = ""
/ /...
return to;
}
export function getHighlight(str) {
let hl = "";
/ /...
return hl;
}
Copy the code
Write the corresponding test file:
import {
getRoutePath,
getHighlight
} from "@/menuChecker";
describe("Check menu path-related functions", ()=>{
it("Should get the correct highlighting", ()=>{
expect( getHighlight("/myworksheet/(.*)") ).toBe("myTickets");
});
it("Default highlighting should be obtained for unknown paths", ()=>{
expect( getHighlight("/myworksheet/ccc/aaa") ).toBe("mydefaulthl111");
});
it("You should complete the leading slash.", ()=>{
expect( getRoutePath("/worksheet/list") ).toBe('/worksheet/list');
});
it("Should be able to correct illegal paths.", ()=>{
expect( getRoutePath("/myworksheet/(.*)") ).toBe("/myworksheet/list");
});
});
Copy the code
Run the test file and get the following output:
The results are very friendly. Although FAIL is highlighted, the error of the judgment, the wrong line, the difference between the actual return value and the expected value, and even the table of code coverage are shown separately. In particular, the most important right and wrong outcomes are shown in green and red.
The only truth is that either the target module was written incorrectly, or the test condition was written incorrectly — we fixed it and re-ran it:
Thus, we have a basic understanding of the process of a unit test.
First, the definition of a so-called “unit” is flexible. It can be a function, a module, or a Vue Component.
Second, because successful use cases are Green and failures are Red in test results, unit tests are often referred to as “Red/Green Testing” or “Red/Green Refactoring,” as follows:
- Add a test
- Run all tests to see if the new one failed; If successful, repeat Step 1
- Write or rewrite code according to failure report; The sole purpose of this step is to pass the test without worrying about the details
- Run the test again; If it succeeds, skip to Step 5; otherwise, repeat Step 3
- Refactoring code that has passed tests to make it more readable and maintainable without affecting passing tests
- Repeat step 1 until all functionality has been tested
1.1 Test Framework
The purpose of a testing framework is to provide some convenient syntax for describing test cases and grouping them.
1.2 assertions (assertions)
Assertions are a core part of the unit testing framework, and failure to assert can cause tests to fail or report error messages.
Here are some examples of common assertions:
-
The result of Equality implies
- expect(sth).toEqual(value)
- expect(sth).not.toEqual(value)
-
Comparison implies
- expect(sth).toBeGreaterThan(number)
- expect(sth).toBeLessThanOrEqual(number)
-
Type Asserts Type implies
- expect(sth).toBeInstanceOf(Class)
-
Condition Test
- expect(sth).toBeTruthy()
- expect(sth).toBeFalsy()
- expect(sth).toBeDefined()
1.3 assertions library
The assertion library mainly provides semantically defined methods for making various judgments about the values being tested. These semantic methods return the results of the test, either success or failure. Common assertion libraries are should.js, Chai. Js, etc.
1.4 Test Cases
A set of test inputs, execution conditions, and expected results written for a particular goal to test a program path or verify that a particular requirement is met.
The general form is:
it('should ... '.function() {... expect(sth).toEqual(sth); });Copy the code
1.5 Test Suite
A set of related tests is often referred to as a test suite
The general form is:
describe('test ... '.function() {
it('should ... '.function() {... }); it('should ... '.function() {... }); . });Copy the code
1.6 spy
Just as spy literally means, we use this “spy” to “monitor” function calls
By wrapping up a monitored function, it makes it clear how many times the function was called, what arguments were passed in, what results were returned, and even what exceptions were thrown.
var spy = sinon.spy(MyComp.prototype, 'someMethod'); . expect(spy.callCount).toEqual(1);Copy the code
1.7 a stub
Stubs are sometimes used to embed or replace code directly for isolation purposes
A stub can simulate the unit test with a minimum of dependencies. For example, one method may depend on the execution of another method, which is transparent to us. It is a good idea to use stubs to isolate and replace it. This enables more accurate unit testing.
var myObj = {
prop: function() {
return 'foo'; }}; sinon.stub(myObj,'prop').callsFake(function() {
return 'bar';
});
myObj.prop(); // 'bar'
Copy the code
1.8 the mock
Mock refers to a test method that uses a dummy object to create and test objects that are not easy to construct or obtain
More broadly speaking, spy and stubs, as well as mocks of modules, Ajax return values, and timers, are all mocks.
1.9 Code Coverage
Istanbul, for example, is a common test coverage statistic.
Istanbul, the capital of Turkey, is so named because Turkey is famous for its carpet, which is used to “cover” 😷.
Review the chart above:
Columns 2 to 5 in the table correspond to four measurement dimensions respectively:
- Statement coverage: Whether each statement is executed
- Branch coverage: Whether each
if
The code blocks are all executed - Function coverage: Whether every function is called
- Line coverage: Is every line executed
Test results are classified as “green, yellow and red” based on coverage, and should be focused on these metrics. The more comprehensive the test, the higher the assurance it will provide.
There is also no need to be obsessed with line coverage because it can lead us to focus too much on the internal implementation details of components, leading to trivial testing.
Unit testing tools in ii.vue.js
2.1 Jest
Different from “traditional” front-end frameworks like Jasmine/Mocha/Chai, which haven’t been around for years; Jest is simpler to use (perhaps in the word’s original “wisecracking” sense) and offers more integration and functionality.
Jest is a test runner developed by Facebook. Compared with other testing frameworks, it features built-in common testing tools, such as built-in assertions, test coverage tools, and implements out-of-the-box testing.
In addition, Jest’s test cases are executed in parallel and only the tests for the changed files are executed, increasing testing speed.
configuration
Jest claims to be a “Zero Configuration Testing platform”, just need to configure testin NPM scripts: Jest, which runs NPM test, automatically identifies and tests use-case files that conform to its rules (typically in the __tests__ directory in Vue. Js projects).
In practice, a custom configuration in the package.json jest field or a separate jest. Config.js will result in a more suitable test scenario for us.
Reference documentation vue-test-utils.vuejs.org/zh/guides/t… , you can quickly configure the Jest test environment in vue.js projects.
Four basic words
The syntax for writing unit tests is usually very simple; For JEST, the use-case syntax is the same as Jasmine because Jasmine 2 is used internally for testing.
In fact, just remembering these four words is enough for most test situations:
describe
: Defines a test suiteit
: Defines a test caseexpect
: The judgment condition of the assertiontoEqual
: Comparison results of assertions
describe('test ... '.function() {
it('should ... '.function() {
expect(sth).toEqual(sth);
expect(sth.length).toEqual(1);
expect(sth > oth).toEqual(true);
});
});
Copy the code
2.2 sinon
This picture “I lead the horse” is not rolling curtain general Sha Wujing… In fact, the story in the picture is the well-known “Trojan horse”; Probably means the greeks laid siege to Troy people for more than ten years, long entrenched, the heart of one meter, the tents are off, leaving only a huge Trojan (containing soldiers), and stolen the light also was beaten up before the people, that is here to talk about the role of sinon, by him to deceive the trojans – people are familiar with the story behind.
So the named test tool, a collection of spoofed penetration methods, provides a rich set of independent Spy, stub, and mock methods for unit testing, compatible with a variety of testing frameworks.
While Jest itself has some tools to implement Things like SPY, Sinon is more convenient to use.
2.3 the Vue Test Utils
Vue Test Utils is the official Vue. Js unit Test utility library. This library is very similar to the Enzyme library used to test the React component
It emulates a portion of the jquery-like API, is intuitive and easy to use and learn, provides interfaces and methods to reduce test boilerplate code, facilitate judging, manipulating, and traversing the output of the Vue Component, and reduces coupling between the test code and the implementation code.
Typically, the target component is converted to a Wrapper object using its mount() or shallowMount() methods and its various methods are called in the test, such as:
import { mount } from '@vue/test-utils'
import Foo from './Foo.vue'
describe('Foo', () => {
it('renders a div', () = > {const wrapper = mount(Foo)
expect(wrapper.contains('div')).toBe(true)})})Copy the code
III. A unit test instance of vue.js
3.1 Another chestnut
import { shallowMount } from "@vue/test-utils";
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import i18nMessage from '@/i18n';
import Comp from "@/components/Device.vue";
const fakeData = { / / false data
deviceNo: "abcdefg".deviceSpace: 45.deviceStatus: 2.devices: [{id: "test001".location: "12".status: 1
},
{
id: "test002".location: "58".status: 3
},
{
id: "test003".location: "199".status: 4}}; Vue.use(VueI18n);// Reproduce the necessary dependencies
const i18n = new VueI18n({
locale: 'zh-CN'.silentTranslationWarn: true.missing: (locale, key, vm) = > key,
messages: i18nMessage
});
let wrapper = null;
const makeWrapper = (a)= >{
wrapper = shallowMount( Comp, {
i18n, / / look here
propsData: { // And here
unitHeight: 5.data: fakeData
}
} );
};
afterEach((a)= >{ // Also common usage
if(! wrapper)return;
wrapper = null;
});
describe("test Device.vue", ()=>{
it("should be a VUE instance", ()=>{
makeWrapper();
expect( wrapper.isVueInstance() ).toBeTruthy();
});
it("Should have normal total height.", ()=>{
makeWrapper();
expect( wrapper.vm.totalHeight ).toBe( 1230 );
});
it("Correct number of devices should be rendered", ()=>{
makeWrapper();
expect( wrapper.findAll('.deviceitem').length ).toBe( 3 );
});
it("The specified equipment should be in the correct location.", ()=>{
makeWrapper();
const sty = wrapper.findAll('.deviceitem').at(1).attributes('style');
expect( sty ).toMatch( /height\:\s*20px/ );
expect( sty ).toMatch( /bottom\:\s*20px/ );
});
it("The tooltip should be rendered correctly", ()=>{
makeWrapper();
// The usage here is worth noting
const popper_ref = wrapper.find({ref: 'device_tooltip_test002'});
expect( popper_ref.exists() ).toBeTruthy();
const cont = popper_ref.find('.tooltip_cont');
expect( cont.html() ).toMatch(/ Location \:\s58/);
});
it("Correct device classification should be rendered.", ()=>{
makeWrapper();
const badge = wrapper.find('.badge');
expect( badge.exists() ).toBeTruthy();
expect( badge.findAll('li').length ).toBe(4);
expect( badge.findAll('li').at(2).text() ).toBe('Spray equipment');
});
it("When the close button is clicked, it should no longer be displayed.", (done)=>{ // Asynchronous use cases
makeWrapper();
wrapper.vm.$nextTick((a)= >{ // Now look at this
expect(
wrapper.find('.devices_container').exists()
).toBeFalsy();
done();
});
});
});
Copy the code
There is no need to explain this point by point; the main apis can be found in the documentation of both Jest and Vue Test Utils.
One of the notable lessons is the correct use of wrapper.vm.$nextTick after some asynchronous updates (such as delays in code); Wrapper. Find ({ref: XXX}) for component elements mounted to external locations such as document.body.
3.2 Integrate into workflow
Written unit tests that are simply executed manually with each NPM test are bound to be forgotten over time, become obsolete, and eventually become unexecutable.
There are multiple points in time when you can optionally insert automatic unit tests — save a file every time, execute a build every time, etc. Here we have chosen a very simple configuration approach:
Install the pre-commit dependencies in your project first. Then configure NPM scripts in package.json:
"scripts": {
...
"test": "jest"
},
"pre-commit": [
"test"
],
Copy the code
In this way, the existing unit tests in the project will be automatically executed once before each Git commit, often avoiding the “change one bug, send ten new bugs” situation.
IV. Improve vue.js components with unit tests
In addition to reducing errors, another significant benefit of unit testing is that it allows us to think more clearly about componentization and develop better habits.
A component that has been verified to render the desired output for a given input is called a tested component;
A testable component means it is easy to test
How do you ensure that a component works as expected?
We may be used to checking our written components again and again with our hands and eyes; But if you’re going to manually validate every change to every component, sooner or later you’re going to get tired or lazy, and the bugs will be stuck in the code.
This is why automated unit tests are important. Unit tests ensure that components work correctly after every change made to them.
Unit testing is not just about finding bugs early. Another important aspect is its ability to verify the level of component architecture.
A component that is untestable or difficult to test is basically the same as a poorly designed component.
Components are difficult to test because they have too many props, dependencies, referenced models, and access to global variables — all signs of bad design.
A poorly designed component becomes untestable, and you simply skip unit testing, leaving it untested in a vicious cycle.
4.1 Hope is the last chestnut
Suppose you want to test the NumStepper. Vue component
//NumStepper. Vue <template> <div> <button class="plus" v-on:click="updateNumber(+1)"> </button> <button class="minus" V-on :click="updateNumber(-1)"> </button> <button class="zero" > </button> </div> </template> <script> export default { props: { targetData: Object, clear: Function }, methods: { updateNumber: function(n) { this.targetData.num += n; } } } </script>Copy the code
This component relies on an outer component to provide it with data and methods:
//NumberDisplay.vue <template> <div> <p>{{somedata.num}}</p> <NumStepper :targetData="somedata" :clear="clear" /> </div> </template> <script> import NumStepper from "./NumStepper" export default { components: { NumStepper }, data() { return { somedata: { num: 999 }, tgt: this } }, methods: { clear: function() { this.somedata.num = 0; } } } </script>Copy the code
So our test would read something like this:
import { shallowMount } from "@vue/test-utils";
import Vue from 'vue';
import NumStepper from '@/components/NumStepper';
import NumberDisplay from '@/components/NumberDisplay';
describe("Test NumStepper component", ()=>{
it("Should be able to affect data from outer components", () = > {const display = shallowMount(NumberDisplay);
const wrapper = shallowMount(NumStepper, {
propsData: {
targetData: display.vm.somedata,
clear: display.vm.clear
}
});
expect(display.vm.somedata.num).toBe(999);
wrapper.find('.plus').trigger('click');
wrapper.find('.plus').trigger('click');
expect(display.vm.somedata.num).toBe(1001);
wrapper.find('.minus').trigger('click');
expect(display.vm.somedata.num).toBe(1000);
wrapper.find('.zero').trigger('click');
expect(display.vm.somedata.num).toBe(0); })});Copy the code
is very complex to test because it correlates implementation details of external components.
An additional
component is required in the test scenario to reproduce the external component, pass data and methods to the target component, and verify that the target component has correctly modified the state of the external component.
It’s not hard to imagine that if the
component were to rely on other components or environment variables, global methods, and so on, things would get even worse, requiring several test-specific components to be implemented separately, or even impossible to test at all.
4.2 The real last chestnut
When
is independent of the details of the external components, testing is easy. Let’s implement and test a properly packaged version of the
component:
Vue <template> <div> <button class="plus" v-on:click="updateFunc(+1)"> </button> <button class="minus" V-on :click="updateFunc(-1)"> </button> <button class=" clearFunc"> <script> export default { props: { updateFunc: Function, clearFunc: Function } } </script>Copy the code
In testing, there is no need to introduce additional components:
import { shallowMount } from "@vue/test-utils"; import Vue from 'vue'; import NumStepper from '@/components/NumStepper2'; Describe (" test NumStepper component ", ()=>{it(" should be able to effect the data of outer component ", ()=>{const obj = {func1: function(){}, func2: function(){}}; const spy1 = jest.spyOn(obj, "func1"); const spy2 = jest.spyOn(obj, "func2"); const wrapper = shallowMount(NumStepper, { propsData: { updateFunc: spy1, clearFunc: spy2 } }); wrapper.find('.plus').trigger('click'); expect(spy1).toHaveBeenCalled(); wrapper.find('.minus').trigger('click'); expect(spy1).toHaveBeenCalled(); wrapper.find('.zero').trigger('click'); expect(spy2).toHaveBeenCalled(); })});Copy the code
Note: this example only verifies whether it is clicked. You can also introduce sinon’s related methods to verify the incoming parameters, etc., to write a more complete test.
V. summary
Unit testing, as a classical means of development and refactoring, is widely recognized and adopted in the field of software development. The front-end field has also gradually accumulated a wealth of testing frameworks and methods.
Unit testing can provide basic guarantee for our development and maintenance, so that we can complete the construction and reconstruction of the code with a clear mind and a bottom in mind.
Good encapsulation makes testing easy, whereas improper encapsulation makes testing difficult.
Testability is a practical criterion for checking the soundness of a component structure.
VI. Reference materials
- Juejin. Cn/post / 684490…
- Juejin. Cn/post / 684490…
- Blog.csdn.net/xqtesting/a…
- www.jianshu.com/p/ec40734c8…
- www.jianshu.com/p/ffd6d319f…
–END–