阅读 32

单元测试--jest

前言

通过自动化测试可以了解当前代码能否达到预期结果,如果逻辑有缺陷,可以快速的定位bug;如果是开发类库,可以避免新添加的逻辑影响之前的内容;同时也可以提高代码的可维护性。

jest是facebook推出的,它基于jsdom,用js对象模拟浏览器环境,默认具备断言库、chai、sinon等,零配置实现自动化测试,但是不能测试样式相关内容

js测试

语法describe,表示套件;it(测试用例)

// parser.js
const parser = (str) => {
  const obj = {}
  str.replace(/([^&=]+)=([^&=]+)/g, function() {
    obj[arguments[1]] = arguments[2]
  })
  return obj
}
// parser.test.js
describe('测试parser', () => {
  it('测试parser是否能正常解析', () => {
    expect(parser('a=1&b=2')).toEqual({
      a: "1",
      b: "2"
    })
  })
})
复制代码

测试结果类型

it('测试相等 全等  长得一样  是不是真的  是不是假的', () => {
  expect(1+1).toBe(2)
  expect({name: "mm"}).toEqual({name: "mm"})
  expect(true).toBeTruthy()
  expect(false).toBeFalsy
});
it('测试不相等 大于 小于  大于等于  小于等于', () => {
  expect(1+1).not.toBe(3)
  expect(1+1).toBeLessThan(3)
  expect(1+1).toBeGreaterThan(1)
});
it('是否包含 是否匹配', () => {
  expect('hello').toContain('h')// toContain的参数是字符串
  expect('hello').toMatch(/hello/); // toMatch的参数是正则
});
// it.only只测试当前文件的这个用例
it.only('是否包含 是否匹配', () => {
  expect('hello').toContain('h')
  expect('hello').toMatch(/hello/);
});
复制代码

dom测试

jest在node环境下自己模拟了一套dom的api, 叫做jsDom,用于支持dom测试

// dom.js
const addNode = (node, parent) => {
  parent.appendChild(node)
}
// dom.test.js
it('测试能否正常添加节点', () => {
  document.body.innerHTML = '<div id="wrapper"></div>'
  let button = document.createElement('button');
  let wrapper = document.querySelector('#wrapper');
  addNode(button, wrapper)
  let btn = wrapper.querySelector('button')
  expect(btn).not.toBeNull()
})
复制代码

async测试

done函数

默认情况下,Jest测试一旦执行到末尾就会完成,而不会执行cb中的内容,即不会等待异步执行完毕再测试;要解决这个问题,需要使用单个参数调用done;Jest会等done函数执行后,调用测试用例

// async.js
const getData = (cb) => {
  setInterval(() => { // 或setTimeout(() => {
    cb({name: "mm"})
  }, 2000)
}

// async.test.js
it('测试回调函数  获取数据', (done) => {
  function cb(data) {
     expect(data).toEqual({name: "mm"});
     done()
  }
  getData(cb)
})
复制代码

Timer Mocks

原生定时器功能依赖于实时时间,对于测试环境来说不太理想。Jest可以将定时器换成允许我们自己控制时间的功能,实现代码同步效果。

// async.js
const getData = (cb) => {
  setInterval(() => {
    cb({name: "mm"})
  }, 2000)
}

// async.test.js
it('测试回调函数  获取数据', (done) => {    
  function cb(data) {
     expect(data).toEqual({name: "mm"});
  }
  getData(cb)
  jest.useFakeTimers() // 启用假定时器
  // jest.runAllTimers(); // 运行所有的定时器 对定时器进行mock,适用于setTimeout
  // jest.runOnlyPendingTimers(); // 只运行当前等待队列的一个  适用于setInterval
  jest.advanceTimersByTime(3); // 快进几秒
})
复制代码

promise

如果是promise可以采用done 也可以采用async await

// async.js
const getDataByPromise = (cb) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({name: "mm"})
    }, 2000)
  })
}

// async.test.js
/* it.only('测试promise  获取数据', (done) => {
  getDataByPromise().then(data => {
    expect(data).toEqual({name: "mm"})
    done()
  })
}) */
it.only('测试promise  获取数据', async () => {
  let data = await getDataByPromise()
  expect(data).toEqual({name: "mm"})
})
复制代码

mock function

当测试 forEach|map等函数的实现时,这类函数为数组中每个项调用回调函数。此时,需要mock function,通过擦去真正的函数实现,捕获函数调用。它允许测试时配置返回值,从而更简单地测试代码之间的链接。

// fn.js
const map = (arr, fn) => {
  arr.forEach((item, index) => {
    fn(item, index)
  });
}
// fn.jest.js
import {map} from '../src/4.fn'

it('测试map方法', () => {
  let fn = jest.fn(); //模拟函数,供用户调用,可以记录被执行的过程 ;调用的所有信息,被记录在mock上
  map([1,2,3], fn);
  expect(fn.mock.calls.length).toBe(3) // fn.mock.calls.length模拟函数被调用次数
  expect(fn.mock.calls[0][0]).toBe(1) // 函数第一次调用的第一个参数是1
  expect(fn).toHaveBeenCalledTimes(3) // 函数被调用3次
})
复制代码

coverage覆盖率

coverage,表示测试内容是否覆盖到所有情况。在package.json默认生成的test脚本后添加 jest --coverage

// coverage.js
const flip = (bool) => {
  if(bool){
    return '正'
  } else {
    return '反'
// coverage.test.js
it('测试flip方法', () => {
  expect(flip(true)).toBe('正')
  expect(flip(false)).toBe('反')
})
复制代码

ajax测试

mock 文件

  • 在test文件夹下新建_mocks_/ajax.js
const fetchData = () => {
  return new Promise((resolve) => resolve(['mm', 'xx']))
}

export {
  fetchData
}
复制代码
  • 在test文件中添加jest.mock,相当于import
// ajax.js
import axios from 'axios'
const fetchData = () => {
  return axios.get('/user'); //获取用户数据
}
const sum = (a, b) => {
  return a + b
}

// ajax.test.js
jest.mock('./__mocks__/ajax.js') // mock文件
import {fetchData} from '../src/ajax'
let {sum} = jest.requireActual('../src/ajax') // 如果使用真实的函数进行测试,采用requireActual引入
it('测试能否正常获取用户数据',async () => {
  let r = await fetchData();
  expect(r).toEqual(['mm', 'xx'])
  console.log(r)
})
it('测试求和函数sum',async () => {
  expect(sum(1, 1)).toBe(2)
})
复制代码

mock api

mock的api默认会查找__mocks__中对应的axios文件

// __mock__/axios.js
export default {
  get(url) {
    if(url === '/user') {
      return new Promise((resolve) => resolve(['苗苗', '兮兮']))
    }
  }
}

// ajax.test.js
import {fetchData, sum} from '../src/ajax.js'
it('测试能否正常获取用户数据',async () => {
  let r = await fetchData();
  expect(r).toEqual(['苗苗', '兮兮'])
})
it('测试求和函数sum',async () => {
  expect(sum(1, 1)).toBe(2)
})
复制代码