Jest测试学习笔记(1)

140 阅读5分钟

测试的思路

  1. 引入要测试的函数
  2. 给函数一个输入
  3. 定义预期输出
  4. 检查函数是否返回了预期的输出结果
// demo.js
function sum (a, b) {
  return a + b;
}
function substract (x, y) {
  return x - y;
}
module.exports = {
  sum,
  substract
}
  • 我们常见Jest的断言语法大概是这样子
expect(sum(1, 2)).toBe(3)
  • 大概实现思路
// demo.test.js
function expect(result) {
  return {
    toBe(expected) {
      if (result !== expected) {
        throw new Error(`期望结果是${expected},但是收到了${result}`);
      } else {
        console.log('Pass');
      }
    }
  }
}

expect(sum(1, 2)).toBe(3)
  • 输出结果

image.png

  • 为了更好的捕获报错信息,我们需要输出日志,一般会这么写 测试用例
test('sum(1, 2)的结果应该是3', () => {
  expect(sum(1, 2)).toBe(3)
})
// demo.test.js
test('sum(1, 2)的结果应该是3', () => {
  // 一个测试用例中可以有多个断言函数
  expect(sum(1, 2)).toBe(3)
  expect(sum(2, 2)).toBe(4)
})

test('substract(1, 2)的结果应该是1', () => {
  expect(substract(3, 2)).toBe(1)
})

function test (message, callback) {
  try {
    callback()
  } catch (err) {
    console.error(`${message}: ${err.message}`);
  }
}
  • 输出结果

image.png

体验Jest

  • 安装
npm install --save-dev jest @types/jest

// package.json
"scripts": {
    "test": "jest"
}

// 在项目内自定义jest配置
npx jest --init

image.png

  • Jest CLI选项
    • --watchAll 监视文件的更改并在任何更改时重新运行所有测试
    • --watch 想要重新运行仅基于已更改文件的测试, 依赖git
    • 单独运行某个文件 npm run test -- xxx.test.js

在jest中使用es6模块

npm install --save-dev babel-jest @babel/core @babel/preset-env

// babel.config.js
module.exports = {
  presets: [['@babel/preset-env', {
    targets: {node: 'current'}
  }]]
}

  • 此时可以使用import, export语法
// const { sum, substract } = require('./demo');
import { sum, substract } from './demo';

Jest全局APT

  • 在测试文件中,Jest将所有的这些方法和对象放在全局环境中,你可以无需导入即可使用,当然你也可以显示导入
import { describe, expect, test } from '@jest/global'

Test函数

test函数别名: it(name, fn, timeout)

test.only(name, fn, timeout) 当前测试文件下只执行当前test用例

Expect匹配器

  • 在编写测试时,通常需要检查是否满足某些条件。Expect使你可以访问许多‘匹配器’,以使你可以验证不同的内容
test.only('测试expect', () => {
 expect(2 + 2).toBe(4)
 expect({name: 'zhangwh'}).toEqual({name: 'zhangwh'})
 expect('Christoph').toMatch(/stop/) // 匹配正则
 expect(4).toBeGreaterThan(3) // 期望4>3
 expect(4).toBeLessThan(5) // 期望4<5
})

describe函数

  • describe创建一个将几个相关测试组合在一起的块。
describe('a 组件', () => {
  test('a 功能', () => {
    console.log('a组件 xx 功能')
  })
  test('a 功能', () => {
    console.log('a组件 oo 功能')
  })
})

assertions函数

expect.assertions(1); // 至少得有一次断言

生命周期钩子

  • afterAll(fn, timeout)
  • afterEach(fn, timeout)
  • beforerAll(fn, timeout)
  • beforeEach(fn, timeout) 每个测试用例执行之前执行一次

Jest对象

  • jest 对象会自动注入到每个测试文件中, jest 对象上的方法帮助你创建模拟并让你控制 Jest 的整体行为。 也可以通过 import { jest } from '@jest/globals' 手动进行引入

Jest测试异步代码

  • setTimeout
function getData(callback) {
  setTimeout(() => {
    callback({foo: 'bar'});
  }, 2000);
}
test('async getData', (done) => {
  getData((data) => {
    done(); // 执行异步
    expect(data).toEqual({foo: 'bar'});
  })
})

image.png

  • Promises
function getData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({foo: 'bar'});
    }, 2000);
  })
}
// 写法1
test('async getData', (done) => {
  getData().then((data) => {
    done(); // 执行异步
    expect(data).toEqual({foo: 'bar'});
  })
})
// 写法2
test('async getData', () => {
  return getData().then((data) => {
    expect(data).toEqual({foo: 'bar'});
  })
})
  • .resloves/.rejects
// 写法3
test('async getData', () => {
  return expect(getData()).resolves.toEqual({foo: 'bar'});
})
  • Async/Await
// 写法4
test('async getData', async () => {
  const data = await getData();
  expect(data).toEqual({foo: 'bar'});
})

Mock定时器

  • 可以快速所有定时器结束
  • jest.runAllTimers(); jest.useFakeTimers();
// mock 定时器取代setTimeout
jest.useFakeTimers();
test('async getData', () => {
  expect.assertions(1); // 至少得有一次断言
  // 异步任务 下面这种写法不写return 正常来说expect不会执行
  // 这里使用了useFakeTimers, runAllTimers
  getData().then((data) => {
    expect(data).toEqual({foo: 'bar'});
  })
  // 快速所有定时器结束
  jest.runAllTimers();
})
  • 计时器可以恢复他们默认的行为通过jest.useRealTimers()
  • 涉及到递归调用,运行等待计时器,会计当前进行的定时器结束,不等待其他的 jest.runOnlyPendingTimers()
// 如果涉及到递归调用
function getData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({foo: 'bar'});
      // 递归
      getData();
    }, 2000);
  })
}
jest.useFakeTimers();
test('async getData', () => {
  expect.assertions(1); // 至少得有一次断言
  getData().then((data) => {
    expect(data).toEqual({foo: 'bar'});
  })
  // 运行等待计时器
  jest.runOnlyPendingTimers();
})
  • jest.advancertimersbytime () 快进多少ms

Mock函数

  • 假设我们要测试函数 forEach 的内部实现,这个函数为传入的数组中的每个元素调用一次回调函数
// forEach.js
function forEach(arr, callback) {
  for (let index = 0; index < arr.length; index++) {
    callback(arr[index], index);
  }
}
  • 为了测试此函数,我们可以使用一个 mock 函数,然后检查 mock 函数的状态来确保回调函数如期调用。
test('mock fn', () => {
  const arr = [1, 2, 3];
  const mockFn = jest.fn();
  forEach(arr, mockFn);
  expect(mockFn.mock.calls.length).toBe(3); // passed
  // calls: [ [1, 0], [2, 1], [3, 2] ]
  expect(mockFn.mock.calls[0][1]).toBe(0); // passed
})

test('mock fn', () => {
  const arr = [1, 2, 3];
  const mockFn = jest.fn((val, index) => {
    return val + 1;
  });
  forEach(arr, mockFn);
  console.log(mockFn.mock);
  expect(mockFn.mock.calls.length).toBe(3);
  // calls: [ [1, 0], [2, 1], [3, 2] ]
  expect(mockFn.mock.calls[0][1]).toBe(0);
  expect(mockFn.mock.results[0].value).toBe(2);
})

image.png

Mock 的返回值

  • Mock 函数也可以用于在测试期间将测试值注入代码
const myMock = jest.fn();
console.log(myMock());
// > undefined

myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true

Mock 的模拟模块

  • 模拟axios响应
  • 为测试该方法而不实际调用 API (使测试缓慢与脆弱),我们可以用 jest.mock(...) 函数自动模拟 axios 模块
  • 一旦模拟模块,我们可为 .get 提供一个 mockResolvedValue ,它会返回假数据用于测试。 实际上,我们想说的是我们想让axios.get('/users.json') 有个伪造的响应结果
// user.js
import axios from 'axios';
export const getAllusers = () => {
  return axios.get('/users.json').then((response) => response.data)
}
// user.test.js
import axios from 'axios';
import { getAllusers } from './user';
// 模拟axios 实际调用的是axios.get.mockResolvedValue(response);
jest.mock('axios');

test('should fetch users', async () => {
  const users = [{ name: 'Agony' }];
  const response = { data: users };

  axios.get.mockResolvedValue(response);
  const data = await getAllusers();
  expect(data).toEqual(users);
})

image.png

Mock模拟函数实现(Mock Implementations)

// foo.js
// 正常返回111
export default function() {
  return 111
}
// foo.test.js
jest.mock('./foo')
import foo from './foo';
foo.mockImplementation(() => 123); // 模拟函数内部逻辑
test('mock implementation', () => {
  expect(foo()).toBe(123); // Passed
})

image.png

Jest中的钩子函数

  • 如果你有一些要为多次测试重复设置的工作,你可以使用 beforeEach 和 afterEach
  • beforeEach 运行每个测试用例之前执行
  • afterEach 每个测试用例执行结束之后执行
  • beforeAll 在所有测试用例之前仅执行 1 次
  • aftereAll 在所有测试用例执行结束之后仅执行 1 次

钩子函数的作用域

  • 顶层的 before* 和 after* hook 函数会应用于文件中的每一条测试
  • 在 describe 块中声明的 hook 函数,只会作用于 describe 中的测试
  • 注意顶级的beforeEach 会比 describe 中的beforeEach 执行的更早。
  • 注意顶级的afterEach 会比 describe 中的afterEach 执行的更晚。