“This is the 15th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

preface

When your project is large enough, it is possible to influence previous modules in the process of stacking modules and components. But the affected modules had already been tested, and few testers would retest the system as we iterated. Therefore, the affected module may have an invisible bug deployed online. So we use automated testing. The most important function is to ensure the correct operation of the whole system and the robustness of the system in each iteration of large projects. The importance of unit testing:

  • To ensure the quality of research and development
  • Improve project stability
  • Speed up development

Unit testing

Unit tests are currently used in three ways:

  • Jest or mocha
  • @vue / test-utils
  • sinon

The Vue CLI has built-in options for unit testing through Jest or Mocha out of the box. We also have the official Vue Test Utils for more detailed guidance and customizations. Vue Test Utils is the official Vue. Js unit Test utility library.

Why unit tests

As a programmer, unit testing can be a tough nut to crack. Unit testing is a great way to improve code quality. Because good code is generally easy to test. If during unit testing you find that some of your code is not easy to test, you may want to revisit the code to see if there are any design errors or improvements that can be made. Of course, this is not to say that code should “accommodate” unit tests, which would be putting the cart before the horse.

In summary, unit testing improves the reliability of an application, gives developers more confidence when releasing it, and makes users feel more secure. While writing unit tests can take some time, it’s worth the time and effort compared to the benefits it brings. It is also worth noting that unit testing is not a complete substitute for functional testing, because the program itself design logic errors or other environmental factors caused by the impact of unit testing may not be able to do. So, unit testing just makes sure that if you want a program module to output a pig, it won’t produce a donkey. Further functional testing, or “meat testing,” is still necessary.

vue-test-utils

Vue-test-utils is an open source project in the Vue ecosystem. It was originally avoriaz. Avoriaz is a good package, but it will be scrapped when vue-test-Utils is officially released, according to its README. Vue-test-utils can greatly simplify vue.js unit testing. For example, a search on the Web for Vue unit tests yields examples like this (including the default Vue – CLI templates) :

import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'

describe('HelloWorld.vue'.() = > {
  it('should render correct contents'.() = > {
    const Constructor = Vue.extend(HelloWorld)
    const vm = new Constructor().$mount()
    expect(vm.$el.querySelector('.hello h1').textContent)
      .toEqual('Welcome to Your Vue.js App')})})Copy the code

With vue-test-utils, you can do something like this

import { shallow } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld'

describe('HelloWorld.vue'.() = > {
  it('should render correct contents'.() = > {
    const wrapper = shallow(HelloWorld, {
      attachToDocument: ture
    })

    expect(wrapper.find('.hello h1').text()).to.equal('Welcome to Your Vue.js App')})})Copy the code

You can see that the code is much cleaner. The Wrapper contains many useful methods, the simplest of which is find() used in the above example. Vue-test-utils also has methods such as createLocalVue() and functions such as stub, which can basically complete most test cases. You also need to choose a good assertion library, usually CHAI, sometimes in combination with Sinon. Chai is an excellent library with well-developed methods. I don’t like the chai syntax, for example, which is more commonly used

to.be.ok,to.not.be.ok,expect({a1.b2}).to.be.an('object').that.has.all.keys('a'.'b'),Copy the code

Why is it so long? Why so many periods? What if I forget the order?

So I chose Expect. Js at first (Expect is part of Jest and can be installed separately), mainly because its syntax is more to my taste, and it saves a lot of trouble later on migrating to Jest. A suitable testing framework – Jest. Only Jest is mentioned here, of course, it is my personal preference, and this is my final decision. Of course before using Karma + Mocha + Chai + Chrome… That, too, has its application and merits. Some of the advantages and disadvantages of Jest will be mentioned later.

Unit test jEST

Let’s create a new demo

vue create demo-jest
Copy the code

The globally installed VUE-CLI3 is used here. Remember to check unit tests. Once the project demo is created, you will notice that there is a new tests folder where you can write individual tests. There is also a Jest configuration file for jest.config.js.

// jest.config.js

module.exports = {
  moduleFileExtensions: ["js"."jsx"."json"."vue"].// Test the file type

  transform: {
    "^.+\.vue$""vue-jest".".+\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$":
      "jest-transform-stub"."^.+\.jsx? $""babel-jest"
  },
  // Similar to loader in webpack

  transformIgnorePatterns: ["/node_modules/"].// Ignore the files in node_modules

  moduleNameMapper: {
    "^ @ / (. *) $""<rootDir>/src/$1"
  },
  // Use @ to quickly access files in the SRC directory


  snapshotSerializers: ["jest-serializer-vue"].// Format the snapshot

  testMatch: [
    "**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"].// Specify which files to unit test

  testURL"http://localhost/".// Test the address to simulate the browser environment

  watchPlugins: [
    "jest-watch-typeahead/filename"."jest-watch-typeahead/testname"]};Copy the code

In the test folder, an example.spec.js is given by default

// example.spec.js

import { shallowMount } from "@vue/test-utils";
import HelloWorld from "@/components/HelloWorld.vue";

// Define a test set
describe("HelloWorld.vue".() = > {
  // The first argument is a name, and the second is a method
  // Each IT wraps a minimum set of unit tests
  it("renders props.msg when passed".() = > {
    // The first argument is the name or description of the unit test, and the second argument is the function
    // The following body content is the verification
    const msg = "new message";
    const wrapper = shallowMount(HelloWorld, {
      // shallowMount shallow render HelloWorld
      // If you use render, you can write mount(lower case)
      propsData: { msg }
    });
    expect(wrapper.text()).toMatch(msg);
    // we expect toMatch to be the same as MSG
  });
});
Copy the code

Here we write a counter component to test

// Create a new Counter. Vue
<template>
  <div>
    <span>count: {{ count }}</span>
    <button @click = "count++">count++</button>
  </div>    
</template>

<script>
export default{
  data(){
    return{
      count0}}}</script>

<style></style>

// Create a new unit test counter.spec.js
import { shallowMount } from "@vue/test-utils";
import Counter from "@/components/Counter.vue";

describe("HelloWorld.vue".() = > {
  it("renders counter html".() = > {
    const wrapper = shallowMount(Counter, {
      propsData: { msg }
    });
    expect(wrapper.html()).toMatchSnapshots();
    // Generate a snapshot
  });
  if("count++".() = > {
    const button = warpper.find("button");
    button.trigger("click");
    expect(warpper.vm.count).toBe(1); })});Copy the code

Here we have a simple click-count function, and then we can add some additional functions.

// Counter.vue
<template>
  <div>
    <span>count: {{ count }}</span>
    <button @click = "handleClick">count++</button>
  </div>    
</template>

<script>
export default{
  data(){
    return{
      count0}},methods: {
    handleClick(){
      this.count++;
      this.$emit("change".this.count)
    }
  }
}
</script>

<style></style>


// counter.spec.js, where to install a library called sinon
import { shallowMount } from "@vue/test-utils";
import Counter from "@/components/Counter.vue";
import sinon from "sinon";

describe("HelloWorld.vue".() = > {
  let isCalled = false;
  const change = sinon.spy();
  const wrapper = mount(Counter, {
    listener: {
      // change(){
      // isCalled = true
      // }
      change
    }
  })

  it("renders counter html".() = > {
    const wrapper = shallowMount(Counter, {
      propsData: { msg }
    });
    expect(wrapper.html()).toMatchSnapshots();
    // Generate a snapshot
  });
  if("count++".() = > {
    const button = warpper.find("button");
    button.trigger("click");
    expect(warpper.vm.count).toBe(1);
    // expect(isCalled).toBe(true);
    expect(change.isCall).toBe(true);
    button.trigger("click");
    expect(change.callCount).toBe(2); })});Copy the code

Whether to use unit tests in the subsequent business, and whether to write our tests in the form of separate files or grouped parts, depends on the specific business requirements.

Automatic single test using CI service

There are already a number of platforms offering CI services, such as TravisCI and CircleCI. For open source projects, the services of these platforms can be used for free to continuously integrate some daily build and test work. At present, I use CircleCI. The specific reasons will not be discussed. Which one to use depends on my preference and specific business situation.

Add a test coverage logo to your project

Adding a logo that shows unit test coverage to your open source project’s README will enhance users’ first impressions. High coverage of the logo, will make the project appear more professional and reliable, but also allows users to further understand the whole project and ultimately choose. CodeCov provides this service and can be used in conjunction with the previously mentioned CI, which automatically performs unit tests after code is pushed and sends code coverage data to CodeCov upon passing, so that the coverage logo added to the README can be updated automatically. To do this, you’ll need a Codecov account (usually a GitHub account) and install the Codecov package.

$ yarn add -D codecov
Copy the code

Then add steps to upload code test coverage data to CI task configuration, such as CircleCI configuration as follows:

Steps: ## Run tests! - run: yarn test # update codecov stats - run: ./node_modules/.bin/codecovCopy the code

Finally, add a micromarked image to README.

The pros and cons of Jest

As mentioned earlier, the importance of unit testing and the simplicity of Jest was not a fad, but a decision made after many trade-offs and trials. Because Jest has several advantages over previous solutions, some features make testing easier and more efficient. The rough summary is as follows:

advantages

  • One-stop solution

Before USING Jest, I needed a test framework (Mocha), I needed a test runner (Karma), I needed an assertion library (CHAI), I needed a tool to do Spies/Stubs/Mocks (sinon and sinon-chai plug-ins), A browser environment for testing (either Chrome or PhantomJS). With Jest, you just install it and you’re all set.

  • Comprehensive official documentation, easy to learn and use

Jest’s official documentation is perfect, and it’s easy to get started. Before that, I had to learn how to use several mocha plugins. I had to learn how to configure karma and command, how to assert chai… It is annoying and inefficient to constantly have to navigate between different document sites.

  • Simple and convenient configuration
  • More intuitive and clear test information prompt
  • Convenient command line tools

After Jest is installed globally, unit tests can be executed on the command line. With various command parameters, it is easy to execute single tests, monitor file changes, and automatically execute them. Especially for monitoring file changes and executing, it provides multiple modes for executing only modified tests.

Jest even provides a tool called jest-Codemods for migrating tests that use other packages to use Jest

disadvantages

  • Some limitations of jsDOM

Because Jest is based on jsDOM, jsDOM is not a real browser environment after all, and it doesn’t really “render” components during testing. This can cause problems, for example, if some component code evaluates to the actual rendered property value (such as the element’s clientWidth), because these parameters in jsDOM usually default to 0. So in some cases, you might have to do something fancy with your test, such as mock (fake on instance, but fake reasonably) some intermediate values to satisfy the test case. If this happens a lot in your project, a combination of Karma + Mocha + Chrome is recommended.

  • Surrounding packages may not be complete

For example, vue-Jest, the current version does not fully implement vue-Loader. For example, use sass, postCSS, etc., which will throw warnings. If you import the actual CSS file directly from your code, you may get an error. Use a mock to simulate the CSS file. These problems are not present when using Karma-Mocha Chrome, because the tests are run in a real browser environment.

ChromeHeadless vs. PhantomJS?

Newer versions of Chrome support running in Headless mode, which is great for testing tasks that don’t require a display (you can use regular mode as well, but Chrome pops up while you’re testing). Before Chrome introduced headless mode. We used PhantomJS ‘Headless WebKit environment for testing, but it had some lingering issues and updates were getting slower and slower. PhantomJS was put on hold on March 5, 2018 due to internal disagreements.

Chrome Headless was a major blow to PhantomJS, especially since Chrome’s official puppeteer has been widely accepted and used in a short period of time. There are some scenarios where PhantomJS is useful, such as when a server doesn’t support Chrome. So far, PhantomJS is looking increasingly weak, crushed by rivals and with its own maintenance team scattered.

conclusion

Practice is always the most efficient way to learn, especially on the front end, where new things come out every day. Writing unit tests can be tedious because it’s not as exciting as working on new features. But with patience, when all the test cases pass, when the test coverage slowly increases, the sense of accomplishment is no less than that of developing new features, which is also the path to becoming a big front end in its own right.