# 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
  // 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
  • 到这里测试文件和被测试文件就准备好了
  • 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
  • 执行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

# 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
  • 此时项目的根目录下就会生成jest.config.js文件
  • 里面有很多的配置项,可以进行自定义
module.exports = {
  clearMocks: true,
  coverageDirectory: "coverage",
  // ....
}
1
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
  • 执行命令
 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
└─coverage
  └─lcov-report
      └─index.html
1
2
3
  • 打开这个index.html即可查看测试报告

# 匹配器 matchers

  test('这里是一段关于测试的描述可自定义',()=>{
    let name = 'husky'
    expect(hello(name)).toBe('hello' + name)
  })
1
2
3
4
  • 匹配器指的就是expect()后面的toBe()方法,但是jest有很多类型的匹配器,toBe()是普通匹配器
  • 详细介绍官网地址

# 普通匹配器

  • toBe() 用于值类型的精准匹配
  • toEqual() 用于返回值是对象或者数组的精准匹配
  • not 不相等的匹配
 expect().not.toEqual() // 对象或数组不相等
 expect().not.toBe() // 值类型不相等
1
2

# Truthiness

  • 用来精准匹配 undefinednull,和 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

# 字符串匹配器

  • 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

# 例外

# 自定义匹配器

  • 通过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

# 断言 assertions()

  • 表示如果没有执行assertions指定次数的expect(),就代表该条测试用例不通过
test('is husky', () => {
  expect.assertions(1)
  return fetchDataPromise().catch((err) => {
    expect(err.toString()).toMatch('err')
  })
})
1
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
// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env',{xxxxxxxx配置xxxxxxxx}]
  ]
}
1
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
  • 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

# promise方式的异步方法

  • 准备要测试的代码
import axios from 'axios'

export function fetchDataPromise () {
  return axios.get('https://v1.hitokoto.cn/') // 直接返回promise
}
1
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

# async-await方式处理异步方法

  • 准备要测试的代码
import axios from 'axios'

export function fetchDataPromise () {
  return axios.get('https://v1.hitokoto.cn/') // 直接返回promise
}
1
2
3
4
5
  • 使用async和await来处理异步测试
test('fetchData async', async () => {
  let response = await fetchDataPromise()
  expect(response.data).not.toBeNull()
})
1
2
3
4

# 辅助函数

# beforeEach

  • 每个测试之前的钩子函数,支持异步,和测试用例中解决异步的方式一样,done参数或者返回一个promise
beforeEach(()=>{
  // 每个测试的前置钩子
  console.log('start')
})
1
2
3
4

# afterEach

  • 这个和beforeEach 钩子函数相反,是在每个测试之后调用的钩子函数
afterEach(()=>{
  // 每个测试的前置钩子
  console.log('start')
})
1
2
3
4

# beforeAll

  • 每个测试文件最开始的时候只执行一次
beforeAll(() => {
  console.log('start once')
})
1
2
3

# afterAll

  • 每个测试文件最后的时候只执行一次
beforeAll(() => {
  console.log('end once')
})
1
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
  • 全局钩子函数依然作用于该文件的全部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

# test.only

  • 只执行该测试用例
test.only('only test',()=>{
  expect(true).toBeTruthy()
})
test('basic test',()=>{
  expect(1+1).toBeGreaterThanOrEqual(2)
})
1
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
Last Updated: 1/23/2022, 10:16:22 AM