# jest单元测试小记
- 官网
- 环境安装
  yarn add jest -D
1
# 第一个测试demo
- 准备一个文件夹 test
- 准备一个待测试文件 hello-jest.js
- 准备一个测试文件 hello-jest.test.js,待测试文件名只需要在.js结尾之前加上.test即可,jest就会识别完成测试
  // hello-jest.js
  function hello (name) {
    return 'hello' + name
  }
  module.exports = hello // 这里要用commonjs规范
1
2
3
4
5
6
2
3
4
5
6
  // hello-jest.test.js
  const hello = require('./hello-jest.js') // 这里要用commonjs规范
  
  // 测试代码
  test('这里是一段关于测试的描述可自定义',()=>{
    let name = 'husky'
    expect(hello(name)).toBe('hello' + name)
  })
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 到这里测试文件和被测试文件就准备好了
- 在package.json文件配置命令
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "test": "jest" // 添加jest命令
  }
1
2
3
4
5
6
2
3
4
5
6
- 执行npm run test即可完成测试
PASS  test/hello.test.js
  √ 这里是一段关于测试的描述可自定义 (2 ms)
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.009 s
Ran all test suites.
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# jest的配置
- 本小节记录一下jest的基础配置
- 生成配置文件,要在项目的根目录下执行
  npx jest --init
1
- 执行完上面的命令会有一个命令行的交互
// 第一个选择测试的环境,这里我们选择`jsdom`浏览器端
√ Choose the test environment that will be used for testing » jsdom (browser-like)
// 是否生成测试覆盖率文件
√ Do you want Jest to add coverage reports? ... yes
// 自动清除每次测试之间的模拟调用和实例
√ Automatically clear mock calls and instances between every test? ... yes
1
2
3
4
5
6
2
3
4
5
6
- 此时项目的根目录下就会生成jest.config.js文件
- 里面有很多的配置项,可以进行自定义
module.exports = {
  clearMocks: true,
  coverageDirectory: "coverage",
  // ....
}
1
2
3
4
5
2
3
4
5
# 代码覆盖率
- 在package.json配置命令
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint",
    "test": "jest", // 添加jest命令
    "test:coverage": "jest --coverage" // 添加jest命令
  }
1
2
3
4
5
6
7
2
3
4
5
6
7
- 执行命令
 npm run test:coverage
1
- 由于再jest的配置文件中设置了生成代码覆盖率文件,执行命令之后,分别会在命令行和根目录下产生测试的反馈和文件
---------------|---------|----------|---------|---------|-------------------
File           | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
---------------|---------|----------|---------|---------|-------------------
All files      |     100 |      100 |     100 |     100 |
 hello-jest.js |     100 |      100 |     100 |     100 |
---------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.157 s
Ran all test suites.
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
└─coverage
  └─lcov-report
      └─index.html
1
2
3
2
3
- 打开这个index.html即可查看测试报告
# 匹配器 matchers
  test('这里是一段关于测试的描述可自定义',()=>{
    let name = 'husky'
    expect(hello(name)).toBe('hello' + name)
  })
1
2
3
4
2
3
4
- 匹配器指的就是expect()后面的toBe()方法,但是jest有很多类型的匹配器,toBe()是普通匹配器
- 详细介绍官网地址
# 普通匹配器
- toBe()用于值类型的精准匹配
- toEqual()用于返回值是对象或者数组的精准匹配
- not不相等的匹配
 expect().not.toEqual() // 对象或数组不相等
 expect().not.toBe() // 值类型不相等
1
2
2
# Truthiness
- 用来精准匹配 undefined、null,和false
- toBeNull只匹配- null
- toBeUndefined只匹配- undefined
- toBeDefined与 toBeUndefined 相反
- toBeTruthy匹配任何- if语句为真
- toBeFalsy匹配任何- if语句为假
# 数字匹配器
- toBeGreaterThan() 大于
- toBeGreaterThanOrEqual() 大于等于
- toBeLessThan() 小于
- toBeLessThanOrEqual() 小于等于
- toBeCloseTo() 浮点数相加精度会不准,这个匹配器一般用于浮点数
test('两个浮点数字相加', () => {
  const value = 0.1 + 0.2; 
  // 为什么toBe会报错,就是因为0.1+0.2结果不是0.3而是0.30000000000000004,精度失去精度导致的,所以浮点数用toBeCloseTo进行匹配
  //expect(value).toBe(0.3);           这句会报错,因为浮点数有舍入误差
  expect(value).toBeCloseTo(0.3); // 这句可以运行
});
1
2
3
4
5
6
2
3
4
5
6
# 字符串匹配器
- toMatch() 可以用正则表达式来匹配字符串
  expect('husky are you scared').toMatch(/scared$/)
1
# 数组和迭代的匹配器
- toContain() 是否在数组或者有迭代器的数据结构中
let names = ['husky', 'are', 'you', 'scared']
test('the shopping list has beer on it', () => {
  expect(names).toContain('husky');
  expect(new Set(names)).toContain('husky');
});
1
2
3
4
5
2
3
4
5
# 例外
- toThrow()匹配一些错误参数可以是:- 正则、字符串、Error对象
# 自定义匹配器
- 通过expect.extend来实现
expect.extend({
  IsHusky (received) {
    const pass = received === 'husky'
    if (pass) {
      return {
        message: () =>
          `expected ${received}, is husky`,
        pass: true
      }
    } else {
      return {
        message: () =>
          `expected ${received}, not is husky`,
        pass: false
      }
    }
  }
})
// 测试
test('is husky', () => {
  expect('husky').IsHusky()
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 断言 assertions()
- 表示如果没有执行assertions指定次数的expect(),就代表该条测试用例不通过
test('is husky', () => {
  expect.assertions(1)
  return fetchDataPromise().catch((err) => {
    expect(err.toString()).toMatch('err')
  })
})
1
2
3
4
5
6
2
3
4
5
6
# 配置babel支持es6
- 下载babel核心包,语法转换包
  yarn add @babel/core @babel/preset-env -D
1
- 在项目根目录下配置.babelrc也就可以使用babel.config.js的方式
// .babelrc
{
  "presets": [
    [
      "@babel/preset-env", {
        'targets': {
          'esmodules': true
        }
      }
    ]
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env',{xxxxxxxx配置xxxxxxxx}]
  ]
}
1
2
3
4
5
6
2
3
4
5
6
- 配置完就可以使用 import的方式在测试文件引入其他包了
# 异步代码测试
# 回调函数式的异步方法
- 先定义一个异步请求方法
import axios from 'axios'
// 这里调用一言的接口
export function fetchData (cb) {
  axios.get('https://v1.hitokoto.cn/').then(res => {
    cb(res.data)
  })
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- done来配合异步函数测试
import { fetchData } from './hello-jest'
test('fetchData cb', (done) => {
  fetchData((data) => {
    expect(data).not.toBeNull()
    done() // 为了确保异步测试成功测试,需要在异步回调中执行done回调
  })
})
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# promise方式的异步方法
- 准备要测试的代码
import axios from 'axios'
export function fetchDataPromise () {
  return axios.get('https://v1.hitokoto.cn/') // 直接返回promise
}
1
2
3
4
5
2
3
4
5
- 准备测试文件,如果要测试返回的是promise的异步,那么需要return 异步方法
test('fetchData promise', () => {
  // 一定要加return
  return fetchDataPromise().then((res) => {
    expect(res.data).not.toBeNull()
  })
})
1
2
3
4
5
6
2
3
4
5
6
# async-await方式处理异步方法
- 准备要测试的代码
import axios from 'axios'
export function fetchDataPromise () {
  return axios.get('https://v1.hitokoto.cn/') // 直接返回promise
}
1
2
3
4
5
2
3
4
5
- 使用async和await来处理异步测试
test('fetchData async', async () => {
  let response = await fetchDataPromise()
  expect(response.data).not.toBeNull()
})
1
2
3
4
2
3
4
# 辅助函数
# beforeEach
- 每个测试之前的钩子函数,支持异步,和测试用例中解决异步的方式一样,done参数或者返回一个promise
beforeEach(()=>{
  // 每个测试的前置钩子
  console.log('start')
})
1
2
3
4
2
3
4
# afterEach
- 这个和beforeEach钩子函数相反,是在每个测试之后调用的钩子函数
afterEach(()=>{
  // 每个测试的前置钩子
  console.log('start')
})
1
2
3
4
2
3
4
# beforeAll
- 每个测试文件最开始的时候只执行一次
beforeAll(() => {
  console.log('start once')
})
1
2
3
2
3
# afterAll
- 每个测试文件最后的时候只执行一次
beforeAll(() => {
  console.log('end once')
})
1
2
3
2
3
# 作用域 describe
- 可以将一个测试文件中的不同测试进行分组
beforeEach(()=>{
  console.log('global start each')
})
describe('group 1',()=>{
  beforeEach(()=>{
    console.log('group 1 start each')
  })
  test('test 1',()=>{
    expect(true).toBeTruthy()
  })
  test('test 2',()=>{
    expect(true).toBeTruthy()
  })
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 全局钩子函数依然作用于该文件的全部test,describe分组中的钩子只对该分组有效果
- 全局钩子前置钩子优先于组内前置钩子,后置钩子相反
# describe和test的执行顺序
- 该文件的全部describe先执行,按照定义的顺序先执行,除了test里面的方法,describe中的代码全部先执行
- 然后 test 再按照定义的顺序进行执行
- 官网给了一下例子帮助理解传送门
describe('outer', () => {
  console.log('describe outer-a');
  describe('describe inner 1', () => {
    console.log('describe inner 1');
    test('test 1', () => {
      console.log('test for describe inner 1');
      expect(true).toEqual(true);
    });
  });
  console.log('describe outer-b');
  test('test 1', () => {
    console.log('test for describe outer');
    expect(true).toEqual(true);
  });
  describe('describe inner 2', () => {
    console.log('describe inner 2');
    test('test for describe inner 2', () => {
      console.log('test for describe inner 2');
      expect(false).toEqual(false);
    });
  });
  console.log('describe outer-c');
});
// describe outer-a
// describe inner 1
// describe outer-b
// describe inner 2
// describe outer-c
// test for describe inner 1
// test for describe outer
// test for describe inner 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# test.only
- 只执行该测试用例
test.only('only test',()=>{
  expect(true).toBeTruthy()
})
test('basic test',()=>{
  expect(1+1).toBeGreaterThanOrEqual(2)
})
1
2
3
4
5
6
2
3
4
5
6
# Mock Functions
- 通俗易懂的解释:可以对代码的每一个执行环节进行测试
# 使用 mock 函数
- 使用 jest.fn()定义mock函数
function increment (target, cb) {
  cb(target)
}
test('123', () => {
  let result = 0
  // 定义 mock 函数
  const mockCallback = jest.fn((target)=>{
    result += target
  })
  increment(2, mockCallback)
  increment(2, mockCallback)
  increment(2, mockCallback)
  // // 此 mock 函数被调用了两次
  // expect(mockCallback.mock.calls.length).toBe(2)
  // 第一次调用函数时的第一个参数是 2
  expect(mockCallback.mock.calls[0][0]).toBe(2)
  // 第二次调用函数时的第一个参数是 2
  expect(mockCallback.mock.calls[1][0]).toBe(2)
  // 第三次调用函数时的第一个参数是 2
  expect(mockCallback.mock.calls[2][0]).toBe(2)
  // 递增结束最后 arr 预期值 6
  expect(result === 6).toBeTruthy()
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
