Jest单元测试:玩转代码的小捉迷藏!

32 阅读5分钟
test('callback test', (done) => {
  fetchData((data) => {
    expect(data).toEqual(expectedData);
    done();
  });
});

Mocking

在测试中,我们经常会需要模拟函数或模块的行为。Jest提供了内置的模拟函数工具来实现此功能。

以下是一个使用Jest的模拟函数的示例:

function fetchData(callback) {
  // 假设这是一个异步操作
  setTimeout(() => {
    callback('Hello Jest!');
  }, 1000);
}

test('mocking test', () => {
  const mockCallback = jest.fn();
  fetchData(mockCallback);

  expect(mockCallback).toHaveBeenCalledTimes(1);
  expect(mockCallback).toHaveBeenCalledWith('Hello Jest!');
});

在上面的示例中,我们使用jest.fn()创建一个模拟函数mockCallback,然后将其作为回调函数传递给fetchData函数。通过使用jest.fn(),我们可以跟踪这个模拟函数的调用次数和传入的参数,以进行断言。

代码覆盖率

代码覆盖率是衡量测试覆盖范围的指标。Jest提供了内置的代码覆盖率工具,可以帮助你分析测试覆盖情况。

通过在package.json中添加以下配置,可以生成代码覆盖率报告:

"scripts": {
  "test": "jest --coverage"
}

运行npm test命令后,Jest将会生成一个代码覆盖率报告,展示你的测试覆盖情况。

高级配置

Jest提供了丰富的配置选项,用于满足项目的需求。你可以在项目根目录下创建一个jest.config.js文件来配置Jest。

以下是一个简单的配置示例:

// jest.config.js
module.exports = {
  verbose: true,
  testEnvironment: 'node',
  coverageDirectory: 'coverage',
  collectCoverageFrom: ['src/**/*.js'],
};

  • 1

异常测试

在测试代码中,我们需要确保正确地处理异常情况。Jest提供了多个断言方法来处理异常。

function divide(a, b) {
  if (b === 0) {
    throw new Error('Divide by zero');
  }
  return a / b;
}

test('divide should throw an error when dividing by zero', () => {
  expect(() => {
    divide(10, 0);
  }).toThrow('Divide by zero');
});

在上面的示例中,我们使用toThrow断言方法来验证代码是否会抛出预期的错误。

测试异步代码的错误

当测试异步代码时,必须确保能够捕捉到异步操作中的错误。Jest提供了几种方式来处理这种情况。

async function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('Fetch error');
    }, 1000);
  });
}

test('fetchData should throw an error', async () => {
  expect.assertions(1);
  try {
    await fetchData();
  } catch (error) {
    expect(error).toEqual('Fetch error');
  }
});

在上面的示例中,我们使用expect.assertions来确保至少有一个断言被执行。然后使用try-catch块捕捉到fetchData函数中的错误,并使用断言验证错误的值。

测试对象的方法调用次数

有时候我们需要确保对象的方法被正确调用了指定的次数。Jest提供了用于检查模拟函数调用次数的方法。

class Counter {
  constructor() {
    this.count = 0;
  }

  increment() {
    this.count++;
  }
}

test('Counter increment method should be called twice', () => {
  const counter = new Counter();
  counter.increment();
  counter.increment();
  const incrementMock = jest.spyOn(counter, 'increment');

  expect(incrementMock).toHaveBeenCalledTimes(2);
});

在上面的示例中,我们使用jest.spyOn来监视Counter类的increment方法,然后通过调用两次increment方法,并使用toHaveBeenCalledTimes断言方法验证方法被正确调用了两次。

测试组件交互

在测试React组件时,我们通常需要模拟用户交互和验证组件的行为。Jest提供了一些方法和工具来帮助测试React组件。

import { render, fireEvent } from '@testing-library/react';
import Button from './Button';

test('Button click should trigger callback', () => {
  const handleClick = jest.fn();
  const { getByText } = render(<Button onClick={handleClick}>Click me</Button>);
  const button = getByText('Click me');
  fireEvent.click(button);

  expect(handleClick).toHaveBeenCalled();
});

在上面的示例中,我们使用@testing-library/react库中的render函数和fireEvent工具来渲染和测试组件。然后使用jest.fn创建一个模拟函数来监视回调函数的调用,并通过模拟点击按钮来触发回调,并使用toHaveBeenCalled断言方法来验证回调函数是否被调用。

快照测试

快照测试是一种用于捕捉组件或数据结构的初始渲染和状态的测试方法。Jest的toMatchSnapshot方法可以用来创建和比较快照。

import renderer from 'react-test-renderer';
import MyComponent from './MyComponent';

test('MyComponent snapshot', () => {
  const tree = renderer.create(<MyComponent />).toJSON();
  expect(tree).toMatchSnapshot();
});

  • 1

在第一次运行测试时,Jest将创建一个快照文件,然后在后续运行时将快照与新的渲染结果进行比较。这有助于检测组件是否发生了意外更改。

参数化测试

有时我们需要测试一组相似的输入,可以使用参数化测试来减少代码重复。

const testData = [  { input: 2, expected: 4 },  { input: 3, expected: 9 },  { input: 4, expected: 16 },];

test.each(testData)('square(%i) should return %i', (input, expected) => {
  expect(square(input)).toBe(expected);
});

  • 1

在上面的示例中,我们使用test.each方法来定义一个参数化测试,它会根据不同的输入值多次运行相同的测试代码,从而避免了重复的测试用例。

自定义匹配器

Jest允许你创建自定义匹配器,以便更容易地编写特定于应用程序的断言。

expect.extend({
  toBeValidEmail(received) {
    const regex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$/i;
    const pass = regex.test(received);
    if (pass) {
      return {
        message: () => `expected ${received} not to be a valid email`,
        pass: true,
      };
    } else {
      return {
        message: () => `expected ${received} to be a valid email`,
        pass: false,
      };
    }
  },
});

test('email validation', () => {
  expect('test@example.com').toBeValidEmail();
  expect('invalid-email').not.toBeValidEmail();
});

  • 1

在上面的示例中,我们创建了一个自定义匹配器toBeValidEmail,用于验证字符串是否为有效的电子邮件地址。这使得我们可以使用自定义的断言来验证应用程序的特定行为。

使用beforeEach和afterEach

beforeEach和afterEach函数允许你在每个测试用例之前和之后执行特定的操作,例如设置和清理测试环境。

let counter = 0;

beforeEach(() => {
  counter++;
});

afterEach(() => {
  counter = 0;
});

test('increment counter', () => {
  expect(counter).toBe(1);
});

test('reset counter', () => {
  expect(counter).toBe(1);
  counter = 5;
  expect(counter).toBe(5);
});

  • 1

在上面的示例中,beforeEach用于在每个测试用例之前递增counter,而afterEach用于在每个测试用例之后将counter重置为0,以确保测试的隔离性。

测试组件的生命周期方法

如果你使用 React 或其他支持生命周期方法的库,你可以使用 jest 和 enzyme(或其他库)来测试组件的生命周期方法。

import React from 'react';
import { mount } from 'enzyme';
import MyComponent from './MyComponent';

test('componentDidMount is called', () => {
  const componentDidMountSpy = jest.spyOn(MyComponent.prototype, 'componentDidMount');
  const wrapper = mount(<MyComponent />);