前端单元测试vue+jest,简单上手

475 阅读3分钟

〇、前言

集成单元测试的过程还是简单的,难的是用例的书写和补充,所以这篇文档也是在最后记录我在写用例时常用的语法和api以及一些较为复杂场景的用例,共勉😊

一、安装jest

1、基于vue项目的初始化安装配置

  • 初始化配置命令

    vue add @vue/unit-jest

  • 生成一个脚本命令以及对应的依赖

    ···
    "scripts": {
        ···
        "lint": "vue-cli-service lint"
    },
    ···
    "devDependencies": {
        ···
        "@vue/cli-plugin-unit-jest": "^4.5.15",
        ···
    },
    
  • 基于初始化配置,可以进行基础的单元测试

    // add.js组件文件
    export default {
      template: `
        <div>
          <span class="counts">{{ counts }}</span>
          <button @click="add">➕</button>
        </div>
      `,
    
      data() {
        return {
          counts: 0
        }
      },
    
      methods: {
        add() {
          this.counts++
        }
      }
    }
    // addTest.js测试用例文件
    import { mount } from '@vue/test-utils'
    import Add from './add'
    
    describe('Add', () => {
      // 挂载组件
      const wrapper = mount(Add)
    
      it('初始化存在正确的span元素', () => {
        expect(wrapper.html()).toContain('<span class="counts">0</span>')
      })
    
      it('存在一个按钮', () => {
        expect(wrapper.contains('button')).toBe(true)
      })
    
      it('模拟用户点击+,counts加1', () => {
        // 找到button元素
        const button = wrapper.findComponent('button')
        // trigger可以触发元素默认的事件
        button.trigger('click')
        // 断言counts+1
        expect(wrapper.vm.counts).toBe(1)
      })
    })
    

以上是几个简单的用例,较为复杂的放在文档最后面,整理了一些用例场景和一些常用的api,只是以上的初始化配置并不能满足我们的需求,比如生成覆盖率报告文件,实时监听测试用例等等

2、基于vue项目的个性化安装配置

  • 安装vue-jest和@vue/test-utils、jest-serializer-vue快照测试依赖

    npm install --save-dev vue-jest @vue/test-utils jest-serializer-vue

  • 在文件根目录下面新建一个jest.config.js配置文件

    module.exports = {
      'preset': '@vue/cli-plugin-unit-jest',
      'roots': [
        '<rootDir>'  // 定义根目录路径
      ],
      'snapshotSerializers': ['jest-serializer-vue'], // 快照的序列化工具
      'moduleFileExtensions': [
        'js',
        'json',
        // 告诉 Jest 处理 `*.vue` 文件
        'vue'
      ],
      'moduleNameMapper': {
        '^@/(.*)$': '<rootDir>/src/$1', // 将@/引入的路径格式映射替换,防止路径出错
        '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/tests/unit/__mocks__/fileMock.js', // 模拟加载静态文件
        '\\.(css|less|scss|sass)$': '<rootDir>/tests/unit/__mocks__/styleMock.js' // 模拟加载样式文件
      },
      'transform': {
        '.*\\.(vue)$': 'vue-jest' // 用 `vue-jest` 处理 `*.vue` 文件
        // 项目解析js文件使用babel-jest存在冲突,先注释了,也能解析,可能是因为初始化生成的配置也可以解析
        // '^.+\\.js$': '<rootDir>/node_modules/babel-jest'
      },
      'coverageDirectory': '<rootDir>/tests/unit/coverage', // 生成覆盖率报告的目录
      'testMatch': ['**/tests/unit/specs/*.spec.[jt]s?(x)'], // 匹配测试用例的文件
      'transformIgnorePatterns': ['/node_modules/'], // 忽略解析的文件
      'collectCoverage': false,
      // 测试报告想要覆盖那些文件,目录,前面加!是避开这些文件,根据个人项目自己配置
      'collectCoverageFrom': [
        'src/**/*.{js,vue}',
        '!src/main.js',
        '!src/App.vue',
        '!**/node_modules/**'
      ],
      'coverageReporters': ['html', 'text-summary'] // 生成覆盖率报告文件类型
    };
    
    
  • 重新设置运行脚本

    "scripts": {
        ···
        "unit": "vue-cli-service test:unit", // 执行一次测试,生成通过和总用例数
        "unit:watch": "vue-cli-service test:unit --watchAll", // 实时监听测试
        "coverage": "vue-cli-service test:unit --coverage", // 生成测试覆盖率报告文件
        "verbose": "vue-cli-service test:unit --verbose" // 生成所有用例测试结果
    },
    
  • 执行一次测试,在根目录下生成一个tests的文件夹,下面对tests文件下进行配置

    // tests/unit/__mocks__/fileMock.js
    module.exports = 'test-file-stub'; // 模拟输出静态资源
    
    // tests/unit/__mocks__/styleMock.js
    module.exports = 'test-file-stub'; // 模拟输出静态资源
    
    // tests/unit/.eslintrc
    {
      "env": { 
        "jest": true
      }
    }
    
    // tests/unit/coverage/**
    `跑用例时自己生成覆盖率报告文件,无需配置`
    
    // tests/unit/specs/__snapshots__/*.spec.js.snap
    `用例文件中使用了快照测试就会生成相对应的快照文件,内容都是一个个简单的dom树`
    
    // tests/unit/specs/*.spec.js
    `测试用例文件,都是以.spec.js后缀结尾创建`
    
    • 因为tests/unit/coverage文件夹比较大,切不是必须提交的,在提交时可忽略
    // .gitignore
    node_modules
    coverage
    
  • 至此,配置工作已经完成,下面将我们之前的addTest用例运行一下

    add.js文件 image.png

    AddTest.spec.js文件 image.png

    运行之后生成的测试结果,中间爆红是因为目前的toContain方法即将废弃,出现警告 image.png

3、常用写法的整理

  • computed用法

    // Table.js
    export default {
      template: `
        <div id="table" v-if="visible">{{ tableData }}</div>
      `,
      props: {
        moduleVisible: {
          type: Boolean,
          default: false
        }
      },
      data() {
        return {
          tableData: []
        };
      },
      computed: {
        visible: {
          get() {
            if (this.moduleVisible) {
              this.getData();
            }
            return this.moduleVisible;
          },
          set() {
          }
        }
      },
      methods: {
        getData() {
          this.tableData = [1, 2];
        }
      }
    };
    // Table.spec.js
    import Table from './Table.js';
    import { mount, createLocalVue } from '@vue/test-utils';
    
    const localVue = createLocalVue();
    
    describe('Table', () => {
      test('computed', async() => {
        const wrapper = mount(Table, {
          localVue,
          propsData: {
            moduleVisible: false
          }
        });
        expect(wrapper.vm.visible).tobe(false);
        expect(wrapper.vm.tableData).toEqual([]);  // 初始化为空
        await wrapper.setProps({ moduleVisible: true });  // 设置为true
        expect(wrapper.vm.visible).toBe(true);
        expect(wrapper.vm.getData).toBeTruthy();  // 断言执行函数
        expect(wrapper.vm.tableData).toEqual([1, 2]);  // 断言变量更改后的值
      });
    });