有两种方法可以模拟函数:要么在测试代码中创建一个 mock 函数,要么编写一个来覆盖模块依赖。

假设我们要测试函数 forEach 的内部实现,这个函数为传入的数组中的每个元素调用一次回调函数。

为了测试此函数,我们可以使用一个 mock 函数,然后检查 mock 函数的状态来确保回调函数如期调用。

  1. const mockCallback = jest.fn(x => 42 + x);
  2. forEach([0, 1], mockCallback);
  3. // 此 mock 函数被调用了两次
  4. expect(mockCallback.mock.calls.length).toBe(2);
  5. // 第一次调用函数时的第一个参数是 0
  6. expect(mockCallback.mock.calls[0][0]).toBe(0);
  7. // 第二次调用函数时的第一个参数是 1
  8. expect(mockCallback.mock.calls[1][0]).toBe(1);
  9. // 第一次函数调用的返回值是 42
  10. expect(mockCallback.mock.results[0].value).toBe(42);

.mock 属性

所有的 mock 函数都有这个特殊的 .mock属性,它保存了关于此函数如何被调用、调用时的返回值的信息。 .mock 属性还追踪每次调用时 this的值,所以我们同样可以也检视(inspect) this

  1. const myMock = jest.fn();
  2. const a = new myMock();
  3. const b = {};
  4. const bound = myMock.bind(b);
  5. bound();
  6. console.log(myMock.mock.instances);
  7. // > [ <a>, <b> ]

这些 mock 成员变量在测试中非常有用,用于说明这些 function 是如何被调用、实例化或返回的:

  1. // The function was called exactly once
  2. expect(someMockFunction.mock.calls.length).toBe(1);
  3. // The first arg of the first call to the function was 'first arg'
  4. expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
  5. // The second arg of the first call to the function was 'second arg'
  6. expect(someMockFunction.mock.calls[0][1]).toBe('second arg');
  7. // The return value of the first call to the function was 'return value'
  8. expect(someMockFunction.mock.results[0].value).toBe('return value');
  9. // This function was instantiated exactly twice
  10. expect(someMockFunction.mock.instances.length).toBe(2);
  11. // had a `name` property whose value was set to 'test'
  12. expect(someMockFunction.mock.instances[0].name).toEqual('test');

Mock 函数也可以用于在测试期间将测试值注入代码︰

  1. const myMock = jest.fn();
  2. // > undefined
  3. myMock
  4. .mockReturnValueOnce(10)
  5. .mockReturnValueOnce('x')
  6. .mockReturnValue(true);
  7. console.log(myMock(), myMock(), myMock(), myMock());
  8. // > 10, 'x', true, true

大多数现实世界例子中,实际是在依赖的组件上配一个模拟函数并配置它,但手法是相同的。 在这些情况下,尽量避免在非真正想要进行测试的任何函数内实现逻辑。

模拟模块

假定有个从 API 获取用户的类。 该类用 axios 调用 API 然后返回 data,其中包含所有用户的属性:

  1. // users.js
  2. import axios from 'axios';
  3. class Users {
  4. static all() {
  5. return axios.get('/users.json').then(resp => resp.data);
  6. }
  7. }
  8. export default Users;

现在,为测试该方法而不实际调用 API (使测试缓慢与脆弱),我们可以用 jest.mock(…) 函数自动模拟 axios 模块。

一旦模拟模块,我们可为 .get 提供一个 mockResolvedValue ,它会返回假数据用于测试。 实际上,我们想让 axios.get('/users.json') 有个假的 response。

  1. // users.test.js
  2. import axios from 'axios';
  3. import Users from './users';
  4. jest.mock('axios');
  5. test('should fetch users', () => {
  6. const users = [{name: 'Bob'}];
  7. const resp = {data: users};
  8. axios.get.mockResolvedValue(resp);
  9. // or you could use the following depending on your use case:
  10. // axios.get.mockImplementation(() => Promise.resolve(resp))
  11. return Users.all().then(data => expect(data).toEqual(users));
  12. });

Still, there are cases where it's useful to go beyond the ability to specify return values and full-on replace the implementation of a mock function. This can be done with jest.fn or the mockImplementationOnce method on mock functions.

  1. const myMockFn = jest.fn(cb => cb(null, true));
  2. myMockFn((err, val) => console.log(val));
  3. // > true

The mockImplementation method is useful when you need to define the default implementation of a mock function that is created from another module:

  1. // foo.js
  2. };
  3. // test.js
  4. jest.mock('../foo'); // this happens automatically with automocking
  5. const foo = require('../foo');
  6. // foo is a mock function
  7. foo.mockImplementation(() => 42);
  8. foo();
  9. // > 42

When the mocked function runs out of implementations defined with mockImplementationOnce, it will execute the default implementation set with jest.fn (if it is defined):

  1. const myMockFn = jest
  2. .fn(() => 'default')
  3. .mockImplementationOnce(() => 'first call')
  4. .mockImplementationOnce(() => 'second call');
  5. console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
  6. // > 'first call', 'second call', 'default', 'default'

For cases where we have methods that are typically chained (and thus always need to return this), we have a sugary API to simplify this in the form of a .mockReturnThis() function that also sits on all mocks:

  1. const myObj = {
  2. myMethod: jest.fn().mockReturnThis(),
  3. };
  4. // is the same as
  5. const otherObj = {
  6. myMethod: jest.fn(function() {
  7. return this;
  8. }),
  9. };

Mock 名称

You can optionally provide a name for your mock functions, which will be displayed instead of "jest.fn()" in test error output. Use this if you want to be able to quickly identify the mock function reporting an error in your test output.

  1. const myMockFn = jest
  2. .fn()
  3. .mockReturnValue('default')
  4. .mockImplementation(scalar => 42 + scalar)
  5. .mockName('add42');

Finally, in order to make it less demanding to assert how mock functions have been called, we've added some custom matcher functions for you:

  1. // 这个 mock 函数至少被调用一次
  2. expect(mockFunc).toBeCalled();
  3. // 这个 mock 函数至少被调用一次,而且传入了特定参数
  4. expect(mockFunc).toBeCalledWith(arg1, arg2);
  5. // 这个 mock 函数的最后一次调用传入了特定参数
  6. expect(mockFunc).lastCalledWith(arg1, arg2);
  7. // 所有的 mock 的调用和名称都被写入了快照

These matchers are sugar for common forms of inspecting the .mock property. You can always do this manually yourself if that's more to your taste or if you need to do something more specific:

这些只是一部分,有关匹配器的完整列表,请查阅 。