Vue2测试

测试

黑盒测试

  • 黑盒测试一般也被称为功能测试,黑盒测试要求测试人员将程序看作一个整体,不考虑其内部结构和特性,只是按照期望验证程序是否能正常工作(期望结果,不管逻辑实现)

白盒测试

  • 白盒测试是基于代码本身的测试,一般指对代码逻辑结构的测试(我知道里面的逻辑是怎么实现的)

测试分类

单元测试(Unit Testing)

  • 单元测试是指对程序中最小可测试单元进行的测试,例如测试一个函数一个模块一个组件

集成测试(Integration Testing)

  • 将已测试过的单元测试函数进行组合集成暴露出的高层函数或类的封装,对这些函数或类进行的测试

端到端测试(E2E Testing)

  • 打开应用程序模拟输入,检查功能以及界面是否正确

TDD & BDD

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

    • TDD的原理是在开发功能代码之前,先编写单元测试用例代码
    • 先有预期再填坑
  • BDD是行为驱动开发(Behavior-Driven Development)

    • 根据用户的行为进行测试,先开发在测试是否符合预期,一般是黑盒测试
  • 系统业务专家、开发者、测试人员一起合作,分析软件的需求,然后将这些需求写成一个个的故事。开发者负责填充这些故事的内容,保证程序实现效果与用户需求一致。

  • 总结: TDD是先写测试在开发 (一般都是单元测试,白盒测试),而BDD则是按照用户的行为来开发,在根据用户的行为编写测试用例 (一般都是集成测试,黑盒测试

前端测试框架

  • Karma

    • Karma为前端自动化测试提供了跨浏览器测试的能力,可以在浏览器中执行测试用例
    • 真的开一个浏览器去测试,可以跑在不同的浏览器里,测试样式的时候,常用
  • Mocha

    • 前端自动化测试框架,需要配合其他库一起使用,像chai、sinon…
    • 只提供一个自动化测试环境、断言(asset(1==1,’出错啦‘))
  • Jest

    • Jest 是facebook推出的一款测试框架,集成了 Mocha,chai,jsdom(node环境模拟dom环境),sinon等功能
    • 自带测试覆盖率
    • 零配置
    • 无法测试样式
  • vue/test-utils

    • 提供了丰富的api,而且可以和Karma一起使用

Jest

  • https://jest.nodejs.cn/
  • https://jestjs.io/zh-Hans/docs/getting-started
  • https://jest.nodejs.cn/docs/getting-started

安装

npm init -y # 初始化pacakge.json
npm i jest
npm i @types/jest
  • 默认会查找以.test.js结尾的文件进行测试
  • 我们建立一个qs.test.js来专门编写测试用例,这里的用例你可以认为就是一条测试功能 (后缀要以.test.js结尾,这样jest测试时默认会调用这个文件)

分组、用例

describe it

describe 可以嵌套

我们建立一个qs.test.js来专门编写测试用例,这里的用例你可以认为就是一条测试功能 (后缀要以.test.js结尾,这样jest测试时默认会调用这个文件)

import {parser,stringify} from './qs';

it('测试 parser 是否能正常解析结果',()=>{
    // expect 断言,判断解析出来的结果是否和 {name:'zf'}相等
    expect(parser(`name=zf`)).toEqual({name:'zf'});
})

jest默认自带断言功能,断言的意思就是判断是不是这个样子,我断定你今天没吃饭~,结果你吃了。说明这次断言就失败了,测试就无法通过

通过配置scripts 来执行命令

"scripts": {
    "test": "jest"
}

执行 npm run test,可惜的是默认在node环境下不支持es6模块的语法,需要babel转义,当然你也可以直接使用commonjs规范来导出方法,因为大多数现在开发都采用es6模块,所以就安装一下~

# core是babel的核心包 preset-env将es6转化成es5
npm i @babel/core @babel/preset-env --save-dev

并且配置.babelrc文件,告诉babel用什么来转义

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

默认jest中集成了babel-jest,运行时默认会调用.babelrc进行转义,可以直接将es6转成es5语法
运行 npm run test 出现

import {parser,stringify} from './qs';
describe('测试qs 库',()=>{
    it('测试 parser 是否能正常解析结果',()=>{
        expect(parser(`name=zf`)).toEqual({name:'zf'});
    })
    
    it('测试 stringify 是否正常使用stringify',()=>{
        expect(stringify({name:'zf'})).toEqual(`name=zf`)
    })
});

describe的功能是给用例分组,这样可以更好的给用例分类,其实这就是我们所谓的单元测试,对某个具体函数和功能进行测试

如果不写describe,相当于在最外层使用这个describe

matchers匹配器

  • 匹配器分为三类、判断相等、不等、是否包含
describe('查看常见的匹配器',()=>{
  it('判断是否相等',()=>{
    expect(1+1).toBe(2); // 相等于 js中的===
    expect({name:'zf'}).toEqual({name:'zf'}); // 比较内容是否相等
    expect(true).toBeTruthy(); // 是否为 true / false 也可以用toBe(true)
    expect(false).toBeFalsy();
});

it('判断不相等关系',()=>{
    expect(1+1).not.toBe(3); // not取反
    expect(1+1).toBeLessThan(5); // js中的小于
    expect(1+1).toBeGreaterThan(1); // js中的大于
});

it('判断是否包含',()=>{
    expect('hello world').toContain('hello'); // 是否包含
    expect('hello world').toMatch(/hello/); // 可以写正则 可以写字符串
});
   
});
  • jest可以进行配置,哪些文件变化了,只测试变化了的文件,使用it.only代替it

Jest常用命令

我们希望每次更改测试后,自动重新执行测试,修改执行命令

"scripts": {
    "test": "jest --watchAll"
}

重新执行 npm run test,这时就会监控用户的修改
提示我们按下w,显示更多信息

测试操作节点方法

  • dom.js

    // dom.js
    export const removeNode = (node) => {
        node.parentNode.removeChild(node)
    };
    
  • 测试传入一个节点,这个节点是否能从DOM中删除

    • jest中有js-dom,可以在node环境中模拟一套dom结构
    import { removeNode } from './dom'
    
    describe('测试dom库',()=>{
       it('测试删除节点',()=>{
        document.body.innerHTML = `<div><button data-btn="btn"></button</div>`
        let btn = document.querySelector('[data-btn="btn"]')
        // 判断元素是否存在
        expect(btn).not.toBeNull()
        // 删除元素
        removeNode(btn);
        // 重新获取dom映射
        btn = document.querySelector('[data-btn="btn"]');
        // 判断元素是否从DOM中删除
        expect(btn).toBeNull()
     })
    });
    

异步函数的测试

  • 异步无非就两种情况,一种是回调函数的方式,一种就是现在流行的promise方式

    export const getDataThroughCallback = fn => {
      setTimeout(() => {
        fn({ name: "zf" });
      }, 1000);
    };
    
    export const getDataThroughPromise = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({ name: "zf" });
        }, 1000);
      });
    };
    
  • 编写async.test.js方法

    import {getDataThroughCallback,getDataThroughPromise} from './3.getData';
    
    describe('测试异步获取数据的方法', () => {
      // 默认测试用例不会等待测试完成,所以增加done参数,当完成时调用done函数
      it('测试传入回调函数 获取异步返回结果', (done) => { // 异步测试方法可以通过done
        getDataThroughCallback((data) => {
          expect(data).toEqual({ name: 'zf' });
          done();
        })
      })
      // 返回一个promise 会等待这个promise执行完成
      it('测试promise 返回结果 1', () => {
        return getDataThroughPromise().then(data => {
          expect(data).toEqual({ name: 'zf' });
        })
      })
      // 直接使用async + await语法
      it('测试promise 返回结果 2', async () => {
        let data = await getDataThroughPromise();
        expect(data).toEqual({ name: 'zf' });
      })
      // 使用自带匹配器
      it('测试promise 返回结果 3', async () => {
        expect(getDataThroughPromise()).resolves.toMatchObject({ name: 'zf' })
      })
    
    });
    

Jest中的mock

模拟Timer

我们期望传入一个callback,想看下callback能否被调用!

export const timer = callback=>{
    setTimeout(()=>{
        callback();
    },2000)
}

很容易写出了这样的测试用例

import {timer} from './timer';
it('callback 是否会执行',(done)=>{
    let fn = jest.fn();
    timer(fn);
    setTimeout(()=>{
        expect(fn).toHaveBeenCalled();
        done();
    },2500)
});

如果时间很长呢? 很多个定时器呢?这时候我们想到了mock Timer

import {timer} from './timer';
jest.useFakeTimers();
it('callback 是否会执行',()=>{
    let fn = jest.fn();
    timer(fn);
    // 运行所有定时器,如果需要测试的代码是个秒表呢?
    // jest.runAllTimers();
    
    // 将时间向后移动2.5s 也就是快进
    // jest.advanceTimersByTime(2500);

    // 只运行当前等待定时器
    jest.runOnlyPendingTimers();
    expect(fn).toHaveBeenCalled();
});

模拟函数jest.fn()

需要判断函数的返回结果就可以啦,像这样

import { myMap } from "./map";
it("测试 map方法", () => {
  let fn = item => item * 2;
  expect(myMap([1, 2, 3], fn)).toEqual([2, 4, 6]);
});

但是我想更细致一些,像每一次调用函数传入的是否是数组的每一项,函数是否被调用了三次,说的更明确些就是想追溯函数具体的执行过程!

import { myMap } from "./map";
it("测试 map 方法", () => {
  // 通过jest.fn声明的函数可以被追溯
  let fn = jest.fn(item => (item *= 2));
  expect(myMap([1, 2, 3], fn)).toEqual([2, 4, 6]);
  // 调用3次
  expect(fn.mock.calls.length).toBe(3); 
  // 每次函数返回的值是 2,4,6
  expect(fn.mock.results.map(item=>item.value)).toEqual([2,4,6])
});

详细看下这个mock中都有什么东东

模拟文件jest.mock()

mock整个文件

我们希望对接口进行mock,可以直接在__mocks__目录下创建同名文件,将整个文件mock掉,例如当前文件叫api.js

// api.js
mport axios from "axios";

export const fetchUser = ()=>{
    return axios.get('/user')
}
export const fetchList = ()=>{
    return axios.get('/list')
}

创建__mocks__/api.js

// 两个下划线 __mocks__/api.js
export const fetchUser = ()=>{
    return new Promise((resolve,reject)=> resolve({user:'zf'}))
}
export const fetchList = ()=>{
    return new Promise((resolve,reject)=>resolve(['香蕉','苹果']))
}

开始测试

jest.mock('./api.js'); // 使用__mocks__ 下的api.js

describe('测试能否正常获取数据', () => {
  it('fetchUser测试',async ()=>{
    let data = await fetchUser();
    expect(data).toEqual({user:'zf'})
})

it('fetchList测试',async ()=>{
    let data = await fetchList();
    expect(data).toEqual(['香蕉','苹果'])
})

});

这里需要注意的是,如果mock的api.js方法不全,在测试时可能还需要引入原文件的方法,那么需要使用jest.requireActual('./api.js') 引入真实的文件。

这里我们想这样做是不是有些麻烦呢,其实只是想将真正的请求mock掉而已,那么我们是不是可以直接mock axios方法呢?

mock axios中的方法

__mocks__下创建 axios.js 重写get方法

export default {
    get(url){
        return new Promise((resolve,reject)=>{
            if(url === '/user'){
                resolve({user:'zf'});
            }else if(url === '/list'){
                resolve(['香蕉','苹果']);
            }
        })
    }
}

当方法中调用axios时默认会找__mocks__/axios.js

jest.mock('axios'); // mock axios方法
import {fetchList,fetchUser} from './api';
it('fetchUser测试',async ()=>{
    let data = await fetchUser();
    expect(data).toEqual({user:'zf'})
})

it('fetchList测试',async ()=>{
    let data = await fetchList();
    expect(data).toEqual(['香蕉','苹果'])
})

Jest中的钩子函数

为了测试的便利,Jest中也提供了类似于Vue一样的钩子函数,可以在执行测试用例前或者后来执行

class Counter {
  constructor() {
    this.count = 0;
  }
  add(count) {
    this.count += count;
  }
}
module.exports = Counter;

我们要测试Counter类中add方法是否符合预期,来编写测试用例

import Counter from './hook'
it('测试  counter增加 1 功能',()=>{
    let counter = new Counter; // 每个测试用例都需要创建一个counter实例,防止相互影响
    counter.add(1);
    expect(counter.count).toBe(1)
})

it('测试  counter增加 2 功能',()=>{
    let counter = new Counter;
    counter.add(2);
    expect(counter.count).toBe(2)
})

我们发现每个测试用例都需要基于一个新的counter实例来测试,防止测试用例间的相互影响,这时候我们可以把重复的逻辑放到钩子中!

钩子函数

  • beforeAll 在所有测试用例执行前执行
  • afteraAll 在所有测试用例执行后
  • beforeEach 在每个用例执行前
  • afterEach 在每个用例执行后
import Counter from "./hook";
let counter = null;
beforeAll(()=>{
    console.log('before all')
})
afterAll(()=>{
    console.log('after all')
})
beforeEach(() => {
  console.log('each')
  counter = new Counter();
});
afterEach(()=>{
    console.log('after');
})
it("测试  counter增加 1 功能", () => {
  counter.add(1);
  expect(counter.count).toBe(1);
});
it("测试  counter增加 2 功能", () => {
  counter.add(2);
  expect(counter.count).toBe(2);
});

钩子函数可以多次注册,一般我们通过describe 来划分作用域

import Counter from "./hook";
let counter = null;
beforeAll(() => console.log("before all"));
afterAll(() => console.log("after all"));
beforeEach(() => {
  counter = new Counter();
});
describe("划分作用域", () => {
  beforeAll(() => console.log("inner before")); // 这里注册的钩子只对当前describe下的测试用例生效
  afterAll(() => console.log("inner after"));
  it("测试  counter增加 1 功能", () => {
    counter.add(1);
    expect(counter.count).toBe(1);
  });
});
it("测试  counter增加 2 功能", () => {
  counter.add(2);
  expect(counter.count).toBe(2);
});
// before all => inner before=> inner after => after all
// 执行顺序很像洋葱模型 ^-^

Jest中的配置文件

我们可以通过jest命令生成jest的配置文件

npx jest --init

会提示我们选择配置项:

➜  unit npx jest --init
The following questions will help Jest to create a suitable configuration for your project
# 使用jsdon
✔ Choose the test environment that will be used for testing › jsdom (browser-like)
# 添加覆盖率
✔ Do you want Jest to add coverage reports? … yes
# 每次运行测试时会清除所有的mock
✔ Automatically clear mock calls and instances between every test? … yes

在当前目录下会产生一个jest.config.js的配置文件

jest覆盖率

执行 npx jest --init,会得到一个配置文件

刚才产生的配置文件我们已经勾选需要产生覆盖率报表,所有在运行时我们可以直接增加 --coverage参数

"scripts": {
    "test": "jest --coverage"
}

可以直接执行npm run test,此时我们当前项目下就会产生coverage报表来查看当前项目的覆盖率

---------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 hook.js  |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.856s, estimated 2s

命令行下也会有报表的提示,jest增加覆盖率还是非常方便的~

  • Stmts表示语句的覆盖率
  • Branch表示分支的覆盖率(if、else)
  • Funcs函数的覆盖率
  • Lines代码行数的覆盖率

到此我们的Jest常见的使用已经基本差不多了!接下我们来看看如何利用Jest来测试Vue项目!

Vue中集成Jest

我们可以通过vue官方提供的@vue/cli 直接创建Vue项目,在创建前需要先安装好@vue/cli~

这里直接创建项目:

vue create vue-unit-project
? Please pick a preset:
  default (babel, eslint)
❯ Manually select features # 手动选择
? Check the features needed for your project:
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◯ CSS Pre-processors
 ◯ Linter / Formatter
❯◉ Unit Testing
 ◯ E2E Testing
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, Unit
? Use history mode for router?  # history模式
ion) Yes
? Pick a unit testing solution: Jest # 测试框架选择Jest
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config  # 将配置文件产生独立的文件
 files
? Save this as a preset for future projects? (y/N) # 是否保存配置

初始化成功后,我们先来查看项目文件,因为我们主要关注的是测试,所以先来查看下jest.config.js文件

module.exports = {
  moduleFileExtensions: [ // 测试的文件类型
    'js','jsx','json','vue'
  ],
  transform: { // 转化方式
    '^.+\\.vue$': 'vue-jest', // 如果是vue文件使用vue-jest解析
    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', // 如果是图片样式则使用 jest-transform-stub
    '^.+\\.jsx?$': 'babel-jest' // 如果是jsx文件使用 babel-jest
  },
  transformIgnorePatterns: [ // 转化时忽略 node_modules
    '/node_modules/'
  ],
  moduleNameMapper: { // @符号 表示当前项目下的src
    '^@/(.*)$': '<rootDir>/src/$1'
  },
  snapshotSerializers: [ // 快照的配置
    'jest-serializer-vue'
  ],
  testMatch: [ // 默认测试 /test/unit中包含.spec的文件 和__tests__目录下的文件
    '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
  ],
  testURL: 'http://localhost/', // 测试地址
  watchPlugins: [ // watch提示插件
    'jest-watch-typeahead/filename',
    'jest-watch-typeahead/testname'
  ]
}

通过配置文件的查看我们知道了所有测试都应该放在tests/unit目录下!

我们可以查看pacakge.json来执行对应的测试命令

"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "test:unit": "vue-cli-service test:unit --watch" // 这里增加个 --watch参数
},

开始测试 npm run test:unit

测试Vue组件

我们先忽略默认example.spec.js文件,先来自己尝试下如何测试Vue组件

测试HelloWorld组件

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

HelloWorld组件需要提供一个msg属性,将msg属性渲染到h1标签中,ok我们来编写测试用例

tests/unit下创建 HelloWorld.spec.js

import Vue from 'vue';
import HelloWorld from '@/components/HelloWorld'
describe('测试HelloWolrd 组件',()=>{
    it('传入 msg 属性看能否渲染到h1标签内',()=>{
        const  baseExtend = Vue.extend(HelloWorld);
        // 获取当前组件的构造函数,并且挂载此组件
        const vm = new baseExtend({
            propsData:{
                msg:'hello'
            }
        }).$mount();
        expect(vm.$el.innerHTML).toContain('hello');
    })
});

这样一个简单的Vue组件就测试成功了,但是写起来感觉不简洁也不方便!所以为了更方便的测试Vue官方提供给我们了个测试工具Vue Test Utils,而且这个工具为了方便应用,采用了同步的更新策略

import Vue from 'vue';
import HelloWorld from '@/components/HelloWorld';
import {shallowMount} from '@vue/test-utils'
describe('测试HelloWolrd 组件',()=>{
    it('传入 msg 属性看能否渲染到h1标签内',()=>{
        const wrapper = shallowMount(HelloWorld,{
            propsData:{
                msg:'hello'
            }
        })
        expect(wrapper.find('h1').text()).toContain('hello')
    });
});

这样写测试是不是很hi,可以直接渲染组件传入属性,默认返回wrapperwrapper上提供了一系列方法,可以快速的获取dom元素! 其实这个测试库的核心也是在 wrapper的方法上, 更多方法请看 Vue Test Utils

这里的shallowMount被译为潜渲染,也就是说HelloWorld中引入其他组件是会被忽略掉的,当然也有深度渲染mount方法!

刚才写测试的这种方式就是先编写功能!编写完成后,我们来模拟用户的行为进行测试,而且只测试其中的某个具体的功能! 这就是我们所谓的 BDD形式的单元测试。接下来,我们再来换种思路再来写个组件!

测试Todo组件

这回呢,我们来采用TDD的方式来测试,也就是先编写测试用例

先指定测试的功能: 我们要编写个Todo组件

  • 当输入框输入内容时会将数据映射到组件实例上
  • 如果输入框为空则不能添加,不为空则新增一条
  • 增加的数据内容为刚才输入的内容

编写Todo.spec.js

import Todo from '@/components/Todo.vue';
import {shallowMount} from '@vue/test-utils'
describe('测试Todo组件',()=>{
    it('当输入框输入内容时会将数据映射到组件实例上',()=>{
        // 1) 渲染Todo组件
        let wrapper = shallowMount(Todo);
        let input = wrapper.find('input');
        // 2.设置value属性 并触发input事件
        input.setValue('hello world');
        // 3.看下数据是否被正确替换
        expect(wrapper.vm.value).toBe('hello world')
    });
    it('如果输入框为空则不能添加,不为空则新增一条',()=>{
        let wrapper = shallowMount(Todo);
        let button = wrapper.find('button');
        // 点击按钮新增一条
        wrapper.setData({value:''});// 设置数据为空
        button.trigger('click');
        expect(wrapper.findAll('li').length).toBe(0);
        wrapper.setData({value:'hello'});// 写入内容
        button.trigger('click');
        expect(wrapper.findAll('li').length).toBe(1);
    });
    it('增加的数据内容为刚才输入的内容',()=>{
        let wrapper = shallowMount(Todo);
        let input = wrapper.find('input');
        let button = wrapper.find('button');
        input.setValue('hello world');
        button.trigger('click');
        expect(wrapper.find('li').text()).toMatch(/hello world/);
    });
});

我们为了跑通这些测试用例,只能被迫写出对应的代码!

<template>
 <div>
  <input type="text" v-model="value" />
  <button @click="addTodo"></button>
  <ul>
   <li v-for="(todo,index) in todos" :key="index">{{todo}}</li>
  </ul>
 </div>
</template>
<script>
export default {
 methods: {
  addTodo() {
   this.value && this.todos.push(this.value)
  }
 },
 data() {
  return {
   value: "",
   todos: []
  };
 }
};
</script>

以上就是我们针对Todo这个组件进行了单元测试,但是真实的场景中可能会更加复杂,在真实的开发中,我们可能将这个Todo组件进行拆分,拆分成TodoInput组件和TodoList组件和TodoItem组件,如果采用单元测试的方式,就需要依次测试每个组件(单元测试是以最小单元来测试) 但是单元测试无法保证整个流程是可以跑通的,所以我们在单元测试的基础上还要采用集成测试

总结:

1.单元测试可以保证测试覆盖率高,但是相对测试代码量大,缺点是无法保证功能正常运行

2.集成测试粒度大,普遍覆盖率低,但是可以保证测试过的功能正常运行

3.一般业务逻辑会采用BDD方式使用集成测试(像测试某个组件的功能是否符合预期)一般工具方法会采用TDD的方式使用单元测试

4.对于 UI 组件来说,我们不推荐一味追求行级覆盖率,因为它会导致我们过分关注组件的内部实现细节,从而导致琐碎的测试

测试Vue中的异步逻辑

在测试Vue项目中,我们可能会在组件中发送请求,这时我们仍然需要对请求进行mock

<template>
  <ul>
   <li v-for="(list,index) in lists" :key="index">{{list}}</li>
  </ul>
</template>
<script>
import axios from 'axios'
export default {
 async mounted(){
    let {data} = await axios.get('/list');
    this.lists = data;
 },
 data() {
  return {
   lists: []
  };
 }
};
</script>

可以参考上一章节 如何实现jest进行方法的mock

import List from "@/components/List.vue";
import { shallowMount } from "@vue/test-utils";
jest.mock("axios");
it("测试List组件", done => {
  let wrapper = shallowMount(List);
  setTimeout(() => {
    expect(wrapper.findAll("li").length).toBe(3);
    done();
  });
});

这里使用setTimeout的原因是我们自己mock的方法是promise,所以是微任务,我们期望微任务执行后在进行断言,所以采用setTimeout进行包裹,保证微任务已经执行完毕! 如果组件中使用的不是 async、await形式,也可以使用 $nextTick, (新版node中await后的代码会延迟到下一轮微任务执行)

举个例子:

function fn(){
    return new Promise((resolve,reject)=>{
        resolve([1,2,3]);
    })
}
async function getData(){
    await fn(); 
    // await fn()  会编译成
    // new Promise((resolve)=>resolve(fn())).then(()=>{
    //     console.log(1)
    // })
    console.log(1);
}
getData();
Promise.resolve().then(data=>{
    console.log(2);
});

当然不同版本执行效果可能会有差异

来简单看下不是async、await的写法~~~

axios.get('/list').then(res=>{
    this.lists = res.data;
})
it('测试List组件',()=>{
    let wrapper = shallowMount(List);
    // nextTick方法会返回一个promise,因为微任务是先进先出,所以nextTick之后的内容,会在数据获取之后执行
    return wrapper.vm.$nextTick().then(()=>{
        expect(wrapper.vm.lists).toEqual([1,2,3])
    })
})

测试Vue中的自定义事件

我们写了一个切换显示隐藏的组件,当子组件触发change事件时可以切换p标签的显示和隐藏效果

<template>
    <div>
        <Head @change="change"></Head>
        <p v-if="visible">这是现实的内容</p>
    </div>
</template>
<script>
import Head from './Head'
export default {
    methods:{
        change(){
            this.visible = !this.visible;
        }
    },
    data(){
        return {visible:false}
    },
    components:{
        Head
    }
}
</script>

我们来测试它!可以直接通过wrapper.find方法找到对应的组件来发射事件

import Modal from '@/components/Modal';
import Head from '@/components/Head';
import {mount, shallowMount} from '@vue/test-utils'
it('测试 触发change事件后 p标签是否可以切换显示',()=>{
    let wrapper = shallowMount(Modal);
    let childWrapper = wrapper.find(Head);
    expect(wrapper.find('p').exists()).toBeFalsy()
    childWrapper.vm.$emit('change');
    expect(childWrapper.emitted().change).toBeTruthy(); // 检验方法是否被触发
    expect(wrapper.find('p').exists()).toBeTruthy(); // 检验p标签是否显示
})

到这里我们对vue的组件测试已经基本搞定了,接下来我们再来看下如何对Vue中的VuexVue-router进行处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值