This article is also published on my blog

Why unit tests?

Status of the project

Currently, the projects I am responsible for in my company can be divided into two categories:

  • One is for projects with high similarity, such as the admin background, where the pages are constructed from various common components. Common components are highly reusable, so quality is important. If a developer changes a common component and leaves a bug, it directly degrades the quality of the entire project. I want the application to test these common components and make sure that every common component is available.

  • The other is the company’s core projects, which are characterized by long maintenance cycles and the constant addition of new features. During the iteration of the project, when bugs occur in some of the old features that have passed the test, they can only be found by the testers during the test phase. I hope that the program can ensure the normal operation of some core functions. When bugs occur in core functions, it can be quickly detected, rather than found in the testing stage.

To solve the above problem, I tried to introduce unit testing.

The role of unit tests

  • Reduce the incidence of bugs, quickly locate bugs, reduce repeated manual testing.

  • Improve code quality and bring higher code maintainability to projects.

  • Facilitate the handover work of the project, the test script is the best description of the requirements.

Let’s talk about unit testing.

Build test framework

Test Tools overview

Mocha

Mocha (pronounced “Mocha”), launched in 2011, is one of the most popular JavaScript testing frameworks available in both browsers and Node environments.

Karma

Karma is a testing tool developed by the Google team. It is not a testing framework, but a driver to run tests. You can integrate your favorite frameworks, assertion libraries and browsers through karma’s configuration file.

Vue Test Utils

Vue’s official unit testing framework, which provides a series of handy tools that make it easier to write unit tests for Vue applications. There are many major JavaScript Test runners, but Vue Test Utils supports all of them. It is test runner independent.

Chai assert library


Construction method:

The Test framework selected in this paper is Karma + Mocha + Chai + Vue Test Utils. Manual configuration is cumbersome, so VUE-CLI is strongly recommended. Vue-cli has ready-made templates to generate projects. Vue init webpack, ‘Pick a test runner’ select ‘Karma + Mocha’. Vue – CLI will automatically generate Karma + Mocha + Chai configuration, we just need to install vue Test Utils, run NPM install@vue /test-utils.

If you want to do your own configuration, you can refer to this article.

After configuration, the following is the project directory structure:

Under the test folder is the Unit folder, which contains the files related to unit tests.

The specs house the test scripts, which are written by the developers. The Coverage folder holds the test reports, and you can see the code coverage of the tests visually in index.html. Karma. Conf. js is the configuration file for Karma.

How to write unit tests

For example

Test component HelloWorld.vue (path: E:\study\demo\ SRC \ Components)

The code is as follows:

 <template>
  <div class="hello">
    <h1>Welcome to Your Vue.js App</h1>
  </div>
</template>
Copy the code

Test script helloWorld.spec.js (path: E:\study\demo\test\ Unit \specs)

The code is as follows:

import HelloWorld from '@/components/HelloWorld';
import { mount, createLocalVue, shallowMount } from '@vue/test-utils'

describe('HelloWorld.vue', () => {
  it('should render correct contents', () => {
    const wrapper = shallowMount(HelloWorld);
    let content = wrapper.vm.$el.querySelector('.hello h1').textContent;
    expect(content).to.equal('Welcome to Your Vue.js App');
  });
});
Copy the code

1. Test script writing method

Describe a set of related tests. It is a function that takes the name of the test suite (” test of the addition function “) as its first argument and an actual function to execute as its second argument.

It stands for “test case”. It stands for a single test. It is the smallest unit of testing. It is also a function where the first argument is the name of the test case and the second argument is an actual function to execute.

2. Use of the assertion library

In the above test script, there is an assertion:

expect(content).to.equal('Welcome to Your Vue.js App');
Copy the code

The “assertion” is to determine whether the actual execution of the source code matches the expected result and throw an error if it does not. Content should be equal to ‘Welcome to Your vue.js App’.

All test cases (IT blocks) should contain one or more assertions. It is the key to writing test cases.

3. View test results

Finally, run NPM run Unit to see the result:

Open index. Vue under Coverage to see code coverage:


This is a simple unit test writing process, isn’t it simple? Let’s try it out for ourselves.

Helpful hints

1. Use createLocalVue to install the plug-in

When we write unit tests for real projects, the project code will be much more complex than the demo components above. Use createLocalVue if you are testing a single component that uses vue-Router or Vuex. For example, here is the code:

data() {
 return {
     brandId: this.$route.query.id,
 }
}
Copy the code

The $route object must be injected into the router with createLocalVue. Otherwise, the test script will fail. Use createLocalVue to solve this problem:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'

const localVue = createLocalVue()
localVue.use(VueRouter)
const router = new VueRouter()

shallowMount(Component, {
  localVue,
  router
})
Copy the code

The same is true for Vuex. The details of the use of createLocalVue will not be described, you can go to the official documentation.

2. NextTick

If you need to use nextTick in your own test file, note that any errors thrown inside it may not be caught by the test runner because it uses Promise internally. There are two suggestions for this: either you can set Vue’s global error handler to the done callback at the beginning of the test, or you can call nextTick with no arguments and have it return as a Promise:

// This will not be caught'will time out', (done) => {
  Vue.nextTick(() => {
    expect(true).toBe(false)
    done// The next two tests will work as expected.'will catch the error using done', (done) => {
  Vue.config.errorHandler = done
  Vue.nextTick(() => {
    expect(true).toBe(false)
    done()
  })
})

it('will catch the error using a promise', () = > {return Vue.nextTick()
    .then(function () {
      expect(true).toBe(false)})})Copy the code

In the actual practice of the following project, there are examples of using nextTick for your reference.

3. Modify the default test browser

Test in the configuration file Karma. Conf. Browsers default to ‘PhantomJS’.

 module.exports = function karmaConfig (config) {
  config.set({
    // browsers: ['PhantomJS'],
    browsers: ['Chrome'].Copy the code

However, I found that the Warning and error messages in PhantomJS were not the same as those in Chrome, as shown in the following figure:

Chrome:

F set to ‘Chrome’. The browsers get error prompts the same as on real Chrome, and can be debuggable like real development using console.log(). The only drawback is that every time you run NPM Run Unit, a Chrome browser pops up. PhantomJS doesn’t. It’s recommended that you use Chrome when debugging your test scripts, and switch back to PhantomJS when the scripts run and you don’t need to debug.

4. Plus – auto – watch

Auto-watch is off by default. Every time the test script is modified or the project code is modified, you need to manually execute a command to start the test, which is very troublesome. We can add –auto-watch so that during development, if a feature fails the test case, the developer can immediately find it and fix it.

 "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --auto-watch".Copy the code

The project of actual combat

Example 1

Scenario: There is a Textarea input box and a submit button on the page. Click the button to send the request. After clicking Submit, the front end verifies whether the content conforms to THE JSON format. If not, it prompts that the content cannot be submitted.

Objective of the test: validation program

Test case: Verify that all cases are executed correctly by conditional overwriting, typing numbers, strings, wrong JSON strings, ‘null’, correct JSON strings, expecting only the last case to return a result that passes, and all others to fail.

// form-setting.vue测试校验功能
describe('form-setting.vue test validation function ', () => {
	const wrapper = shallowMount(formSetting, {
		localVue
	});

	let vm = wrapper.vm;

	it('Will the test form fail if it fills in a number?', () => {
		vm.appType = 'ios'; // Select system ios vm.ios.schemeInfo = 1; // Enter the number expect(vm.isvalid ()).to.equal(false);
	});

	it('Test form in string format will fail', () => {
		vm.appType = 'ios';
		vm.ios.schemeInfo = '1'; // Enter the string expect(vm.isvalid ()).to.equal(false);
	});

	it('Test form filling error will json format fail?', () => {
		vm.appType = 'ios';
		vm.ios.schemeInfo = '{a:{a:}}'; // Enter an illegal jSON-like string expect(vm.isvalid ()).to.equal(false);
	});

	it('Test form will fail if filled with an empty object', () => {
		vm.appType = 'ios';
		vm.ios.schemeInfo = 'null'; // Enter the null object string expect(vm.isValid()).to.equal(false);
	});

	it('Test form with correct JSON format will pass', () => {
		vm.appType = 'ios';
		vm.ios.schemeInfo = '{"a": 111}'; // Enter the correct JSON string expect(vm.isvalid ()).to.equal(true);
	});
});
Copy the code
Example 2

Scenario: The team develops a verification plug-in to verify whether the input box meets the corresponding rules. If not, an error dom node will appear under the input box.

Test case: Enumerate all input operations and determine whether there is an error node with the class name. Error.

After the input operation is complete, if the content does not pass the verification, the page will generate an error dom node. This process is asynchronous, so nextTick is used. The specific usage is

returnVue.nextTick().then(() => { ... Assertions}Copy the code

Vue Test Utils has a full explanation of this

import { mount, createLocalVue } from '@vue/test-utils'
import ValidateDemo from '@/components/validate-demo'
import validate from '@ / directive/validate / 1.0 / validate'
import Vue from 'Vue'
const localVue = createLocalVue(localVue. Use (validate)'test validate - demo. Vue', () => {
  it('No input operation occurred, [error not displayed]', () => {
    const wrapper = mount(ValidateDemo, {
      localVue
    })
    return Vue.nextTick().then(() => {
      expect(wrapper.find('.error').exists()).to.equal(false)
    })
  })
  it('Focus input box and then lose focus, [display error]', () => {
    const wrapper = mount(ValidateDemo, {
      localVue
    })
    let input = wrapper.find('input')
    input.trigger('focus') // Focus on input.trigger('blur'// Lose focusreturn Vue.nextTick().then(() => {
      expect(wrapper.find('.error').exists()).to.equal(true)
    })
  })

  it('Input occurs, then empty, [display error]', () => {
    const wrapper = mount(ValidateDemo, {
      localVue
    })
    let vm = wrapper.vm
    let input = wrapper.find('input')
    input.trigger('focus')
    vm.name = 'Not empty'
    vm.name = ' '/ / to empty input. The trigger ('blur')
    return Vue.nextTick().then(() => {
      expect(wrapper.find('.error').exists()).to.equal(true)
    })
  })

  it('After input, [error is not displayed]', () => {
    const wrapper = mount(ValidateDemo, {
      localVue
    })
    let vm = wrapper.vm
    vm.name = 'Not empty'// Enter the contentreturn Vue.nextTick().then(() => {
      expect(wrapper.find('.error').exists()).to.equal(false)})})})Copy the code

Limitations of unit testing

Unit testing has many advantages, but it does not mean that it is suitable for every project. In my opinion, it has the following limitations:

1. Extra time spent

Even if you’re willing to spend a fraction of the development time writing unit tests, changing functionality means that the test logic needs to be tweaked. This can result in significant unit test maintenance for frequently changing features. To balance the pros and cons, consider writing unit tests only for stable features (such as common components) and core processes.

2. Not all code can be unit tested

If your project is full of code with low granularity and coupling between methods, you may find it impossible to unit test. Because unit testing is about code granularity and application quality. In this case, either refactor the existing code or abandon unit testing for other testing methods, such as manual testing, e2E testing.

While this is one of the downsides of unit testing, I think it’s also a strength that getting used to writing unit tests forces engineers to be more granular and thoughtful.

3. The operation of an entire process cannot be guaranteed

The front end is a very complex testing environment because each browser is different and the required data is dependent on the back end. Unit test can only test each unit of function. For some apI-dependent data, it can only mock, and cannot really simulate the actual use scenarios of users. In this case, other testing methods, such as manual testing and E2E testing, are recommended.

conclusion

Through this exploration of unit testing, I feel that the biggest obstacle to doing unit testing is time.

The biggest advantage of manual testing is that when a feature code is written, you can simply manually refresh the browser to see if it works. Writing unit tests for this can take extra development time.

But people are not machines, and no matter how simple things can go wrong. After we add new features to the system, we usually don’t manually test the old ones. It’s time consuming and boring, and we tend to think that the code we write won’t affect the old functionality.

Another way to think about it, though, is that if you write unit tests when you’re developing old features, you can run them through with test scripts every time you go into the testing phase. This saves me time testing old features, and I can rest easy: NO matter what, I can be sure that the code I write passes the tests.

Finally, thank you for your reading. This article is my exploration of a relatively simple unit test for a Vue project, which belongs to throwing bricks to attract jade. If there is any unreasonable place or suggestion, please point out!