单元测试实战分享(一)

93 阅读4分钟

明白要测试的是什么,只关注输入输出

一、新建测试文件

转存失败,建议直接上传图片文件

image.png jest 支持三种方式写测试代码

  • 以 .spec.js 命名
  • 以 .test.js 命名
  • 放在 __tests__文件夹下

二、规划测试场景

√ 检查索要发票首页-列表父组件初始化

√ 检查索要发票首页-列表父组件:tagID=0,验证获取可申请开票数据成功并加载

√ 检查索要发票首页-列表父组件:tagID=0,验证获取可申请开票数据失败

√ 检查索要发票首页-列表父组件:tagID=1,验证获取开票中数据成功并加载

√ 检查索要发票首页-列表父组件:tagID=1,验证获取开票中数据失败

√ 检查索要发票首页-列表父组件:tagID=2,验证获取完成开票数据成功并加载

√ 检查索要发票首页-列表父组件:tagID=2,验证获取完成开票数据失败

三、构建测试代码结构

import {shallowMount} from '@vue/test-utils';
import '@/test/component/mobile';//RequestInvoice_TabList使用了vant组件
import RequestInvoice_TabList from '../../../pages/inv/invoiceCenter/mobile/RequestInvoice_TabList.vue'; 
  • shallowMount 将会创建一个包含被挂载和渲染的 Vue 组件的 Wrapper,只存根当前组件,不包含子组件(mount:会测试子子孙孙组件,耗费性能更大。一般用 shallowMount 即可。)。
const common = require('@teld/api-proxy/src/utils/common.js'); //注册公共组件库
const url = require('@teld/api-proxy/src/utils/url.js');
const $utils = {
  common: common,
  url:url,
  sgApi: {
    getDataAsync: (param) => {
      //可以根据param参数,写入你期望的返回值
      console.log("mock 成功")
    }
  }
};

我们的代码中用到了this.$utils相关,需要引入,避免单元测试运行时报错

describe('RequestInvoice_TabList.vue', () => {   
  let $route;
  let mockEventBus;  

  // 创建一个模拟的 EventBus    
  mockEventBus = {  
    $on: jest.fn(),  
    $off: jest.fn(), // 你可能还需要在组件销毁时调用这个来清理事件监听器  
    $emit: jest.fn(),
  };  

  const updateTagID = jest.fn();
  beforeEach(() => {  
    $route = {
      query: {CompanyID:"B2D7B9B6-E936-46BA-8B8B-0B83C1AFFE7C"}
    }
  })
  const factory = (values = {}) => {
    return shallowMount(RequestInvoice_TabList,
      { mocks:
        {$route,
         $EventBus: mockEventBus,  
         $utils
      }},
    )
  }

  it('检查索要发票首页-列表父组件初始化', async() => {  
    const wrapper = factory();
    expect(wrapper.exists()).toBe(true);  
    expect(wrapper.vm.companyID).toBe("B2D7B9B6-E936-46BA-8B8B-0B83C1AFFE7C"); // 检查 companyID 是否根据路由参数正确设置  

    wrapper.vm.onLoad();
    await wrapper.vm.$nextTick(); // 等待
    expect(wrapper.vm.loading).toBe(false);


  });  
  it('检查索要发票首页-列表父组件:tagID=0,验证获取可申请开票数据成功并加载',async () => {  
    const wrapper = factory();
    // 当点击可申请开票,传入的tagID=0时
    wrapper.vm.tagID = 0;
    wrapper.vm.page = 1;
    wrapper.vm.finished = false;
    wrapper.vm.invoiceListAll = [];
    wrapper.vm.loadList();
    const jsonData = require('../../testData/ISSG-GetSettleBillInvoiceList.json'); // 替换为实际的文件路径

    await wrapper.vm.$nextTick(); // 等待
   
    expect(wrapper.html()).toContain('<requestinvoicetab_applyforinvoice-stub'); //检查子组件 requestinvoicetab_applyforinvoice 是否被渲染
    expect(wrapper.vm.total1).toBe(13); // 检查 total1 是否正常
    expect(wrapper.vm.invoiceListAll.length).toBe(jsonData.data.rows.length);   
    expect(wrapper.vm.invoiceListAll).toEqual(jsonData.data.rows);

    const dom = wrapper.find('requestinvoicetab_applyforinvoice-stub')    // 检查 渲染的子组件参数是否正确
    expect(dom.attributes().companyid).toBe("B2D7B9B6-E936-46BA-8B8B-0B83C1AFFE7C");
    expect(dom.attributes().tagid).toBe("0");
    expect(dom.attributes().total1).toBe("13");
   // expect(wrapper.element).toMatchSnapshot();  // 检查组件的渲染输出与预定义的“快照”(snapshot)相匹配
  }); 

  it('检查索要发票首页-列表父组件:tagID=0,验证获取可申请开票数据失败',async () => {  
    const wrapper = factory();
    // 当点击可申请开票,传入的tagID=0时
    wrapper.vm.tagID = 0;
    const jsonData = {
      "data": null,
      "errcode": "",
      "errmsg": "",
      "state": "0",
      "errstack": null
    };
    wrapper.vm.processInvoiceList(jsonData);
   
    await wrapper.vm.$nextTick(); // 等待
   
    expect(wrapper.html()).toContain('<requestinvoicetab_applyforinvoice-stub'); //检查子组件 requestinvoicetab_applyforinvoice 是否被渲染
    expect(wrapper.vm.invoiceListAll.length).toBe(0);   
    expect(wrapper.vm.invoiceListAll).toEqual([]);
    expect(wrapper.element).toMatchSnapshot();  // 检查组件的渲染输出与预定义的“快照”(snapshot)相匹配
  }); 
  it('检查索要发票首页-列表父组件:tagID=1,验证获取开票中数据成功并加载',async () => {  
    const wrapper = factory();
    // 当点击可申请开票,传入的tagID=0时
    wrapper.vm.tagID = 1;
    wrapper.vm.page = 1;
    wrapper.vm.finished = false;
    wrapper.vm.invoiceListAll = [];
    wrapper.vm.loadList();
    const jsonData = require('../../testData/ISSG-GetPartnerInvoiceAppList.json'); // 替换为实际的文件路径

    await wrapper.vm.$nextTick(); // 等待
   
    expect(wrapper.html()).toContain('<requestinvoicetab_invoicingandcompleted-stub'); //检查子组件 requestinvoicetab_applyforinvoice 是否被渲染
    expect(wrapper.vm.invoiceListAll.length).toBe(jsonData.data.rows.length);   
    expect(wrapper.vm.invoiceListAll).toEqual(jsonData.data.rows);

    const dom = wrapper.find('requestinvoicetab_invoicingandcompleted-stub')    // 检查 渲染的子组件参数是否正确
    expect(dom.attributes().companyid).toBe("B2D7B9B6-E936-46BA-8B8B-0B83C1AFFE7C");
    expect(dom.attributes().tagid).toBe("1");
   // expect(wrapper.element).toMatchSnapshot();  // 检查组件的渲染输出与预定义的“快照”(snapshot)相匹配

   //翻页
   if(!wrapper.vm.finished){
    wrapper.vm.loading = true;
   }
   wrapper.vm.onLoad();
   await wrapper.vm.$nextTick(); // 等待

   expect(wrapper.vm.page).toBe(2); 



  });
 
  it('检查索要发票首页-列表父组件:tagID=1,验证获取开票中数据失败',async () => {  
    const wrapper = factory();
    // 当点击可申请开票,传入的tagID=0时
    wrapper.vm.tagID = 1;
    const jsonData = {
      "data": null,
      "errcode": "",
      "errmsg": "",
      "state": "0",
      "errstack": null
    };
    wrapper.vm.processInvoiceList(jsonData);
   
    await wrapper.vm.$nextTick(); // 等待
   
    expect(wrapper.html()).toContain('<requestinvoicetab_invoicingandcompleted-stub'); //检查子组件 requestinvoicetab_applyforinvoice 是否被渲染
    expect(wrapper.vm.invoiceListAll.length).toBe(0);   
    expect(wrapper.vm.invoiceListAll).toEqual([]);
    expect(wrapper.element).toMatchSnapshot();  // 检查组件的渲染输出与预定义的“快照”(snapshot)相匹配
  });  
  it('检查索要发票首页-列表父组件:tagID=2,验证获取完成开票数据成功并加载',async () => {  
    const wrapper = factory();
    // 当点击可申请开票,传入的tagID=0时
    wrapper.vm.tagID = 2;
    wrapper.vm.page = 1;
    wrapper.vm.finished = false;
    wrapper.vm.invoiceListAll = [];
    wrapper.vm.loadList();
    const jsonData = require('../../testData/ISSG-GetPartnerInvoiceAppList1.json'); // 替换为实际的文件路径

    await wrapper.vm.$nextTick(); // 等待
   
    expect(wrapper.html()).toContain('<requestinvoicetab_invoicingandcompleted-stub'); //检查子组件 requestinvoicetab_applyforinvoice 是否被渲染
    expect(wrapper.vm.invoiceListAll.length).toBe(jsonData.data.rows.length);   
    expect(wrapper.vm.invoiceListAll).toEqual(jsonData.data.rows);

    const dom = wrapper.find('requestinvoicetab_invoicingandcompleted-stub')    // 检查 渲染的子组件参数是否正确
    expect(dom.attributes().companyid).toBe("B2D7B9B6-E936-46BA-8B8B-0B83C1AFFE7C");
    expect(dom.attributes().tagid).toBe("2");
    expect(wrapper.html()).toContain('没有更多了'); // 基本 HTML 结构检查  
   // expect(wrapper.element).toMatchSnapshot();  // 检查组件的渲染输出与预定义的“快照”(snapshot)相匹配

    //翻页
    wrapper.vm.onLoad();
    await wrapper.vm.$nextTick(); // 等待
 
    expect(wrapper.vm.page).toBe(1);
    expect(wrapper.html()).toContain('没有更多了'); // 基本 HTML 结构检查  
  });
  it('检查索要发票首页-列表父组件:tagID=2,验证获取完成开票数据失败',async () => {  
    const wrapper = factory();
    // 当点击可申请开票,传入的tagID=0时
    wrapper.vm.tagID = 1;
    const jsonData = {
      "data": null,
      "errcode": "",
      "errmsg": "",
      "state": "0",
      "errstack": null
    };
    wrapper.vm.processInvoiceList(jsonData);
   
    await wrapper.vm.$nextTick(); // 等待
   
    expect(wrapper.html()).toContain('<requestinvoicetab_invoicingandcompleted-stub'); //检查子组件 requestinvoicetab_applyforinvoice 是否被渲染
    expect(wrapper.vm.invoiceListAll.length).toBe(0);   
    expect(wrapper.vm.invoiceListAll).toEqual([]);
    expect(wrapper.element).toMatchSnapshot();  // 检查组件的渲染输出与预定义的“快照”(snapshot)相匹配
  });  

});
  • describe(name, fn) 这边是定义一个测试套件,RequestInvoice_TabList.vue 是测试套件的名字,fn 是具体的可执行的函数
  • it(name, fn) 是一个测试用例,检查索要发票首页-列表父组件初始化 是测试用例的名字,fn 是具体的可执行函数;一个测试套件里可以保护多个测试用例。
  • beforeEach():它是Jest的钩子函数,会在执行每一个测试用例之前调用,在这个钩子函数中我们重新挂载组件,避免多个测试用例互相影响。
  • async/await:因为我们调用了公共方法,它修改了组件的数据,进而会触发DOM更新,因此我们需要调用组件的$nextTick()方法,以确保我们获取到了正确DOM的状态。
  • $route:当前匹配路由的信息,其中包含路由参数中的字段。
  • $EventBus:模拟引入全局事件方法。
  • 引入工厂函数 ,将 values 对象合并到了 data 并返回了一个新的 wrapper 实例。这样我们就不需要在每个测试中重复 const wrapper = shallowMount(RequestInvoice_TabList)
  • expectJest 内置的断言。
  • toBeJest 提供的断言方法,严格相等。
  • 对于异步的代码,写断言的时候需要放在 wrapper.vm.$nextTick()
  • wrapper.html()方法,返回组件渲染后的DOM结构
  • wrapper.attributes()方法,他返回组件渲染后的DOM属性对象
  • toContain :用于检查数组或字符串是否包含特定项
  • toEqual :是“相等”,不是“相同”,相当于==
  • toMatchSnapshot :获取代码的快照,并将其与以前保存的快照进行比较,如果新的快照与前一个快照不匹配,测试会失败。快照测试对于测试一个组件来说,相对比较有用,因为如果添加了快照测试,它能防止我们错误的修改了组件。

四、结果

image.png

image.png 从coverage\lcov-report文件夹下找到文件:RequestInvoice_TabList.vue.html,打开后可以看到目前已经覆盖和未覆盖的代码

补充:v2.cn.vuejs.org/v2/cookbook…