前端单元测试 jest

549 阅读24分钟

Jest

参考链接:jestjs.io/zh-Hans/doc…

Jest 是一个基于 JavaScript 应用程序的测试框架,它提供了一个简单、高效和强大的测试解决方案。它内置了许多有用的工具和扩展,可以轻松地进行单元测试、集成测试、UI 测试以及其他类型的测试,还包括代码测试覆盖率分析。

以下是 Jest 的一些主要特点:

  • 易于使用:Jest 可以安装并设置在几分钟内即可开始测试代码。相较于其他测试框架,Jest 更简介、更易于设置。

  • 强大的断言库:Jest 内置了几乎所有你所需要的断言方法,无需额外的库或方法。

  • 快速和并行运行的测试:Jest 运行单元测试时非常快。可选选择性地,tests 也可以并行执行,以加快预处理速度。

  • 智能、角色感知的“watch mode”: Jest 监视文件更改,并且可以在开发人员编写代码时通过聪明复原时间来强化回归测试。

  • 可自定义和可扩展:必要的情况下,Jest 可以进行极简设置,同时还可用于包括复杂预处理、测试扫描器、持续集成等更广泛的使用场景。

在 Jest 的生态系统中,还有其他许多有用的工具和扩展。例如,@testing-library/jest-dom 可以与 Jest 相结合,提供简洁可读的 DOM 测试断言。Cypress 是一个增强型前端端到端测试框架,它可以与 Jest 集成,提供更好的浏览器测试能力。使用 Jest 及其配套的工具,可以大大提高你的 JavaScript 应用程序的代码质量和稳定性。

如何使用

在 React + TypeScript 项目中使用 Jest 进行测试也是非常简单的。下面是使用 Jest 进行 TypeScript + React 项目中的组件测试的基本步骤:

create-react-app 新版中默认集成了 Jest、 jest-dom 等

image.png

1.安装 Jest

npm install --save-dev jest @types/jest

2.在 package.json 中添加 Jest 配置:

{
  "scripts": {
    "test": "jest"
  },
  "jest": {
    "preset": "ts-jest",
    "testEnvironment": "jsdom",
    "moduleNameMapper": {
      "^@/(.*)$": "<rootDir>/src/$1"
    }
  }
}

其中,preset 属性告诉 Jest 使用 ts-jest 进行测试, testEnvironment 属性指定测试环境为 jsdommoduleNameMapper 属性用于配置别名,以便 Jest 插件在使用时可以正确解析文件路径。

3.编写测试用例:

import { render, screen } from '@testing-library/react';
import App from './App';

test('renders App component', () => {
  render(<App />);
  const linkElement = screen.getByText(/hi there!/i);
  expect(linkElement).toBeInTheDocument();
});

在测试用例中,我们使用了 @testing-library/react 进行断言。使用 render 方法渲染 App 组件,并检查是否正确地包含了文本“hi there!”

4.运行测试:

npm test

至此,你就可以在 React + TypeScript 项目中使用 Jest 进行测试了。同时,你可以安装其他 Jest 插件和扩展,比如 ts-jest,来更加灵活地对你的代码进行测试。如果你在使用 TypeScript 的时候被卡住了,可以先检查你的 tsconfig.json 文件是否匹配 jest 的配置,例如使用源映射和排除编译测试文件。

Jest 常用参数

以下是一些 Jest 常用参数的说明:

  1. –config:指定 Jest 配置文件路径,在该文件中可以配置 Jest 的使用方法、测试覆盖率、测试文件匹配规则等等。例如:
 jest --config jest.config.js
  1. –testMatch:指定考虑执行哪些测试用例的模式。例如:
jest --testMatch '*spec.ts'

上述例子中,只执行以.spec.ts 结尾的测试文件。

  1. –coverage:开启测试覆盖率功能,输出每个文件和语句的覆盖情况,并生成报告。例如:
jest --coverage
  1. –watch:监听测试文件的修改,并在文件修改后重新执行测试。例如:
jest --watch
  1. –updateSnapshot:在更新测试快照时使用。例如:
jest --updateSnapshot
  1. –detectOpenHandles:检测异步请求(比如 AJAX 请求)是否在测试完成后被正确关闭。例如:
jest --detectOpenHandles
  1. –forceExit:在测试完成后强制退出,不等待其它异步任务完成。例如:
jest --forceExit
  1. –notify:在测试完成后推送操作系统的通知。例如:
jest --notify

这些是 Jest 常用的几个选项和参数,如果需要更多的选项和参数,在命令行中输入 jest --help 可以查看所有可用的选项和参数。

Jest 常用的 API

Jest API 是 Jest 提供的一组测试相关的方法和对象,可以让开发者更灵活地定制测试。

import {expect, jest, test} from '@jest/globals';

下面列举和说明 Jest API 中一些比较常用的方法和对象:

1. describe : 用于将测试用例分组,便于阅读和管理

describe('group',() => {
    test("should do something",() => {
        //test code
    })
})

2. test : 用于编写测试用例,包括输入数据,执行代码,期望输出等

test('a simple example',() => {
    const a = 1;
    const b = 2;
    expect(a + b).toBe(3)
})

it(name, fn): 描述一个测试用例,与test功能相同,也可以用于描述一个测试用例。

function add(a:number,b:number){
    return a + b;
}
describe('jest 常用的PAI使用',()=>{
    it('add function returns correct value test',()=>{
        expect(add(2,5)).toBe(7)
    })
    it('add function returns correct value it',()=>{
        expect(add(2,2)).toBe(4)
    })
})

3.expect :用于断言结果是否符合预期或是否抛出了期望的异常

expect 是 Jest 提供的用于进行测试断言的 API,它可以使得你的测试代码更加直观、易读。

在使用 expect API 进行测试断言时,需要遵循的一般流程是先通过一些代码获取到待测试的值,然后使用 expect API 对该值进行某些测试断言,最后通过 Jest 运行该测试。以下是一个使用 expect API 测试一个函数输出值的示例:

// 待测试的函数
function add(a, b) {
  return a + b;
}

// 测试代码
test('add function returns correct value', () => {
  expect(add(1, 2)).toBe(3); // 判断 add 函数返回值是否为 3
  expect(add(1, 2)).not.toBe(4); // 判断 add 函数返回值是否不为 4
});

在上面的代码中,expect(add(1,2)).toBe(3)断言了add函数的返回值为3,如果不是测试失败;

expect(add(1,2)).not.toBe(4)断言了add函数的返回值不能为4,如果值为4测试失败,使用以上两个测试断言,可以对函数返回值的正确性进行全面的测试。

4. beforeEach/afterEach : 在每个测试用例之前/之后执行

beforeEach

beforeEach(fn)Jest提供的一个函数,用于在每个测试用例前执行一个函数。

这个函数可以用来复用一些公共的代码,比如说初始化某些变量、创建测试数据、执行一些准备工作等等。

beforeEach中使用的函数会在每个测试用例运行之前,都会被调用一次,确保每个测试用例基于相同的环境进行测试。如果有多个测试用例需要共享某些变量或者执行某些相同的逻辑,就可以使用这个函数来统一管理。

例如,假设有一个测试套件,包含多个测试用例,需要在每个测试用例前都初始化一个 fruits 的空数组,可以在该测试套件外侧定义该数组,并将它传递给beforeEach函数:

let fruits = [];

beforeEach(() => {
  fruits = ['apple', 'banana', 'orange'];
});

describe('fruits array', () => {
  it('should contain apple', () => {
    expect(fruits).toContain('apple');
  });

  it('should not contain grape', () => {
    expect(fruits).not.toContain('grape');
  });
});

在以上代码中,我们先定义了一个空数组 fruits,然后在每个测试用例前都执行 beforeEach函数,将fruits数组初始化为包含'apple''banana''orange'的数组。这样,每个测试用例就都基于同一个 fruits 数组运行。

总之,beforeEach函数可以帮助我们复用测试用例中的公共代码,确保测试用例执行的环境一致性,并可以提高代码重用性和可维护性。

afterEach

afterEach是一个Jest测试框架的钩子函数,它在每个测试用例运行结束后执行。它通常用于重置或清理测试环境,以确保每个测试用例独立运行,不会相互影响或受到上一个测试用例的影响。在afterEach函数中,你可以执行一些特定的操作,比如关闭测试用例中打开的资源、清理测试用例中创建的数据等。示例代码:

// 声明一个空的字符串数组 fruits
let fruits:string[] = [];
// 在每个测试用例执行之前,将 fruits 数组初始化为三个元素
beforeEach(()=>{
    fruits = ['apple','banana','orange'];
    console.log(fruits)
})
// 描述 fruit 数组的测试用例
describe('fruits array',()=>{
    // 测试 fruit 数组是否包含 apple
    it('should contain apple',()=>{
        expect(fruits).toContain('apple')
    });
    // 测试 fruit 数组是否不包含 grape
    it('should not contain grape',() => {
        expect(fruits).not.toContain('grape')
    })
})
// 清空 fruits 数组并输出
afterEach(()=>{
    fruits = []
    console.log(fruits)
})

5. beforeAll / afterAll : 在所有测试用例之前/之后执行

beforeAllafterAll 是 Jest 测试框架中的生命周期方法,可以用来在所有测试用例执行之前或之后执行一次特定的操作。

beforeAll 方法在所有测试用例执行前调用一次,可以用来进行一些全局配置或者数据准备操作,比如连接数据库、初始化全局变量等。例如:

beforeAll(() => {
  console.log('开始执行所有测试用例前的准备工作')
  // 连接数据库,初始化配置等操作
})

afterAll 方法在所有测试用例执行完毕后调用一次,可以用来进行一些全局清理操作,比如清空数据库、释放资源等。例如:

afterAll(() => {
  console.log('所有测试用例执行完毕,进行清理工作')
  // 清空数据库,释放资源等操作
})

需要注意的是,beforeAllafterAll 的作用域是整个测试文件,所以在同一个文件中只会被调用一次。如果有多个测试文件需要执行相同的全局操作,需要在每个文件中都添加相应的 beforeAllafterAll 方法。

6. expect.extend : 用于自定义 matcher

expect.extend 是 Jest 框架中的一个 API,用于自定义 Jest 断言库中的匹配器(matcher)。可以使用 expect.extend 方法将自定义的匹配器添加到 Jest 的 expect 方法中,从而可以在测试用例中使用自定义的匹配器进行断言。

自定义匹配器的定义有一定的复杂性,需要确保匹配器的正确性和实用性。自定义匹配器的语法如下:

expect.extend({
  customMatcher(received, argument) {
    // received 是通过 toMatchCustom(argument) 传入的实际值
    // argument 是自定义匹配器需要比对的预期值
    const pass = received === argument
    if (pass) {
      return {
        message: () => `Expected ${received} to not equal ${argument}`,
        pass: true,
      }
    } else {
      return {
        message: () => `Expected ${received} to equal ${argument}`,
        pass: false,
      }
    }
  },
})

以上是一个简单的自定义匹配器的例子。customMatcher 是一个自定义函数,用于对比传入的实际值和预期值,并返回匹配结果。其中 pass 是一个布尔值,用于表示实际值和预期值是否匹配。message 函数用于返回描述匹配结果的字符串。

使用自定义匹配器的语法如下:

test('测试自定义匹配器', () => {
  expect('foo').toMatchCustom('foo') // 匹配成功
  expect('foo').not.toMatchCustom('bar') // 匹配成功
})

在测试用例中,我们可以通过 toMatchCustom 方法调用自定义匹配器,并传入预期值作为参数。如果匹配成功,则测试用例通过,反之则失败。

7.jest.fn() 函数模拟工具

jest.fn()\ 是 Jest 框架中的一个函数模拟工具,可以用于创建一个被监视的函数或创建一个虚拟函数来模拟其他函数的行为。

使用 jest.fn() 可以模拟一个函数的返回值,以及它是否被调用过、被调用了多少次、被传递了什么参数等行为,可以使用 mockReturnValuemockResolvedValuemockRejectedValue等方法来模拟不同的返回值类型,还可以使用mockImplementation方法来自定义模拟函数的实现。

例如,一个简单的使用 jest.fn() 来模拟一个异步函数的例子:

async function fetchData() {
  // 这里是一个异步操作,比如使用 fetch 方法获取数据
}

test('测试 fetchData 函数', async () => {
  const mockFetchData = jest.fn().mockResolvedValue({ name: 'Tom' })
  
  // 执行代码,同步调用 mockFetchData 方法
  const result = await mockFetchData()
  
  // 断言函数的行为以及返回值
  expect(mockFetchData).toHaveBeenCalled()
  expect(mockFetchData).toHaveBeenCalledTimes(1)
  expect(mockFetchData).toHaveReturnedWith({ name: 'Tom' })
  
  // 断言函数返回的数据结构
  expect(result).toEqual({ name: 'Tom' })
})

在这个例子中,我们使用 jest.fn() 创建了一个名为 mockFetchData 的虚拟函数,并使用 mockResolvedValue 方法模拟了异步操作的返回值。然后在测试用例中,调用 mockFetchData 方法并对其行为和返回值进行了断言。

除了以上的使用场景,jest.fn() 还可以用来替代某些外部依赖的函数,从而可以使得测试更加简单灵活,还可以进行进一步封装和定制,以满足测试的需求。

8. spyOn : 用于模拟函数,并记录调用和返回结果

const obj = {
  getPosition() {
    return {
      x: 1,
      y: 2,
    };
  },
};

test('getPosition returns x and y', () => {
   const spy = jest.spyOn(obj, 'getPosition');
   expect(obj.getPosition()).toEqual({ x: 1, y: 2 });
   expect(spy).toHaveBeenCalled();
});

9. mockImplementation:用于模拟函数的实现:

function add(a, b) {
  return a + b;
}

jest.mock('./math', () => ({
  add: jest.fn(() => 42),
}));

test('should return 42', () => {
  expect(add(1, 2)).toEqual(42);
});

上述内容涵盖了 Jest API 的一些常用方法和对象。根据需要可以查看 Jest 的官方文档,里面有更多的 API 详细说明和示例代码:jestjs.io/docs/gettin…

异步测试代码

在JavaScript中执行异步代码是很常见的。 当你有以异步方式运行的代码时,Jest 需要知道当前它测试的代码是否已完成,然后它可以转移到另一个测试。 Jest有若干方法处理这种情况。

Promise

为你的测试返回一个Promise,则Jest会等待Promise的resove状态 如果 Promise 的状态变为 rejected, 测试将会失败。

例如,有一个名为fetchData的Promise, 假设它会返回内容为'peanut butter'的字符串 我们可以使用下面的测试代码︰

// 定义一个返回Promise对象的函数fetchPromise,返回一个等待1秒后返回一个数据对象{name:'张三',age:18}的Promise对象
function fetchPromise() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ name: '张三', age: 18 });
    }, 1000);
  });
}

// 使用fetchPromise()获取请求数据,等待数据返回,并使用toEqual()方法匹配数据是否符合预期
it('the data is data object', () => {
  return fetchPromise().then(data => {
    expect(data).toEqual({ name: '张三', age: 18 });
  });
});

// 定义一个使用回调函数异步获取数据的函数fetchData,等待1秒后执行回调函数并传入数据'peanut butter'
function fetchData(callback: { (data: any): void; (arg0: string): void; }) {
  setTimeout(() => {
    callback('peanut butter');
  }, 1000);
}

// 使用fetchData()获取请求数据,等待数据返回,并使用expect()方法匹配数据是否符合预期。使用done()方法确保异步测试完成后正确结束。
test('the data is peanut butter', (done) => {
  return fetchData((data: any) => {
    expect(data).toEqual('peanut butter')
    done()
  });
});

Async/Await

在 Jest 中, 我们可以使用 async/await 来处理异步测试,它也可以用于测试异步函数或 Promise。使用 async/await 可以使代码更加清晰,易于读写和调试。下面是更多的细节:

  1. async声明:对于包含awiat的函数,我们需要在函数声明前加上async关键字,让函数明确返回一个Promise对象。
  2. await操作符:将Promise对象暂停当前上下文执行,等待Promise对象的状态变为已解决或已拒绝。如果状态被解决,则awiat可以返回Promise对象resolved的值,如果eject,则抛出错误、

使用async/awiat可以简化异步测试,下面是一个使用async/awiat测试Promise的例子

// 定义一个返回Promise对象的函数fatchAsyncAwait,1秒后返回一个解析值为 'peanut butter' 的Promise对象
function fatchAsyncAwait() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('peanut butter');
    }, 1000);
  });
}

// 使用async关键字声明一个异步测试函数,该函数使用await操作符等待fatchAsyncAwait方法返回的Promise对象,并使用expect方法验证返回的数据是否符合预期
test('async/await - should retrun data peanut butter', async () => {
  expect.assertions(1); // 确保expect方法被调用至少一次
  const data = await fatchAsyncAwait(); // 调用异步方法,并等待结果返回
  expect(data).toBe('peanut butter'); // 使用expect方法来验证数据是否符合预期
});

在此测试中,我们在测试函数上使用 async 声明。fetchData 函数返回一个解析值为 peanut butter 的 Promise 对象。在测试中,我们通过 await 暂停测试的上下文,并等待 fetchData 返回数据。而不是使用 .then() 风格的 Promise 成功或失败处理程序,我们使用 expect() 判断 data 变量是否符合预期的值,然后断言它是否为 ‘peanut butter’。

使用 async/await 还可以使多个异步操作更简洁并更具可读性。下面是一个使用 async/await 测试异步函数的例子:

// 定义一个 async 函数
async function fetchData(): Promise<string> {
    // 调用第一个 async 函数,并等待它返回结果
    const result1 = await firstAsyncFunction();
    // 调用第二个 async 函数,并以第一个函数的结果作为参数,等待它返回结果
    const result2 = await secondAsyncFunction(result1);
    // 调用第三个 async 函数,并以第二个函数的结果作为参数,等待它返回结果
    return thirdAsyncFunction(result2);
  }
  
  // 测试 async 函数
  // eslint-disable-next-line jest/valid-title
  test('test async/await with asynchronous function', async () => {
    // 断言执行结果为一个值
    expect.assertions(1);
    // 调用 fetchData 函数,并等待它返回结果
    const result = await fetchData();
    console.log(result)
    // 断言执行结果的值等于 'test result'
    expect(result).toEqual('first result + second result = test result');
  });
  
  // 定义第一个异步函数
  function firstAsyncFunction(): Promise<string> {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve('first result');
      }, 1000);
    });
  }
  
  // 定义第二个异步函数
  function secondAsyncFunction(result: string): Promise<string>  {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(`${result} + second result`);
      }, 500);
    });
  }
  
  // 定义第三个异步函数
  function thirdAsyncFunction(result: string): Promise<string>  {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(`${result} = test result`);
      }, 300);
    });
  }

在此测试中,我们定义了三个异步函数,fetchData 函数会先等待 firstAsyncFunction 异步执行并返回值,然后再等待 secondAsyncFunction 输入前一个函数的值,并返回一个组合结果。最后,我们使用 thirdAsyncFunction 来完成最终的返回值组装。

在测试的 async 函数中使用 await 来暂停上下文执行,等待 fetchData 完成,并断言返回值是否正确。这个测试代码更具可读性和简洁性, 更能够清晰地表达这个测试的目的。

也可以将 async and await.resolves or .rejects一起使用。

// 定义一个返回 Promise 对象的异步函数 fetchDataResolve() ,2秒后返回一个值为 'return resolve' 的 Promise 对象
async function fetchDataResolve(): Promise<string> {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            resolve('return resolve')
        }, 2000);
    });
}

// 定义一个返回 Promise 对象的异步函数 fetchDataReject() ,3秒后返回一个拒绝的值为 'error' 的 Promise 对象
async function fetchDataReject(): Promise<string> {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('error')
        }, 3000);
    });
}

// 使用 async/await 声明一个测试函数,使用 expect().resolves 来判断异步测试函数返回的 Promise 对象是否成功,并比较返回的数据是否符合预期值
test('async/await - should return data return resolve', async () => {
    await expect(fetchDataResolve()).resolves.toBe('return resolve');
});

// 使用 async/await 声明一个测试函数,使用 expect().rejects 来判断异步测试函数返回的 Promise 是否对象被拒绝,并比较返回的错误信息是否符合预期值
test('async/await - should return data return error', async () => {
    await expect(fetchDataReject()).rejects.toBe('error');
});

以上代码包含了详细注释来解释每行代码的作用和功能,主要展示了在 Jest 中使用 async/await 处理异步编程的方式;同时,注释也解释了 resolves 与 rejects 方法呈现的 Promise 状态。具体而言,resolves 是当 Promise 被成功 resolve 时, rejects 是在 Promise 被拒绝(reject)时执行某项操作。在测试中, 使用 toBe() 方法以检查返回的数据是否符合预期。

Expect 断言

在写测试的时候,我们经常需要检查值是否满足指定的条件。 expect 让你可以使用不同的“匹配器”去验证不同类型的东西。

import {expect, jest, test} from '@jest/globals';

export(value)

判断一个值是否满足条件,会使用到expect函数。一般很少会单独调用expect函数,因为通常会结合expect和匹配函数来断言某个值。

// 判断函数返回值是否正确
test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});

// 判断对象属性是否正确
test('obj property equals', () => {
  const obj = { name: 'Tom', age: 18 };
  expect(obj).toEqual({ name: 'Tom', age: 18 });
});

// 判断数组是否包含某个元素
test('array includes value', () => {
  const arr = [1, 2, 3];
  expect(arr).toContain(2);
});

// 判断异步请求是否被正确处理
test('async request resolves', async () => {
  await expect(api.fetchData()).resolves.toEqual({ status: 200, data: { name: 'Tom', age: 18 } });
});

上述示例展示了如何使用 expect API 进行测试断言。其中的 toBe 判断返回值是否相等,toEqual 判断对象是否相等,toContain 判断数组是否包含某个元素,resolves 判断 Promise 是否被正确处理。使用以上测试断言可以对代码的正确性进行全面测试,确保代码的质量,以及保障应用的稳定性。

以下是expect API列表:

  • .not:反转matcher
  • .resolves:用于异步测试,判断Promises是否resolve
  • .rejects:用于异步测试,判断Promises是否reject
  • .toBe:精确相等
  • .toEqual:递归地比较对象
  • .toBeCloseTo:数字相等(Size defaults to 2)
  • .toBeDefined:定义过
  • .toBeInstanceOf:实例化过的类
  • .toBeNull:是否为空
  • .toBeTruthy:是否为真值
  • .toBeUndefined:是否未定义
  • .toBeGreaterThan:比某个值大
  • .toBeGreaterThanOrEqual:比某个值大或相等
  • .toBeLessThan:比某个值小
  • .toBeLessThanOrEqual:比某个值小或相等
  • .toBeNaN:数字是否为 NaN
  • .toContain:字符串包含
  • .toContainEqual:数组包含
  • .toEqual:内容递归相等
  • .toHaveLength:长度相等
  • .toHaveProperty:是否具有对象的指定属性
  • .toMatch:字符串满足正则表达式
  • .toMatchObject:包括在对象中
  • .toThrow:抛出一个异常
  • .toThrowError:抛出特定类型的错误

.not 反转 matcher

expect.not 与 expect API 同属于 Jest 测试框架提供的用于编写测试断言的 API,不同的是 expect.not 可以让我们测试一些与期望值不同的测试用例。

例如,假设我们有一个函数 isEven,用于判断一个整数是否为偶数,我们可以编写以下测试用例来测试该函数:


function isEven(num: number): boolean {
  return num % 2 === 0;
}

test('2 is even', () => {
  expect(isEven(2)).toBe(true);
});

test('3 is not even', () => {
  expect(isEven(3)).toBe(false);
});

在上述测试例中,我们使用 toBe 断言来测试输入值是否为偶数,其中包括一个测试用例 3 is not even。现在假设我们想要测试一个非偶数,那么我们可以使用 expect.not 来编写一个相似的测试用例:

function isEven(num: number): boolean {
  return num % 2 === 0;
}

describe('sum module', () => {
  test('4 is not even', () => {
    expect(isEven(3)).not.toBe(true);
  });
});

述测试用例使用了 expect.not 断言,它将会测试 isEven(3) 的返回值是否为 true 的相反值,即是否为 false。这使得我们能够使用 expect.not 来测试一些与期望值相反的情况,从而更全面地测试代码的鲁棒性和正确性。

需要注意的是,一定要小心使用 expect.not 来编写测试断言,确保你的测试用例是正确的并且对代码的正确性进行了有效的测试,而不仅仅是测试代码的某个行为是否与预期不同。

.resolves 用于异步测试,判断Promises是否resolve

使用resolves打开已实现promise的值,以便可以链接任何其他匹配器。如果promise被拒绝,则断言失败。如果promise被拒绝,则断言失败。

例如,这段代码测试promise解析,结果值是'lemon'

test('resolves to lemon', () => {
  // make sure to add a return statement
  return expect(Promise.resolve('lemon')).resolves.toBe('lemon');
});

由于您仍然在测试promise,因此测试仍然是异步的。注意,由于您仍然在测试promise,因此测试仍然是异步的。因此,您需要通过返回未包装的断言来告诉Jest等待。

或者,您可以将async/await.resolves结合使用:

test('resolves to lemon', async () => {
  await expect(Promise.resolve('lemon')).resolves.toBe('lemon');
  await expect(Promise.resolve('lemon')).resolves.not.toBe('octopus');
});

.rejects 用于异步测试,判断Promises是否reject

使用.rejects来解开一个被拒绝的promise的原因,这样任何其他匹配器都可以被链接起来。如果promise被实现,则断言失败。如果promise被满足,断言失败。

例如,这段代码测试promise是否以原因'octopus'拒绝:

test('rejects to octopus', () => {
  // make sure to add a return statement
  return expect(Promise.reject(new Error('octopus'))).rejects.toThrow(
    'octopus',
  );
});

由于您仍然在测试promise,因此测试仍然是异步的。注意,由于您仍然在测试promise,因此测试仍然是异步的。因此,您需要通过返回未包装的断言来告诉Jest等待。

或者,您可以将async/await.rejects组合使用。

test('rejects to octopus', async () => {
  await expect(Promise.reject(new Error('octopus'))).rejects.toThrow('octopus');
});

.toBe(value) 精确相等

测试某个值是否等于另一个值。如果被测试的值与期望的值相等,测试通过,否则测试失败并输出错误消息。例如:

test('测试数字相加', () => {
  const a = 2
  const b = 3
  expect(a + b).toBe(5)
})

在这个例子中,expect(a + b)表示预期a + b的值等于5,如果值不等于5,测试将失败。

.toHaveBeenCalled()

用于测试某个 mock 函数是否被调用过。如果被测试的 mock 函数确实被调用了,测试通过,否则测试失败并输出错误消息。例如:

test('测试函数是否被调用', () => {
  const mockFunction = jest.fn()
  // 做一些事情,调用 mock 函数
  mockFunction()
  // 测试 mock 函数是否被调用
  expect(mockFunction).toHaveBeenCalled()
})

在这个例子中,创建了一个 mock 函数 mockFunction,调用之后即可使用 toHaveBeenCalled 匹配器来测试是否被调用过。如果被测试的函数确实被调用了,测试将通过。

toHaveBeenCalledTimes

toHaveBeenCalledTimes 是 Jest 的匹配器(matcher)之一,用于测试 mock 函数被调用的次数是否符合预期。如果被测试的 mock 函数不止被调用了 n 次,测试将失败并输出错误消息。例如:

    test('测试函数调用次数', () => {
      const mockFunction = jest.fn()
      // 做一些事情,调用 mock 函数
      mockFunction()
      mockFunction()
      // 测试 mock 函数是否被调用了两次
      expect(mockFunction).toHaveBeenCalledTimes(2)
    })

在这个例子中,创建了一个 mock 函数 mockFunction,先调用了两次,然后使用 toHaveBeenCalledTimes(2) 匹配器来测试是否被调用了两次。如果测试通过,则表示被测试的 mock 函数确实被调用了两次。

@testing-library

@testing-library是一个开源的JavaScript测试工具库,提供了一套用于编写可维护、可读性高的测试代码的功能。主要针对React和Vue等流行框架进行测试。

该库包括以下一些子库及其特点:

  • @testing-library/dom:测试用例可以与真实DOM进行交互,并且不需要依赖第三方库。
  • @testing-library/jest-dom:为Jest提供自定义匹配器。
  • @testing-library/react:专注于React的测试工具库,提供了测试React组件和Hook的工具方法。
  • @testing-library/vue:专注于Vue的测试工具库,提供了测试Vue组件和内置指令的函数。
  • @testing-library/cypress:该库可以与Cypress测试框架集成。

使用@testing-library不仅可以编写更好的测试代码,还能够从更高的维度发现组件行为中的问题,提高应用程序的质量和稳定性。

React 测试库 @testing-library/react

使用 create-react-app 创建的项目对 React Testing Library 开箱即用,低版本需要通过 npm 添加

npm install --save-dev @testing-library/react

@testing-library/react中有很多函数用于测试,其中一些常用的函数包括:

  • render:渲染React组件并返回一个对象,其中包含了多种DOM测试工具函数。
  • cleanup:清理DOM上的所有React组件,以便下一个测试用例使用。
  • getByLabelText:通过for属性或aria-label属性查找与标签文本匹配的表单元素。
  • getByRole:通过角色查找元素,例如:buttoninputheading等HTML元素。
  • getByText:通过文本内容对元素进行查找,查找的是文本节点,不是元素本身。
  • queryBy:查询元素是否存在,查询不到则返回null。
  • findBy:异步查询元素是否存在,如果元素不存将会一直等待(默认等待2秒)。
  • fireEvent:模拟用户与组件之间的交互,例如:点击、输入、改变等事件。

上述函数并不是全部,还有包括一些查询和断言函数可用于测试React组件的状态和行为。为了更好的使用@testing-library/react,建议通过查阅文档了解所有可用的函数及其用法。