前端自动化测试(一)jest学习与使用

下一篇文章
前端自动化测试(二)TDD与BDD 实todoList
什么是前端自动化测试?
前端编写一段js 去运行生产中的js代码,我们预期会出现的结果与实际出现的结果是否相等,在上线前检测成问题,这样通过代码自动检测,而非人肉点击就是前端自动化测试

前端自动化测试的优势

​ 1.更好的代码组织,项目的可维护性增强。
​ 2.更小的Bug出现概率,尤其是回归测试中的Bug。
​ 3.修改工程质量差的项目,更加安全。
4.项目具备潜在的文档特性。
​ 5.扩展前端知识面。

学习自动化测试更有利于读懂各种源码,测试用例其实就是源码的文档,他详细告诉了实现的功能

tips:下列指定版本只是为了,以后学习不冲突

一、安装

  npm i jest@24.8.0 -D

二、配置jest

// 初始化配置
npx jest --init

package.json

// 配置启动,加上--watchAll会自动监听所有test文件改动并运行 默认是a模式
// --watch 自动是o模式
// 配置任意一个,就会默认为该模式,同时在watch Usage中消失
  "scripts": {
    "test": "jest --watchAll" 
  },

jest 命令行使用

// 按 f 以仅运行失败的测试。
› Press f to run only failed tests.
// 按 o 仅运行与已更改文件相关的测试。
 › Press o to only run tests related to changed files.
//  按 p 以按文件名正则表达式模式进行筛选
//只想执行那些测试文件就可以用p模式 
 › Press p to filter by a filename regex pattern.
//  按 t 以按测试名称 regex 模式进行筛选
// 只想执行那些测试用例就可以用t模式 也叫feilter模式
 › Press t to filter by a test name regex pattern.
//  按q退出监视
 › Press q to quit watch mode.
//  按u确定更新快照(只在快照时显示)
 › Press u to update failing snapshots.
//  按i以交互方式更新失败的快照。
  Press i to update failing snapshots interactively.
// 按 s 跳过当前用例
   Press s to skip the current test.
//  按 Enter 可触发测试运行。
 › Press Enter to trigger a test run.
//  按w展示所有使用方式
 Watch Usage: Press w to show more.

当没有改动或者错误的文件时,再次按对应的就能退出该模式
按 o的时候报错
–watch is not supported without git/hg, please use --watchAll
需要在git中使用,通过git来记录修改的文件所以要安装git并初始化git文件同时需要提交到本地

  1. 查看测试覆盖率
    npx jest --coverage

在jest.config.js中 添加

 coverageDirectory: "coverage",//生成代码覆盖率报告会放在coverage目录下

会在根目录生成coverage文件夹,点击打开lcov-report 下的 index.html 就能看见图形化界面

  1. jest 默认是commoJs的规范是在node环境下运行,如果想用es6的语法,需要安装babel转换
    在你运行jest时,jest内部封装了,会运行 jest(babel-jest)检测你是否安装了babel-core,就会去取.babelrc的配置,
    运行测试前结合babel先把代码进行一次转化
cnpm install @babel/core@7.4.5 @babel/preset-env@7.4.5 -D

配置.babelrc

在根目录新建一个文件.babelrc

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

三、初识jest

我们简单写点测试用例来认识一下jest

新建一个math.js 用来创建 加减方法

export function add(x,y){
  return x+y
}

export function minus(x,y){
  return x-y
}
export function multi(x,y){
  return x*y
}

下面我们就来测试一下这个math.js

//引入需要测试的方法
import {add,minus,multi} from './math'
  
test('测试加法 3+3',()=>{
    //我期望3+3得到6
  expect(add(3,3)).toBe(6)
})
  
test('测试减法 3-3',()=>{
  expect(minus(3,3)).toBe(0)
})
  
test('测试乘法 3*3',()=>{
  expect(multi(3,3)).toBe(9)
})

上面的测试是否很简单,没错前端测试就是这样简单明了,

通过代码去执行方法,或模拟用户行为

test原理 简单用原生js来实现一下上述代码

import {add,minus,multi} from './math'

var result = add(3,7);
var expected = 10;
if(result!== expected){
  throw Error(`3+7应该等于${expected},但是结果却是${result}`)
}

var result = minus(3,3);
var expected = 0;
if(result!== expected){
  throw Error(`3-3应该等于${expected},但是结果却是${result}`)
}



// 实现一个 
//  expect(add(3,3)).toBe(6)
//  expect(minus(6,3)).toBe(3)
function expect (result){
  return {
    toBe: function(actual){
        if(result!==actual){
          throw new Error(`预期值和实际值不相等 预期${actual} 结果却是${result}`)
        }
    }
  }
}
//  expect(add(3,3)).toBe(6)

// 进一步完善
function test (desc,fn){
  try{
    fn()
    console.log(`${desc}通过测试`);
  } catch(e){
    console.log(`${desc}没有通过测试 ${e}`);
  }
}

test('测试加法 3+3',()=>{
  expect(add(3,3)).toBe(6)
})

接下来让我们简单的认识一部分jest的匹配器,

jest匹配器


  
test('测试10与10匹配',()=>{
  //toBe 匹配器
  //类似 object.is  ===
  // 只能匹配值,不能匹配对象等引用
  expect(10).toBe(10)
})


test ('测试对象内容相等',()=>{
  //toEqual 匹配器
  // 能匹配值,对象等引用
  const a = {one:1};
  expect(a).toEqual({one:1})
})

test ('测试内容为null',()=>{
  //toBeNull 匹配器
  const a = null;
  expect(a).toBeNull();
})

test ('测试内容为undefined',()=>{
  //toBeUndefined 匹配器
  const a = undefined;
  expect(a).toBeUndefined();
})

test ('测试内容为defined',()=>{
  //toBeDefined 匹配器
  const a = null;
  expect(a).toBeDefined();
})

test("测试内容为真",()=>{
  // toBeTruthy 匹配器
  const a = 1;
  expect(a).toBeTruthy()
})
test("测试内容为假",()=>{
  // toBeFalsy 匹配器
  const a = 0;
  expect(a).toBeFalsy()
})

test("不为真",()=>{
  // not 匹配器取反操作
  const a = 1
  expect(a).not.toBeFalsy()
})
// 数字相关匹配器

test('count大于9',()=>{
  const count = 10
  expect(count).toBeGreaterThan(9);
})

test('count小于9',()=>{
  // toBeLessThanOrEqual
  const count = 8
  expect(count).toBeLessThan(9);
})
test('count大于等于9',()=>{
  const count = 9
  expect(count).toBeGreaterThanOrEqual(9);
})


// js运算错误示例
test("0.1+0.2",()=>{
  const a = 0.1
  const b = 0.2
  // expect(a+b).toEqual(0.3)
  /* 
   Expected: 0.3
    Received: 0.30000000000000004
*/
// 对于浮点型计算匹配需要使用
// toBeCloseTo
expect(a+b).toBeCloseTo(0.3)
})

// String 相关匹配器
test("str中包含字符",()=>{
    //toMatch 可以是正则表达式
    const str = "www.baidu.com"
    // expect(str).toMatch('baidu')
     expect(str).toMatch(/baid/)
})

// 数组相关匹配器
test("数组中包含某一项",()=>{
  const arr = ['a','b','c']
  // 可以set后在匹配
  expect(arr).toContain('a')
})

// 异常
const throwNewErrorFunc = ()=>{
  throw new Error('this is a new error')
}
test('toThorow',()=>{
  expect(throwNewErrorFunc).toThrow()
  // 如果要填写内容意思就是匹配异常内容相当,也可以是正则表达式
})

jest中的钩子函数

大致分为 :

beforeAll 所有测试用例之前

beforeEach 每个测试用例执行前都调用

afterEach 每个测试用例执行之后

afterAll 所有测试用执行之后

下面来测试一下执行顺序

​ 对于归类分组,你可以手动自己分文件,来归类,也可以用describe来分类,

每一个describe都是一个单独的作用域,可以作用于,下面的所有的describe,
同级的互不影响,每个describe都可以拥有独自的钩子函数,执行顺序,先执行外部,再执行内部

新建一个文件counter.js

//模拟用于测试的方法
export default class Counter{
  constructor(){
    this.number = 0
  }
  addOne(){
    this.number+=1
  }
  addTwo(){
    this.number+=2
  }
  minusOne(){
    this.number-=1
  }
  minusTwo(){
    this.number-=2
  }
}

测试该文件 新建 counter.test.js

import Counter from "./counter"

// 相同的归类分组
  // 2种方式,一种分文件,一种是用describe分组
describe('测试counter的相关代码',()=>{ 
  console.log('测试counter的相关代码');

  let counter  = null
beforeAll(()=>{
  // 所有测试用例之前
  console.log('beforeAll');
   
})
beforeEach(()=>{
  // 每个测试用例执行前都调用
  console.log('beforeEach');
  counter = new Counter()
})
afterEach(()=>{
  // 每个测试用例执行之后
  console.log('afterEach');
})
afterAll(()=>{
    // 所有测试用例之后
  console.log('AfterAll');
})


    describe('测试增加的代码',()=>{
      beforeEach(()=>{
        console.log('beforeEach to add');
      })
      afterEach(()=>{
        console.log('afterEach to add');
      })
      afterAll(()=>{
        console.log('afterAll to add');
      })
      beforeAll(()=>{
        console.log('beforeAll to add');
      })
      console.log('测试增加的代码');
      test('测试Counter中的addOne方法',()=>{
        console.log('测试Counter中的addOne方法');
        counter.addOne();
        expect(counter.number).toBe(1)
      })
      test('测试Counter中的addTwo方法',()=>{
        console.log('测试Counter中的addTwo方法');
        counter.addTwo();
        expect(counter.number).toBe(2)
      })
    })

    describe('测试减少的代码',()=>{
      console.log('测试减少的代码');
      test('测试Counter中的minusOne方法',()=>{
        console.log('测试Counter中的minusOne方法');
        counter.minusOne();
        expect(counter.number).toBe(-1)
      })
      
        test('测试Counter中的minusTwo方法',()=>{
          console.log('测试Counter中的minusTwo方法');
          counter.minusTwo();
          expect(counter.number).toBe(-2)
        })
    })
  })
  // 如果只想执行某一个用例,可以用test.only来修饰 only可以同时存在多个
/* 
// 每一个describe都是一个单独的作用域,可以作用于,下面的所有的describe,
  同级的互不影响,每个describe都可以拥有独自的钩子函数,执行顺序,先执行外部,再执行内部


*/
/* 
执行顺序如下:
  console.log counter.test.js:7
    测试counter的相关代码

  console.log counter.test.js:44
    测试增加的代码

  console.log counter.test.js:58
    测试减少的代码

  console.log counter.test.js:12
    beforeAll

  console.log counter.test.js:42
    beforeAll to add

  console.log counter.test.js:18
    beforeEach

  console.log counter.test.js:33
    beforeEach to add

  console.log counter.test.js:46
    测试Counter中的addOne方法

  console.log counter.test.js:36
    afterEach to add

  console.log counter.test.js:23
    afterEach

  console.log counter.test.js:39
    afterAll to add

  console.log counter.test.js:18
    beforeEach

  console.log counter.test.js:60
    测试Counter中的minusOne方法

  console.log counter.test.js:23
    afterEach

  console.log counter.test.js:27
    AfterAll
*/

jest测试异步代码

安装 axios

npm i axios -D

新建文件 fetchData用于模拟异步代码

    import axios from 'axios'
//该接口返回值
//{
 // "success": true
//}
//回调类型的异步函数
export const fetchDataCbk = function(fn){
  
  axios.get('http://www.dell-lee.com/react/api/demo.json')
.then(function(response) {
  fn(response.data)
})
}
//无回调类型的异步函数
export const fetchData = function(){
  return axios.get('http://www.dell-lee.com/react/api/demo.json')

}

测试文件fetchData.test.js

import {fetchData,fetchDataCbk} from "./fetchData"
// 回调类型的异步函数测试
// 只有执行到done执行才结束
test('用done来测试返回结果为{success:true}',(done)=>{
  fetchDataCbk((data)=>{
    expect(data).toEqual({
      success: true
    })
    done()
  })
  
})

// 无回调类型的异步函数测试
//多种实现方法
test('测试返回结果为{success:true}',()=>{
  return fetchData().then((res)=>{
    expect(res.data).toEqual({
      success:true
    })
  })
  
})

test('测试返回结果为{success:true}',async()=>{
 const res = await fetchData()
    expect(res.data).toEqual({
      success:true
    })
  
})
test('测试返回结果为{success:true}',()=>{
  return expect(fetchData()).resolves.toMatchObject({
  data:{
    success: true
  }
  })
  
})
test('测试返回结果为{success:true}',async()=>{
  await expect(fetchData()).resolves.toMatchObject({
  data:{
    success: true
  }
  })
  
})
 //测试返回404
test('测试返回结果为 404',()=>{
  expect.assertions(1);//测试用例必须执行一次
  return fetchData().catch((e)=>{
    expect(e.toString().indexOf('404')!==-1).toBe(true)
  })
  
})
test('测试返回结果为 404',()=>{
  return  expect(fetchData()).rejects.toThrow()
  
})

jest 中的mock

当我们测试请求时,我们并不需要测试接口返回的数据,接口测试是属于后端的测试了,我们只关心,代码是否正常执行

而且如果都去请求,那么测试效率会很慢,这个时候我们就需要用mock来模拟ajax请求,不去请求真实的ajax

新建文件 demo.js

import Axios from "axios"

export const runCallback  = function (callBack){
  callBack()
}
export const createObject  = function (callBack){
  new callBack()
}

export const getData = function(){
  return Axios.get('/api')
}

测试 demo.test.js

import {runCallback,createObject,getData} from './demo'
import Axios from "axios"

  jest.mock('axios') //模拟axios
//回调的异步函数
test('测试runCallback', ()=>{
 
  // 可以自由定义返回值
  const func = jest.fn(()=>{
    return '456'
  })
    // 上面等同提出来下面
  // func.mockImplementation(()=>{
  //   return '456'
  // })

  // 如果只想返回this
  // func.mockReturnThis()

  // func.mockReturnValueOnce('一次返回')
  // func.mockReturnValue('456') 定义返回值
  	runCallback(func)
 // 通过jest.fn 创建的mock函数,可以用toBeCallEd捕获这个函数是否被调用了
  expect(func).toBeCalled()
  // expect(func).toBeCalledWith('value') 每一次调用传入的都是value
  console.log(func.mock);
  /*打印的mock:
   {
      calls: [ [] ], //包含调用时,传递的参数,可以通过判断calls的length来判断调用了几次
      instances: [ undefined ],指func 运行时,this的指向
      invocationCallOrder: [ 1 ],调用的顺序
      results: [ { type: 'return', value: '456' } ] 执行调用的返回值
    }
  */
})
test('测试createObject',()=>{
  const fn = jest.fn()
  createObject(fn)
  console.log(fn.mock);
})

test('测试getData',async()=>{
  // 模拟返回,不会去请求真实的数据
  // mockResolvedValueOnce
  Axios.get.mockResolvedValue({data:'hello'})
  await getData().then((data)=>{
    expect(data).toEqual({data:'hello'})
  })
})

我们除开上面的模拟axios的方式,我们还可以通过模拟异步函数,通过使用模拟的异步函数来达到不请求axios的效果

被测试文件 demo.js

import Axios from "axios"

export const fetchData = function(){
  return Axios.get('/api')
}

export const getNumber = function(){
  return 123
}

在同级创建一个_mocks_文件夹

同样创建一个demo.js来模拟原demo.js 的异步请求

export const fetchData = ()=>{
  return new Promise((resolved,reject)=>{
    resolved("(function(){return '123'})()")
  })
}

测试文件 demo.test.js

jest.mock('./demo.js') //模拟后会去查找__mocks__下的demo.js,而非真实的的demo.js
// 或者直接将config.js中的automock 改成true 自动开启模拟
// unmok  不模拟
import {fetchData} from './demo'
// 当我们开启模拟时,如果想让模拟文件中异步需要模拟,而同步不需要模拟就需要下面这样引入同步方法
const {getNumber} = jest.requireActual('./demo.js') //引入真正的demo



test('测试异步fetchData',async()=>{
  return fetchData().then(data=>{
    console.log(data);
    expect(eval(data)).toEqual('123')
  })
})
test('测试同步getNumber',async()=>{
  expect(getNumber()).toBe(123)
})

快照 snapshot

故名思意,就是类似拍照一样,给你的代码生成一个副本,当代码有所变动,就去与副本中的代码对比,判断是否需要本次的修改,什么时候使用快照呢,当你的代码基本完善,无需修改时,就可以生成一个快照,以后出现代码修改,就可以通过快照的检测,知道那个文件发生了改动

测试文件 demo.js

export const generateConfig= ()=>{
  return {
    sever:"localhost",
    port:8080,
    proxy:8081
  }
}
export const generateAnotherConfig= ()=>{
  return {
    sever:"localhost",
    port:8080,
    proxy:8082
  }
}

export const generateTimeConfig= ()=>{
  return {
    sever:"localhost",
    port:8080,
    proxy:8084,
    time:new Date()
  }
}

测试文件 demo.test.js

import {
  generateConfig,
  generateAnotherConfig,
  generateTimeConfig
} from "./demo";

test("测试generateConfig 函数", () => {
  expect(generateConfig()).toMatchSnapshot();
});
//假设,测试一个配置文件,如果你修改了配置文件,如果使用的是toEqual(),
/*那么每次修改配置,都需要同步修改test,这样很麻烦,使用toMatchSnapshot()
(快照), 会在根目录生成一个snapshots文件保存运行时,的测试的配置项代码,就
好像,拍了一个照片,之后就会和对比新快照,和旧快照是否一致,判断测试用例是否
通过, 
假设这时修改了配置
 1 snapshot failed from 1 test suite. Inspect your code changes or press `u` to update them.
 你只需打开w操作指令,按u表示,更新快照,就可以再次通过,
 类似于提示你,是否要确实这次修改
 
 当出现多个快照时,如果不想所有快照都更新,想一个一个确认更新,这个时候
 w 中会多出一个 i 模式,让你进入到一个一个确认下,这时再按u就表示确认更新快照
 如果感觉是错的,或者不确定,可以按s跳过该快照用例
 */
test("测试generateAnotherConfig 函数", () => {
  expect(generateAnotherConfig()).toMatchSnapshot();
});

// 当配置项中存在new Date() 这种动态变化的参数,就需要配置去忽略它,不然无法通过
test("测试generateTimeConfig 函数", () => {
  expect(generateTimeConfig()).toMatchSnapshot({
    time: expect.any(Date) //任意date类型都行
  });
});

//生成行内快照,下面的object就是运行后生成的快照
// 前置条件需要安装包cnpm i [email protected] -D
// 行内快照 用toMatchInlineSnapshot不会单独生成一个文件,而是把快照直接
// 生成到函数内,
test("测试generateAnotherConfig 函数", () => {
  expect(generateAnotherConfig()).toMatchInlineSnapshot(`
    Object {
      "port": 8080,
      "proxy": 8082,
      "sever": "localhost",
    }
  `);
});

jest中的timer

当我们测试延时器等等时,不可能去等待时间再执行,这样测试效率会极低,所以jest提供了如下方式来快捷的测试timer

被测试文件 timer.js

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

测试文件 timer.test.js

import {timer} from './timer'

beforeEach(()=>{
  
    jest.useFakeTimers(); //每个测试用例执行前,初始一下防止影响
  
})
test('测试定时器',()=>{
  const  fn = jest.fn()
  timer(fn);
  // jest.runAllTimers(); //让定时器立即执行,与上面的use配对使用
  // jest.runOnlyPendingTimers(); //只执行队列中存在的timer
  jest.advanceTimersByTime(3000)//快进定时器
  expect(fn).toHaveBeenCalledTimes(1) //fn只调用一次
  jest.advanceTimersByTime(3000) //快进是在上一个的基础上,存在多个测试用例时,可能会印象下面的,所以我们需要在运行之前重置一下
  expect(fn).toHaveBeenCalledTimes(2) 
})

jest中类的mock

新建模拟类的测试文件

util.js

 export default class Util{
 	 init(){

 	 	}
  	a(){

 	 }
  	b(){

  	}
}

新建文件 demo.js

import Util from './util'
const demoFunction = (a,b)=>{
  const util = new Util();
  util.a(a)
  util.b(b)
}
export default demoFunction

测试文件 demo.test.js

import demoFunction from './demo'
import Util from './util'

jest.mock('./util')
//jest.mock 发现uitl是一个类,会自动把类的构造函数和方法变成jest.fn()
/* 
  const Util= jest.fn()
  until.prototype.a = jest.fn()
  until.prototype.b = jest.fn()
  如果不满意默认处理,可以自定义在文件__mock__下util.js自行模拟
  如果不是很复杂可以直接传递第二个参数,就会执行第二个参数的代码
  jest.mock('./util',()=>{
     const Util= jest.fn()
  Util.prototype..a = jest.fn()
  Util.prototype..b = jest.fn() 
  return Util
  })
*/

// 这里测试的关注点是是否有执行,如果类里a b 方法很复杂就会很耗性能,而他们执行的结果并非所关心的,所以用mock模拟
test('测试 demoFunction',()=>{
  demoFunction()
  expect(Util).toHaveBeenCalled() //是否执行过
  expect(Util.mock.instances[0].a).toHaveBeenCalled()
  expect(Util.mock.instances[0].b).toHaveBeenCalled()
})


这里引入2种概念 单元测试,和集成测试

单元测试
只关注,该单元的代码,对于外部的引入不关心,如果对性能有影响就会用mock
就想上面的测试demoFunction,我只关心有没有执行过a,b方法并不关心执行的结果

简单说就是对单一功能的测试

集成测试
对单元中所有都测试
我不仅要执行,同时也关心执行后对该单元的影响

对多种功能的集合测试

测试驱动开发

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

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

TDD的优势

  1. 长期减少回归bug
  2. 代码质量更好
  3. 测试覆盖率高

行为驱动开发

BDD(Behavior Driven Developmen)

先编写代码,基于用户的行为去编写测试代码

TDD 与BDD 区别

TDD:

  1. 先写测试再写代码
  2. 一般结合单元测试使用,是白盒测试
  3. 测试重点在代码
  4. 安全低(重点在代码对与用户交互给人的安全低)
  5. 测试速度快

BDD:

  1. 先写代码再写测试
  2. 一般结合集成测试使用,是黑盒测试
  3. 测试重点在UI(DOM)
  4. 安全感高(基于用户使用的测试,给人的安全感高)
  5. 测试速度慢

完成上述基础的学习,那么看懂下面的代码就是轻而易举了

接下来我们将在vue中使用jest

vue自动化测试

创建一个vue项目,勾选上jest,生成一个包含jest的vue文件

更多请查看 Vue Test Utils 文档

常用api

mount
创建一个包含被挂载和渲染的 Vue 组件的 Wrapper。
shallowMount  
和 mount 一样,创建一个包含被挂载和渲染的 Vue 组件的 Wrapper,不同的是被存根的子组件
wrapper options
	wrapper.vm
                这是该 Vue 实例。你可以通过 wrapper.vm 访问一个实例所有的方法和属性。这只存在于 Vue 组件包裹器                  或绑定了 Vue 组件包裹器的 HTMLElement 中
       .contains
				判断 Wrapper 是否包含了一个匹配选择器的元素或组件。
		.emitted
				执行一个自定义事件。返回一个包含由 Wrapper vm 触发的自定义事件的对象。
         .trigger
				为 WrapperArray 的每个 Wrapper DOM 节点都触发一个事件。
		.find
				返回匹配选择器的第一个 DOM 节点或 Vue 组件的 Wrapper。
         .findAll
				返回所有,类似jquery
         .props   
				返回 Wrapper vm 的 props 对象。如果提供了 key,则返回这个 key 对应的值

配置 jest.config.js

module.exports = {
  preset: '@vue/cli-plugin-unit-jest',
    moduleFileExtensions: [ 'js', 'jsx', 'json', 'vue' ], //查找文件的后缀
    transform: { //匹配到对应的后缀文件使用对应的转化
      '^.+\\.vue$': 'vue-jest', //解析vue语法,
      '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',//将静态资源转换为字符
      '^.+\\.jsx?$': 'babel-jest' //es6语法转换es5
    },
    transformIgnorePatterns: [ //不需要转换的文件
      '/node_modules/'
    ],
    moduleNameMapper: { //路径映射,
      '^@/(.*)$': '<rootDir>/src/$1'
    },
    snapshotSerializers: [ //对快照vue语法编译
      'jest-serializer-vue'
    ],
    testMatch: [ //测试文件位置,满足下列规则就当成测试文件
      '**/tests/unit/**/*.(spec|test).(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
    ],
    testURL: 'http://localhost/', //测试下的浏览器地址
    watchPlugins: [ //添加交互选择
      'jest-watch-typeahead/filename',
      'jest-watch-typeahead/testname'
    ]
  }
  

如果你是手动按照最开始那种安装jest, 那么 jest-watch-typeahead/filename、 jest-watch-typeahead/testname、jest-serializer-vue、babel-jest、jest-transform-stub、@vue/cli-plugin-unit-jest、vue-jest 这些包就需要你手动安装一下,

packge.json

如果你的项目没有git初始化,请使用–watchAll

接下来我们找到vue的tests 示例 发现和jest不一样,不慌,我们先用另一个写法实现一下,vue的test-utils, 为我们提供了一些用于测试的api可以查看官网,

import { shallowMount } from '@vue/test-utils' 
// shallowMount浅渲染,只渲染当前组件,不渲染包含的子组件,适合单元测试,
// mount 全渲染,适合集成测试
import HelloWorld from '@/components/HelloWorld.vue'

describe('HelloWorld.vue', () => {
  it('renders props.msg when passed', () => {
    const msg = 'new message'
    //一个 Wrapper 是一个包括了一个挂载组件或 vnode,以及测试该组件或 vnode 的方法。
    //wrapper.vm,可以访问到vue的实例
    const wrapper = shallowMount(HelloWorld, {
      propsData: { msg }
    })
    //返回 Wrapper 的文本内容。渲染的文本中包含msg
    expect(wrapper.text()).toMatch(msg)
  })
})

//模拟一个上述实现

import Vue from 'vue'
import HelloWorld from ' @/components/HelloWorld '
describe( 'HelloWorld.vue', () => {
	it(' renders props.msg when passed',() =>{
        const root = document. createElement( 'div');
        root. className ='root' ;
        document.body.appendChild( root) ;
        new Vue({
         render: h => h(HelloWorld,{
             props:{
                 msg: 'dell lee'
             }
         })
            }).$mount( '.root' )
        consloe.log(document.body.innerHTML);
        expect ( document.getElementsByClassName( 'hello' ). length). toBe(1);
      })
})


测试 vuex

对store进行的单元测试

store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state:{
    value:1
  },
  mutations:{
    ADD:(state,data)=>{
      state.value = data
    }
  },
  actions:{
    commitAdd:({commit},data)=>{
      commit("ADD")
    }
  }
})

export default store
import store from '@/store'
it(`当 store 执行add发生变化`, ()=>{
const value = 123 ;
store.commit( ' ADD', value);
expect( store.state.value).toBe( value);
})

当你的测试文件使用了store,需要在挂载的时候,将store传入,否则store会找不到

import store from '../../store'
it(`
	使用store
	`,()=>{
    const wrapper = mount(todoList,{store})
	........
})

vue中的异步测试

我们不需要请求真实的的地址,我们只需要在tests文件同级建立一个新文件夹_mocks_下面新建一个axios.js

axios.js 示例

export defult get(url){ //模拟get方法
    if (url === '/axios.json'){
        return new promise((resolve)=>{
            const data = [{value:1},{value:2}]
            resolve(data)
        })
    }
}

在test文件调用mount时会执行,mounted,在执行请求获取数据时,test会优先查看_mocks_ 下面的axios.js 去替换真实的请求

import store from '../../store'
it(`
	1. 用户进入页面时,请求远程数据
	2. 列表应该展示远程返回的数据
	`,(done)=>{
    const wrapper = mount(todoList,{store})
    //异步测试,需要使用nextTick和done来等待挂载成功后再执行,jest不会自己等待异步
    wrapper.vm.$nextTick(()=>{
    const listItems = wrapper.findAll('[data-test=item]')
    expect(listItems.length).toBe(2)
         done()
    })
})

如果存在定时器

模拟的测试组件

<template>
		<div>
            <ul>
                <li v-for="item in data" data-test="item">{{item}}</li>
   		 </ul>
    </div>
</template>
<script>
export defult {
 data(){
	return {
	data:[]
	}
}
method:{
	getList(){
	setTimeout(()=>{
		axios.get('/axios.json').then((res)=>{
			this.data = res.data
		})
	},4000)
	}
}
}
</script>
    

错误示例

在测试代码存在异步的代码,jest并不会去等待定时器执行完,会直接忽略,如果需要,需要使用done()

这样确实可以,但是会等待4秒并不是我们想要的结果

it(`
	1. 用户进入页面时,等待4秒
	2. 列表应该展示远程返回的数据
	`,(done)=>{
    const wrapper = mount(todoList)
    	setTimeout(()=>{
		  const listItems = wrapper.findAll('[data-test=item]')
    		expect(listItems.length).toBe(2)
            done()
	},4000)
   
})

正确示例

beforEach(()=>{
	jest.useFakeTimets() //用于模拟定时器
})
it(`
	1. 用户进入页面时,等待4秒
	2. 列表应该展示远程返回的数据
	`,(done)=>{
    const wrapper = mount(todoList)
 	jest.tunAllTimers() //如果遇到timers让他立即执行
     wrapper.vm.$nextTick(()=>{
    const listItems = wrapper.findAll('[data-test=item]')
    expect(listItems.length).toBe(2)
         done()
    })
})

下一篇文章
前端自动化测试(二)TDD与BDD 实战todoList

猜你喜欢

转载自blog.csdn.net/marendu/article/details/106523682