文章目录

  • 持续集成和前端自动化测试
  • 前端自动化分类
  • TDD (Test-Driven Development) 测试驱动开发
  • BDD (Behavior Driven Development) 行为驱动开发
  • 前端自动化工具选择
  • 端到端测试(E2E)
  • 区别
  • 工具选择
  • 环境搭建
  • 断言
  • 截屏和视频录制
  • jest 安装
  • vue-cli 中使用 jest
  • jest基础测试知识
  • jest 文件和目录命名规范
  • 测试函数
  • 断言函数
  • 分组函数
  • 常见命令
  • Jest 钩子函数
  • Jest 中的 Mock
  • jest基础测试
  • 对象等值测试
  • 异步测试
  • 定时器测试(异步测试)及断言
  • Dom 测试
  • snapshot 快照测试
  • Vue 测试
  • 安装 unit-jest
  • 基础知识
  • hello Jest Vue
  • vm 实例测试
  • 事件测试
  • 异步操作
  • axios 异步测试


持续集成和前端自动化测试

持续集成是互联网软件开发上线流程中的核心一环,自动化测试是持续集成得以实现的核心步骤,缺乏了自动化测试,持续集成自然无从谈起。

在日常的开发中,前端错综复杂的变化引发的 bug 往往令开发者头疼,或多或少经历过 修完东墙西墙倒 的经历,此时前端自动化测试就显得非常重要。

前端的自动化测试无非也是编写测试用例,在持续集成时执行跑通全部测试用例。如果是一个短平快的小项目,引入前端自动化测试,编写测试用例,无疑只会增加开发成本,然而当项目扩大、迭代频繁、逻辑复杂、需求反复变更的情况下,回归测试的成本是巨额的,自动化测试的优势就能体现出来。

自动化测试的收益 = 迭代次数 * 全手动执行成本 - 首次自动化成本 - 维护次数 * 维护成本

尽早引入前端自动化测试不仅能够减少项目 bug 出现概率 (尤其是回归测试中的 bug),还能更好地进行代码组织,增强项目的可维护性,尤其对于工程质量较差的项目,收益是巨大的;如果将其应用于持续集成中,commit 触发自动执行测试脚本,还能大幅提升团队的开发效率。

前端自动化分类

单元测试
单元测试,见名知意,可以理解为对系统的某个单元进行测试,而这个单元,可以是某个函数,某个组件,对于这种测试形式来说,我们只关注这个独立的单元的功能是否正常。测试用例以当前单元内的功能作为对象。

集成测试
将多个单元集成到一起,进行测试,重点关注各个单元串联起来之后的系统整体功能是否正常。此时的测试用例以多个单元组成的某个独立的系统为对象。

前端自动化测试可以按照开发模式分两类:TDD (Test-Driven Development) 测试驱动开发、BDD (Behavior Driven Development) 行为驱动开发。

测试还可以按照用例粒度分为 单元测试 (Unit Test)、集成测试 (Integration Test)、端到端测试 (End to End Test)。

TDD (Test-Driven Development) 测试驱动开发

TDD 顾名思义,开发者根据需求先编写测试用例,再逐步开发,最终满足全部测试用例的需求。

TDD 的基本思路就是通过测试来推动整个开发的进行,但测试驱动开发并不只是单纯的测试工作,而是把需求分析,设计,质量控制量化的过程。

TDD 的重要目的不仅仅是测试软件,测试工作保证代码质量仅仅是其中一部分,而且是在开发过程中帮助客户和程序员去除模棱两可的需求。TDD 首先考虑使用需求(对象、功能、过程、接口等),主要是编写测试用例框架对功能的过程和接口进行设计,而测试框架可以持续进行验证。

刚开始的时候,只有测试用例,未进行功能开发,执行测试用例,满屏是红色的测试用例不通过提示,随着测试用例被满足变绿,最终全部变绿,功能开发完成,因此前端自动化测试也被叫做 Red-Green Development。

TDD 先写测试再写代码,单位是模块,多用于 单元测试
重点在测试代码,属于 白盒测试
测试内容是模块,速度快,但是忽略模块间依赖,安全感低

流程

  1. 编写测试用例
  2. 运行测试,测试用例无法通过测试
  3. 编写代码,时测试用例通过测试
  4. 优化代码,完成开发
  5. 重复上述步骤

优势

  1. 长期减少回归 bug
  2. 代码质量更好(组织,可维护性)
  3. 测试覆盖率高
  4. 错误测试代码不容易出现

BDD (Behavior Driven Development) 行为驱动开发

测试用例模拟用户的操作行为,通常在完成业务代码开发之后,以用户的操作为指导编写测试代码。当测试用例跑通之后,就可以认为系统的整体流程已经流畅。

BDD 的模式适用于平时的业务代码开发,因为业务的需求有可能变更频繁,但操作流程有可能不会变化,当业务代码发生变化的时候,可以使用原来的测试用例继续跑代码,节省了开发时间。

BDD 先写代码再写测试,测试单位是功能,多用于集成测试
重点在测试 UI(DOM)功能,属于黑盒测试
测试内容是整套操作流程,速度慢,往往需要多个模块配合,安全感高

TDD 开发模式更适用于开发,类似方法函数库,对于数据的处理,对于这种显示组件,更加推荐于BDD 的开发方式,这样既有了测试,也不会增加过多的工作负担,

在平时的项目中,通常使用 TDD 和 BDD 相结合来进行测试,TDD 负责方法类、独立组件的测试。BDD 则负责整体业务模块的测试。

前端自动化工具选择

前端近几年涌现出很多优秀的测试工具:

karma – Google Angular 团队开发的测试运行平台,配置简单灵活,能够很方便在多个真实浏览器中运行测试

mocha – 很优秀的测试框架,有完善的生态系统,简单的测试组织方式,不对断言库和工具做任何限制,非常灵活

jest – facebook 出品的大而全的测试框架,React 官方推荐的单元测试框架,配置简单运行速度快

还有很多其他的前端测试框架,但大同小异,无非是对断言和测试桩等工具的集成度不同,论成熟度首推 mocha,论效率首推 jest。

jest 是 Facebook 开源的 JavaScript 测试框架,它自动集成了断言、JsDom、覆盖率报告等开发者所需要的所有测试工具,是一款几乎零配置的测试框架,而且速度很快,此处选择 jest 作为测试工具。

- Jest测试框架优点

比较新:喜新厌旧是人的天性,作为一个程序员,你更要有拥抱全新知识的态度。绝不能固步自封,顽固不化。

基础很好:框架基础好就是性能好、功能多、简单易用,Jest在这三个方面你可以完全放心。

速度快: 单独模块测试功能,比如说有两个模块A和B,以前都测试过了,这时候你只改动A模块,再次测试,模块B不会再跑一次,而是直接测试A模块。

API简单 :等你基础知识学完后,你就会发现API非常简单,数量也少。

隔离性好:Jest里会有很多的测试文件等待我们使用,Jest的执行环境都是隔离,这样就避免不同的测试文件执行的时候互相影响而造成出错。

IDE整合:Jest直接可以和很多编辑器(VSCode)进行融合,让测试变的更加简单。

多项目并行:比如我们写了Node.js的后台项目,用React写了一个前台项目,Jest是支持他们并行运行,让我们的效率更加提高了。

快出覆盖率:(测试代码覆盖率) 对于一个项目的测试都要出覆盖率的,Jest就可以快速出这样的覆盖率统计结果,非常好用。

端到端测试(E2E)

区别

在 jest 单元测试中使用快照、API-mock 和 DOM 样式状态断言已经能够实现基础的 UI 测试,但是单元测试属于白盒测试,更关注数据的流动,而端到端测试 (End To End Test) 属于黑盒测试,更关注操作结果的展示,因此测试效果自然不同。端到端测试更贴近真实用户操作,页面运行在真实的浏览器环境中,因此端到端测试是从用户角度出发的测试。

工具选择

端到端测试的工具也有不少,最为突出的是老牌 e2e 测试工具 NightWatch,根据需要安装 Selenium 或其他 Webdriver,优势是可以测试多类浏览器,兼容性好,而 Cypress 是为现代网络打造的下一代前端测试工具,安装更简单,可以测试任何在浏览器中运行的内容,测试执行效率更高,此处选用 Cypress 作为端到端测试工具。

就像官网所说,Cypress 就像一个完整的烘烤箱,他还自带电池,下面是一些其它测试框架无法做到的事情:

时间旅行: Cypress 在你运行测试的时候拍摄快照。 只要将鼠标悬停在 命令日志 上就能够清楚的了解到每一步发生了什么。
可调式能力: 你再也不需要去猜测测试为什么失败了。 调试工具 和 Chrome 的调试工具差不多。 清晰的错误原因和堆栈跟踪让调试能够更加快速。
自动等待: 在你的测试中不再需要添加等待或睡眠函数了。在执行下一条命令或断言前 Cypress 会 自动等待 异步将不再是问题.
Spies, Stubs, and Clocks: 验证和 控制 函数、服务器响应或者计时器的行为。你喜欢的单元测试的功能都掌握在你的手中。
网络流量控制: 非常容易的进行 控制、保存和边缘测试,而这并不需要涉及到你的服务。你可以根据需要保留网络流量。
一致的结果: 架构不需要 Selenium 或者 WebDriver。向快速,一致和可靠的无侵入测试看齐。
屏幕截图和视频: 可以查看测试失败时候系统自动截取的图片,或者整个测试的录制视频。

环境搭建

安装非常简单:

$ npm install cypress --save-dev

配置文件修改:

// baseUrl: "http://localhost:8080", // 测试域名
fixturesFolder: 'tests/e2e/fixtures', // 外部静态数据,如网络请求或存放模拟上传或读取的文件
integrationFolder: 'tests/e2e/specs', // 测试用例文件夹
screenshotsFolder: 'tests/e2e/screenshots', // 屏幕快照
// videoRecording: true,
videosFolder: 'tests/e2e/videos', // 录制后的文件夹
supportFile: 'tests/e2e/support/index.js', // 配置自定义命令全局注入
viewportHeight: 768, // 测试浏览器视口高度
viewportWidth: 1366 // 测试浏览器视口宽度

然后,可以将命令写到 package.json 中,如果使用 vue-cli,可以看到已经存在 “test:e2e”: “vue-cli-service test:e2e”,直接执行即可启动测试,在这之前需要先启动项目和 mock 服务。

$ npm run test:e2e


断言

在 Cypress 中有两种断言写法:

隐式: 使用 .should() 或者 .and(),.and() 只是 .should() 的别名,它链接多个断言使代码更易读
显式: 使用 expect

// 隐式
cy.get('#header a')
  .should('have.class', 'active')
  .and('have.attr', 'href', '/users')
// 显式
cy.get('tbody tr:first').should(($tr) => {
  expect($tr).to.have.class('active')
  expect($tr).to.have.attr('href', '/users')
})

// 常用断言
cy.get(':checkbox').should('be.disabled')
cy.get('form').should('have.class', 'form-horizontal')
cy.get('input').should('not.have.value', 'US')
cy.request('/users/1').its('body').should('deep.eq', { name: 'Jane' })

// 默认断言
/*
cy.visit() 预期这个页面是状态为200的 text/html内容页
cy.request() 预期远程服务器存在并提供响应
cy.contains() 预期包含内容的元素最终存在于DOM中
cy.get() 预期元素最终存在于 DOM中
.find() 预期元素最终存在于 DOM 中
.type() 预期元素最终为 可输入 状态
.click() 预期元素最终为 可操作 状态
.its() 预期最终找到当前主题的一个属性

截屏和视频录制

屏幕录制截屏是 Cypress 的一大特色,在 Test Runner 中单击项目的 Runs 选项卡,登录账号,再根据提示执行指令,即可完成屏幕录制和自动截屏。

$ ./node_modules/cypress/bin/cypress run --record --key xxxxxxxx

还可以在用例中主动截屏,存储在 screenshots 目录下。

jest 安装

jest 需要自动运行测试脚本,node 环境是必不可少的,如果从头搭建,首先得初始化项目 package.json 并安装 jest:

$ npm init
$ npm install jest -D

jest 默认不支持 es6,需要使用 babel 来支持 es6,安装 babel:

$ npm install @babel/core @babel/preset-env -D

配置 babel,修改 .babelrc 文件:

{
    "presets": [
        ["@babel/preset-env", {
            "targets": {
                "node": "current"
            }
        }]
    ]
}

vue-cli 中使用 jest

现实项目中,往往不会从零搭建 jest 项目,更多的情况是,需要在一个脚手架已经搭建好的项目中引入自动化测试,此处在 vue-cli 基础上修改 jest 配置,安装好 jest 后需要修改项目根目录下的配置文件 jest.config.js,重点关注 testMatch 和 testPathIgnorePatterns 两个属性,testMatch 指定了匹配的测试用例文件的路径,而 testPathIgnorePatterns 则可以忽略指定文件,因此使用两个属性可以精确匹配到项目中所有的测试用例。

module.exports = {
  moduleFileExtensions: [
    'js',
    'jsx',
    'json',
    'vue'
  ],
  transform: {
    '^.+\\.vue$': 'vue-jest',
    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
    '^.+\\.jsx?$': 'babel-jest'
  },
  collectCoverageFrom: ['**/*.{vue}', '!**/node_modules/**'],
  transformIgnorePatterns: [
    '/node_modules/'
  ],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  snapshotSerializers: [
    'jest-serializer-vue'
  ],
  testMatch: [
    '**/__tests__/unit/*.test.(js|jsx|ts|tsx)'
  ],
  testPathIgnorePatterns: [
    '/.eslintrc/.js'
  ],
  testURL: 'http://localhost/',
  watchPlugins: [
    'jest-watch-typeahead/filename',
    'jest-watch-typeahead/testname'
  ]
}

最后还需要在 package.json 中添加测试指令

{
	"test:unit": "vue-cli-service test:unit --watch"
}

执行对应指令即可在项目中执行测试

$ npm run test:unit

项目目录结构
项目的目录结构组织如下:

├── src
│   ├── assets
│   ├── containers
│   │   └── TodoList    
│   │       ├── __mocks__                测试mocks文件
│   │       ├── __tests__                测试用例文件
│   │       │   ├── unit                 单元测试
│   │       │   │   └── TodoList.test.js
│   │       │   └── integration          集成测试
│   │       │       └── store.test.js
│   │       ├── components               子组件
│   │       │   ├── Header.vue
│   │       │   └── UndoList.vue
│   │       │   
│   │       └── TodoList.vue             TodoList父vue组件
│   │
│   ├── utils
│   │   └── testUtils.js                 存放测试工具公共工具
│   ├── App.vue                          vue-App
│   └── main.js                          入口文件
│
├── public
├── jest.config.js                       jest配置文件
├── ...
└── package.json

jest基础测试知识

jest 文件和目录命名规范

待测试文件: hello.js 测试脚本文件取名:hello.test.jsorhello.spec.js 测试目录:testsor__tests__

测试函数

test("测试用列描述信息",()=>{

})
// or
it("测试用例描述信息",()=>{

})

断言函数

测试即运行结果是否与我们预期结果一致 断言函数用来验证结果是否正确

exspect(运行结果).toBe(期望的结果);
//常见断言方法
expect({a:1}).toBe({a:1})//判断两个对象是否相等
expect(1).not.toBe(2)//判断不等
expect({ a: 1, foo: { b: 2 } }).toEqual({ a: 1, foo: { b: 2 } })
expect(n).toBeNull(); //判断是否为null
expect(n).toBeUndefined(); //判断是否为undefined
expect(n).toBeDefined(); //判断结果与toBeUndefined相反
expect(n).toBeTruthy(); //判断结果为true
expect(n).toBeFalsy(); //判断结果为false
expect(value).toBeGreaterThan(3); //大于3
expect(value).toBeGreaterThanOrEqual(3.5); //大于等于3.5
expect(value).toBeLessThan(5); //小于5
expect(value).toBeLessThanOrEqual(4.5); //小于等于4.5
expect(value).toBeCloseTo(0.3); // 浮点数判断相等
expect('Christoph').toMatch(/stop/); //正则表达式判断
expect(['one','two']).toContain('one'); //不解释

分组函数

describe("关于每个功能或某个组件的单元测试",()=>{
    // 不同用例的单元测试
})

常见命令

{
  "nocache": "jest --no-cache", //清除缓存
  "watch": "jest --watchAll", //实时监听
  "coverage": "jest --coverage",  //生成覆盖测试文档
  "verbose": "npx jest --verbose" //显示测试描述
}

Jest 钩子函数

  • beforeAll:在所有测试用例执行前调用
  • afterAll:在所有测试用例执行后调用
  • beforeEach:在每一个测试用例执行前调用
  • afterEach:在每一个测试用例执行后调用

describe: 分组测试

每个describe都有自己的作用域且互不影响。

对于其执行的逻辑一定要放在钩子函数中

Jest 中的 Mock

生成mock函数

const func = jest.fn()

mock 函数作用

1、 捕获函数的调用和返回结果,以及this和调用顺序

2、 可以让我们自由的设置返回结果

3、 改变函数的内部实现

API:

mockReturnValue

mockReturnValueOnce

mockImplementation

mockImplementationOnce

mockResolvedValue

mockResolvedValueOnce

mockReturnThis

jest.mock 发现 util 是一个类,会自动把类的构造函数和方法变成 jest.fn()

mock的输出格式

对于单元测试,外部 class 的实现无需关心,使用 jest.fn 生成一个 mock 类,例如测试 mock.js

输出的 mock 为:

{ 
    calls: [ [ 123 ], [ 123 ], [ 123 ] ],
    instances: [ undefined, undefined, undefined ],
    invocationCallOrder: [ 1, 2, 3 ],
    results: [ 
        { type: 'return', value: 456 },
        { type: 'return', value: 789 },
        { type: 'return', value: undefined } 
    ]
}

jest基础测试

对象等值测试

describe('对象测试', () => {

    it("是否同一个对象", () => {
        const foo = { a: 1 }
        expect(foo).toBe(foo)
    })

    it("对象值是否相等", () => {
        expect({ a: 1, foo: { b: 2 } }).toEqual({ a: 1, foo: { b: 2 } })
    })

    test('对象赋值', () => {
        const data = { one: 1 };
        data['two'] = 2;
        expect(data).toEqual({ one: 1, two: 2 });
    });

});

异步测试

异步测试脚本执行完,单元测试就结束了,如果需要延时才能断言的结果,单元测试函数需要设置 done 形参,在定时回调函数中调用,显示的通过单元测试已完成。

describe('异步操作测试',  () => {
    function foo(callback) {
        console.log('foo...')
        setTimeout(() => {
            callback && callback();
        }, 1000)
    }
    it('异步测试', (done) => {
        function bar() {
            console.log('bar..')
            done();
        }
        foo(bar);
    });
});

定时器测试(异步测试)及断言

基于 jest 提供的两个方法 jest.useFakeTimersjest.runAllTimers 可以更优雅的对延时功能的测试。

jest.useFakeTimers()避免定时器等待时间

jest.runAllTimers() 执行所有的timers

jest.runOnlyPendingTimers() 只执行当前队列中的timers

jest.advanceTimersByTime(msToRun) 时间快进多少

describe('定时器相关测试', () => {
    // 开启定时函数模拟
    jest.useFakeTimers();

    function foo(callback) {
        console.log('foo...')
        setTimeout(() => {
            callback && callback();
        }, 1000)
    }
    it('断言异步测试', () => {
        //创建mock函数,用于断言函数被执行或是执行次数的判断
        const callback = jest.fn();
        foo(callback);
        expect(callback).not.toBeCalled();
        //快进,使所有定时器回调
        jest.runAllTimers();
        expect(callback).toBeCalled();
    })
});

Dom 测试

测试 DOM 要记得把测试环境设为浏览器环境,jest 在底层模拟了一套 dom api

jest 提供了一套 node 环境下的 dom,在获取到指定的 dom 元素后,可以对 dom 元素执行各种操作:

const input = findTestWrapper(wrapper, ‘input’)
input.exists() // 获取dom存在性
input.setValue(‘csxiaoyao’) // 给dom赋值
input.trigger(‘keyup.enter’) // 触发dom方法
input.trigger(‘change’) // 触发dom方法

实现 dom 渲染测试,以及点击事件等交互功能测试。

describe('Dom测试', () => {
    it('测试按钮是否被渲染 ', () => {
        document.body.innerHTML = `
    <div>
        <button id='btn'>小按钮</button>
    </div> `
        console.log(document.getElementById('btn'), document.getElementById('btn').toString())
        expect(document.getElementById('btn')).not.toBeNull();
        expect(document.getElementById('btn').toString()).toBe("[object HTMLButtonElement]");
    });

    it('测试点击事件', () => {
        const onclick = jest.fn();
        document.body.innerHTML = `
        <div>
            <button id='btn'>小按钮</button>
        </div> `
        const btn = document.getElementById('btn');
        expect(onclick).not.toBeCalled();
        btn.onclick = onclick;
        btn.click();
        expect(onclick).toBeCalled();
        expect(onclick).toHaveBeenCalledTimes(1);
        btn.click();
        btn.click();
        expect(onclick).toHaveBeenCalledTimes(3);
    });
});

snapshot 快照测试

  • toMatchSnapshot 生成快照文件夹__snapshots__
  • toMatchInlineSnapshot 行内快照
  • 对于变化的配置量 Snapshot({ time2: expect.any(Date)})
  • 同样可以接收 Date | String | Number
test('should generateAnotherConfig 函数', () => {

  expect(generateAnotherConfig()).toMatchSnapshot({

    time2: expect.any(Date)

  })

})

Vue 测试

安装 unit-jest

如果创建的项目没有安装 unit-jest 依赖包,可以通过 vue add @vue/unit-jest 命令添加。否则通过脚手架手动模式创建一个包含 unit-jest 的项目。

基础知识

import { mount, shallowMount } from '@vue/test-utils’

不同的是,mount 方法会渲染完整的组件,包括子组件,适合 BDD 和集成测试,而 shallowMount 方法只会渲染当前组件,因此速度更快,效率更高,更加适合 TDD 和单元测试。

mount 和 shallowMount 的区别 - shallowMount 只挂载指定组件,不挂载子组件 - mount 挂载所有组件

为了方便获取测试需要的 DOM 元素,可以将获取 DOM 元素的方法进行封装,在 testUtils.js 中定义 findTestWrapper 方法如下:

export const findTestWrapper = (wrapper, tag) => {
	return wrapper.find(`[data-test="${tag}"]`)
}

Vue 的渲染机制 默认情况下 Vue 会异步地批量执行更新 (在下一轮 tick),以避免不必要的 DOM 重绘或者是观察者计算

异步测试需要在 nextTick () 之后执行

hello Jest Vue

vue 组件渲染测试

it('挂载countBtn组件', () => {
        const wraper = shallowMount(CountBtn);
        const btn = wraper.find("button");
        expect(wraper.html()).toBe(`<button>点击次数0</button>`);
    });

vm 实例测试

大部分的自动化测试,都是通过 vm 实例上的 data 变化来测试的,可以获取对应的 data 值,也可以通过 vm 调用相关方法。

wrapper.vm.java 檢查前端穿過來的值 java前端测试_软件测试emit(‘add’, content) // 触发外部 add 方法

事件测试

vue 组件点击事件测试

it('测试countBtn组件点击', (done) => {
    const wraper = shallowMount(CountBtn);
    const btn = wraper.find("button");
    expect(wraper.html()).toBe(`<button>点击次数0</button>`);
    btn.trigger('click');
    setTimeout(() => {
        expect(wraper.html()).toBe(`<button>点击次数1</button>`);
        done();
    }, 1000);
});

it('优雅的测试点击事件', async () => {
    const wraper = shallowMount(CountBtn);
    const btn = wraper.find("button");
    expect(wraper.html()).toBe(`<button>点击次数0</button>`);
    btn.trigger('click');
    await wraper.vm.$nextTick();
    expect(wraper.html()).toBe(`<button>点击次数1</button>`);
});

异步操作

定时器测试,可以借助 vm.$nextTick 方法和 jest 定时器操作实现

beforeEach(() => {
	jest.useFakeTimers()
})
it(`
    1. 用户进入页面时,等待 3s
    2. 列表应该展示远程返回的数据
       `, (done) => {
       const wrapper = mount(TodoList, { store }) // 传入 store
       jest.runAllTimers()
       wrapper.vm.$nextTick(() => {
       	const listItems = findTestWrapper(wrapper, 'list-item')
       	// 不能直接判断,因为异步操作在 mounted 之后
       	expect(listItems.length).toBe(2)
       	done()
       })
       })

axios 异步测试

本地去模拟axios请求

实现方法

1.直接 使用创建的 __mock__ 模拟

2.打开jest.config.js Line 6 automock: true

模拟异步请示,测试渲染结果是否一致

<!-- User.vue -->
<template>
<table>
    <tr v-for="item in list" :key="item.id">
        <td>{{item.id}}</td>
        <td>{{item.name}}</td>
        <td>{{item.age}}</td>
    </tr>
</table>
</template>

<script>
export default {
    data() {
        return {
            list: []
        }
    },
    created() {
        this.$http.get('/user').then(({
            data
        }) => {
            this.list = data
        })
    }
}
</script>
// User.spec.js
import { mount } from '@vue/test-utils';
import User from '@/components/User';

it('测试用户组件', async() => {
    const wrapper = mount(User,{
        mocks:{
            $http:{
                get: url=>Promise.resolve({data:[{id:1,name:'xxxx',age:18},{id:2,name:'yyyy',age:19}]})
            }
        }
    })
    console.log(wrapper.html())
    // 渲染前
    expect(wrapper.html()).toBe('<table></table>');
		//对于异步请求,可以使用 vue 的 vm.$nextTick 方法实现异步数据的渲染。
    await wrapper.vm.$nextTick();
    // 渲染后
    // console.log(wrapper.html())
    // console.log(wrapper.find('tr'))
    expect(wrapper.findAll('tr').length).toBe(2)
    expect(wrapper.findAll('td').at(2).html()).toBe('<td>18</td>')

});