jest使用教程

269 阅读5分钟

一、安装和配置

demo地址

github.com/mySkey/jest…

  • 1、安装
npm install --save-dev jest
  • 2、使用babel
npm install --save-dev babel-jest @babel/core @babel/preset-env

通过在项目的根目录中创建 babel.config.js 文件,将 Babel 配置为针对当前版本的 Node:

module.exports = {  
    presets: [['@babel/preset-env', {targets: {node: 'current'}}]],  
};
  • 3、使用Typescript
npm install --save-dev @babel/preset-typescript

然后将 @babel/preset-typescript 添加到 babel.config.js 中的预设列表中。

module.exports = {
  presets: [
    ["@babel/preset-env", { targets: { node: "current" } }],
    "@babel/preset-typescript",
  ],
};

二、搭建demo最基本结构,并写一个最基本的求和函数测试

初始化 package.json

npm init -y

生成 package.json 文件后,安装npm包,生成下面文件,新建2个快捷script,如下面文件中所示:

{
  "name": "jest-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "jest --watchAll src",
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.23.5",
    "@babel/preset-env": "^7.23.5",
    "@babel/preset-typescript": "^7.23.3",
    "babel-jest": "^29.7.0",
    "jest": "^29.7.0"
  }
}

新建 src/basic 目录,并新建 src/basic/index.tssrc/basic/basic.test.js 文件;

src
  └───basic
      └───basic.test.js
      └───index.ts

src/basic/index.ts 导出求和函数

// 求和函数
export function sum(a: number, b: number): number {
  return a + b;
}

src/basic/basic.test.js 测试求和函数,入参的期望

import { sum } from ".";

test("求和函数", () => {
  expect(sum(1, 2)).toBe(3);
});
  • 运行项目,执行 npm run dev 会实时监听 src 下测试用例变化
npm run dev

三、matchers(匹配器)

/**
 * matchers: toBe
 * 匹配值,相当于===
 * expect(1 + 1).toBe(2);
 */
test("matchers: toBe", () => {
  expect(1 + 1).toBe(2);
});

/**
 * matchers: toEqual
 * 匹配值,只匹配内容不匹配引用,可以用于引用类型的匹配
 * expect({}).toBe({});
 */
test("matchers: toBe", () => {
  const info = {
    name: "mySkey",
  };
  const mess = {
    name: "mySkey",
  };
  expect(info).toEqual(mess);
});

// 有时需要区分 undefined、null 和 false,但有时又不想区别对待
test("matchers: undefined、null 和 false", () => {
  const n = null;
  expect(n).toBeNull();
  expect(n).toBeDefined();
  expect(n).not.toBeUndefined();
});

// 比较数字的方法都有匹配器
test("matchers: numbers", () => {
  const value = 2 + 2;
  // 大于
  expect(value).toBeGreaterThan(3);
  // 大于等于
  expect(value).toBeGreaterThanOrEqual(3.5);
  // 小于
  expect(value).toBeLessThan(5);
  // 小于等于
  expect(value).toBeLessThanOrEqual(4.5);

  // toBe and toEqual are equivalent for numbers
  expect(value).toBe(4);
  expect(value).toEqual(4);
});

// 比较浮点数
test("matchers: floats", () => {
  const value = 0.1 + 0.2;
  expect(value).toBeCloseTo(0.3);
});

// 比较字符串
test("matchers: strings", () => {
  const name = "mySkey";
  expect(name).toMatch(/Skey/);
});

// 数组
test("matchers: arrays", () => {
  const arr = [1, 2, 3];
  expect(arr).toContain(2);
  // toMatchObject
  expect(arr).toMatchObject([1, 2, 3]);
});

// 对象
test("matchers: objects", () => {
  const obj = {
    name: "mySkey",
  };
  expect(obj).toHaveProperty("name", "mySkey");
});

// 布尔
test("matchers: booleans", () => {
  expect(true).toBeTruthy();
  expect(false).toBeFalsy();
});

// Error
test("matchers: errors", () => {
  expect(() => {
    throw new Error("error");
  }).toThrow();
});

四、测试异步

  • 第一种:使用 done 方法
test("async: done", (done) => {
  delay("msg").then((result) => {
    expect(result).toBe("msg");
    done();
  });
});
  • 第二种:如果返回的是一个 Promise 对象,可以直接使用 return 写法
test("async: return", () => {
  return delay("msg").then((result) => {
    expect(result).toBe("msg");
  });
});
  • 第三种:如果返回的是一个 Promise 对象,可以直接使用 return + resolves/rejects 写法
test("async: return + resolves/rejects", () => {
  return expect(delay("msg")).resolves.toBe("msg");
});
  • 第四种:使用 async 和 await
test("async: delay", async () => {
  const result = await delay("msg");
  expect(result).toBe("msg");
});

五、钩子函数

  • beforeEach 全局测试之前执行
beforeEach(() => {
  // Add your setup code here
});
  • afterEach 全局测试之后执行
afterEach(() => {
  // Add your teardown code here
});
  • describe内的钩子仅适用于该 describe 块内的测试
describe("matching cities to foods", () => {
  // Applies only to tests in this describe block
  beforeEach(() => {
    // Add your setup code here
  });

  test("", () => {});
});

六、mock

  • jest.fn

模拟函数允许你通过擦除函数的实际实现、捕获对函数的调用(以及这些调用中传递的参数)、捕获使用 new 实例化时的构造函数的实例以及允许测试时配置来测试代码之间的链接 返回值。

test("mock: jest.fn", () => {
  const mockCallback = jest.fn((x) => 42 + x);

  arrayMap([0, 1, 2, 3], mockCallback);

  // mock fn 调用次数
  expect(mockCallback.mock.calls).toHaveLength(4);

  // 第一次调用的第一个参数应该是 0
  expect(mockCallback.mock.calls[0][0]).toBe(0);

  // 第三次调用的第一个参数应该是 2
  expect(mockCallback.mock.calls[2][0]).toBe(2);

  // 第三次调用的返回值应该是 44
  expect(mockCallback.mock.results[2].value).toBe(44);
});
  • module

模拟模块,如 axios

jest.mock("axios");
test("mock: module", async () => {
  const res = await getUserList();
  const userList = res?.data;
  expect(userList?.length).toBe(undefined);
});
  • __mocks__

新建目录 __mocks__

// 返回一个promise
export function getData() {
  return new Promise(resolve => {
    resolve({
      data: '测试数据'
    })
  })
}
jest.mock('./index')
// 从__mocks__中引入
import { getData } from './index';

// 设置callbackFun方法从源文件index中查找
const { callbackFun } = jest.requireActual('./index')

test('测试 getData,使用__mock__', async () => {
  const data = await getData()
  expect(data).toEqual({ data: '测试数据' })
})

test('测试 callbackFun,使用mockReturnValueOnce设置返回值', () => {
  let fun = jest.fn()
  fun.mockReturnValueOnce('123') 
  
  expect(callbackFun(fun)).toBe('123')

})

七、定时器

jest中的怎样去测试定时器

在 index.ts 中写入一些待测试方法

export const timer1 = (callback) => {
  setTimeout(() => {
      callback()
  }, 1000)
}

export const timer2 = (callback) => {
  setTimeout(() => {
      callback()
      setTimeout(() => {
          callback()
      }, 1000)
  }, 1000)
}
  • setTimeout: done

如果定时器时间很长的话,那测试用例运行的时间也会很长,因此不推荐这种写法

test('setTimeout: done', (done) => {
  timer1(() => {
    expect(1).toBe(1)
    done()
  })
})
  • setTimeout: useFakeTimers + runAllTimers
/** 
-   使用`jest.useFakeTimers()`声明使用虚拟的时间
-   再使用 `jest.runAllTimers()` 将全部定时器立即运行结束
-   如果内部有多个定时器,只想运行一个定时器可以使用`jest.runOnlyPendingTimers()`
*/
describe('setTimeout: useFakeTimers + runAllTimers', () => {
  
  test('setTimeout: timer1', () => {
    jest.useFakeTimers()
    const fn = jest.fn()
    timer1(fn)
    jest.runAllTimers()
    expect(fn).toHaveBeenCalledTimes(1)
  })

  test('setTimeout: 2', () => {
    jest.useFakeTimers()
    const fn = jest.fn()
    timer2(fn)
    jest.runAllTimers()
    expect(fn).toHaveBeenCalledTimes(2)
  })
})
  • setTimeout: useFakeTimers + advanceTimersByTime
/** 
-   使用`jest.advanceTimersByTime()`可以立即设置推后多少毫秒
*/
describe('setTimeout: useFakeTimers + advanceTimersByTime', () => {

  test('setTimeout: timer1', () => {
    jest.useFakeTimers()
    const fn = jest.fn()
    timer1(fn)
    jest.advanceTimersByTime(1000)
    expect(fn).toHaveBeenCalledTimes(1)
  })

  test('setTimeout: timer2', () => {
    jest.useFakeTimers()
    const fn = jest.fn()
    timer2(fn)
    jest.advanceTimersByTime(1000)
    expect(fn).toHaveBeenCalledTimes(1)
    jest.advanceTimersByTime(1000)
    expect(fn).toHaveBeenCalledTimes(2)
  })
})

八、snapshot快照

将内容生成快照文件,保存在__snapshots__文件,当修改内容后,再次生成的快照不能和快照文件不匹配的情况,就会报错

如果就是要修改快照文件,可删除__snapshots__目录下的快照文件,然后重新生成就好

export const testData = () => {
  return {
    name: 'mySkey',
    age: 28,
    time: ''
  }
}
import { testData } from ".";

test("snapshot", () => {
  // 生成快照文件
  expect(testData()).toMatchSnapshot();
});
  • 不能生成到__snapshots__目录下,直接生成在测试代码里
import { testData } from ".";

test("snapshot: inline", () => {
  expect(testData()).toMatchInlineSnapshot(`
    {
      "age": 28,
      "name": "mySkey",
      "time": "",
    }
  `);
});