Jest mock 的正确姿势,及如何避免一些常见问题

Jest 是一个流行的 JavaScript 测试框架,可用于测试前端和后端应用程序。其中 mock 是 Jest 的一个重要功能,可以用来模拟函数和对象的行为,从而让测试更加可靠和有复现性。本文将介绍 Jest mock 的正确使用方式,并帮助读者避免一些常见问题。

Jest mock 的基本用法

在 Jest 中,有两种方式可以进行 mock:手动 mock 和自动 mock。手动 mock 是指通过 jest.fn() 创建一个模拟函数,然后在测试中用它替换实际函数。自动 mock 是指在测试运行时,Jest 会自动创建模拟对象,让我们可以模拟模块导出的对象或函数。

手动 mock

手动 mock 的使用方式非常简单,只需要在测试文件中创建一个和实际函数同名的 mock 函数,然后将其替换实际函数即可。

// file.js
function add(a, b) {
  return a + b;
}

module.exports = { add };

// file.test.js
const file = require('./file');

test('add two numbers', () => {
  const addMock = jest.fn((a, b) => a + b);
  file.add = addMock;

  expect(file.add(1, 2)).toBe(3);
  expect(addMock).toHaveBeenCalledWith(1, 2);
});

上面的例子中,我们手动创建了一个名为 addMock 的模拟函数,将其赋值给了 file 模块的 add 属性。在测试中,我们调用 file.add(1, 2),实际上调用的是这个模拟函数。通过 expect(addMock).toHaveBeenCalledWith(1, 2) 可以确认这个 mock 函数被正确调用。

自动 mock

自动 mock 的使用方式更加简便,只需要在测试文件中调用 jest.mock() 方法,并传入要 mock 的模块的路径即可。Jest 会自动创建一个模拟对象,模拟模块导出的对象或函数。

// file.js
function add(a, b) {
  return a + b;
}

module.exports = { add };

// file.test.js
const file = require('./file');

jest.mock('./file');

test('add two numbers', () => {
  file.add.mockReturnValue(3);

  expect(file.add(1, 2)).toBe(3);
  expect(file.add).toHaveBeenCalledWith(1, 2);
});

上面的例子中,我们直接调用了 jest.mock('./file'),Jest 会自动创建模拟对象,模拟模块导出的对象或函数。在测试中,我们使用 mockReturnValue() 方法设置模拟函数的返回值,然后通过 expect(file.add).toHaveBeenCalledWith(1, 2) 进行验证。注意,使用自动 mock 时,我们需要通过模拟对象的属性或方法来访问被 mocked 的对象或函数。

Jest mock 的高级用法

虽然 Jest mock 的基本用法已经足够简单和实用了,但有时我们需要更加复杂的 mock 行为。在这种情况下,我们需要使用 Jest mock 的高级用法。

模拟同名导出

有些模块导出的对象中包含多个同名属性或方法,我们希望能够对其中特定的属性或方法进行 mock。这时我们可以使用 jest.mock() 方法的第二个参数,传入一个函数来返回模拟对象。

// file.js
module.exports = {
  add(a, b) {
    return a + b;
  },
  sub(a, b) {
    return a - b;
  }
};

// file.test.js
const file = require('./file');

jest.mock('./file', () => ({
  __esModule: true,
  ...jest.requireActual('./file'),
  add: jest.fn((a, b) => a + b)
}));

test('mock single named export', () => {
  expect(file.add(1, 2)).toBe(3);
  expect(file.sub(3, 2)).toBe(1);
});

在上面的例子中,我们只 mock 了文件的 add 方法。这里注意,我们需要保留原来的模块导出,否则其他导出的方法都将被 mock 掉。

模拟私有方法

有时我们需要 mock 一个模块中的私有方法,但是无法直接访问它。这时我们可以使用 jest.spyOn() 方法来模拟私有方法的行为。

// file.js
function add(a, b) {
  return a + b;
}

function privateFunc(a, b) {
  return a - b;
}

module.exports = { add };

// file.test.js
const file = require('./file');

test('mock private function', () => {
  const privateFuncSpy = jest.spyOn(file, 'privateFunc');
  privateFuncSpy.mockImplementation((a, b) => a * b);

  expect(file.add(1, 2)).toBe(3);
  expect(privateFuncSpy).toHaveBeenCalledWith(1, 2);
});

在上面的例子中,我们使用 jest.spyOn() 方法模拟了私有函数 privateFunc 的行为,并通过 mockImplementation() 方法实现模拟逻辑。注意,我们需要在测试结束后恢复原始函数,否则可能会影响其他测试用例。

异步 mock

有些函数是异步的,它们可能会使用回调、Promise 或 async/await 进行异步操作。对于这种情况,我们需要使用 Jest mock 的异步用法。在 Jest 中,我们可以使用 jest.fn()、mockReturnValue()、mockResolvedValue()、mockRejectedValue() 和 mockImplementation() 等方法来实现异步 mock。

// file.js
function fetchData(callback) {
  setTimeout(() => {
    callback('data');
  }, 1000);
}

module.exports = { fetchData };

// file.test.js
const file = require('./file');

test('mock async function', async () => {
  const fetchDataSpy = jest.spyOn(file, 'fetchData');
  fetchDataSpy.mockImplementation((callback) => {
    setTimeout(() => {
      callback('mocked data');
    }, 1000);
  });

  const data = await new Promise((resolve) => {
    file.fetchData((data) => {
      resolve(data);
    });
  });

  expect(data).toBe('mocked data');
});

在上面的例子中,我们使用 jest.spyOn() 方法模拟了异步函数 fetchData 的行为,并通过 mockImplementation() 方法实现异步 mock。我们使用 Promise 和 async/await 封装了 fetchData 的回调函数,然后等待 Promise 的结果,并验证返回值是否正确。需要注意的是,我们在测试结束后,需要恢复原始函数。

避免常见问题

在使用 Jest mock 时,有一些常见问题需要避免。下面列出了几个需要注意的事项。

不要误用 jest.requireActual()

jest.requireActual() 是获取真实模块导出的方法,而不是返回 mock 模块的方法。请不要误用它,否则可能会影响测试结果。

不要真正修改原始文件

在使用手动 mock 或 Jest mock 的高级用法时,请不要真正修改原始文件,否则可能会导致测试失败或者其他对象出现问题。

不要在一个测试用例中进行全局修改

在测试用例中,不要全局修改一个对象或变量,否则可能会影响测试结果。

总结

本文介绍了 Jest mock 的基本用法、高级用法以及避免常见问题的方法。Jest mock 是前端测试中必不可少的一个功能,使用正确的方式可以让我们更加轻松地编写可靠和具有复现性的测试用例。希望本文对读者们有所帮助。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65a2570cadd4f0e0ffa769da


纠错反馈