Mock Functions
Mock 功能可以轻松地通过擦除函数的实际实现来测试代码之间的链接,捕获对函数的调用(以及在这些调用中传递的参数),在使用new
实例化时捕获构造函数的实例,并允许测试时间配置返回值。
有两种方式可以模拟函数:可以在测试代码中编写一个模拟函数,或者是编写一个 manual mock
来覆盖模块依赖。
使用模拟函数 #
假设我们要测试函数 forEach
的内部实现,这个函数为提供的数组中的每个元素调用一个回调函数。
function forEach(items, callback) { for (let index = 0; index < items.length; index++) { callback(items[index]); } }
为了测试此函数,我们可以使用一个模拟函数,然后检查模拟函数的状态来确保回调函数如期调用。
const mockCallback = jest.fn(); forEach([0, 1], mockCallback); // 此模拟函数被调用了两次 expect(mockCallback.mock.calls.length).toBe(2); // 第一次调用函数时的第一个参数是 0 expect(mockCallback.mock.calls[0][0]).toBe(0); // 第二次调用函数时的第一个参数是 1 expect(mockCallback.mock.calls[1][0]).toBe(1);
.mock
属性 #
所有的模拟函数都有这个特殊的 .mock
属性,它保存了此函数被调用的信息。 .mock
属性还追踪每次调用时 this
的值,所以也让检视 this 的值成为可能:
const myMock = jest.fn(); const a = new myMock(); const b = {}; const bound = myMock.bind(b); bound(); console.log(myMock.mock.instances); // > [ <a>, <b> ]
这些模拟的成员是非常有用的测试断言这些函数如何得到调用,或者实例化︰
// The function was called exactly once expect(someMockFunction.mock.calls.length).toBe(1); // The first arg of the first call to the function was 'first arg' expect(someMockFunction.mock.calls[0][0]).toBe('first arg'); // The second arg of the first call to the function was 'second arg' expect(someMockFunction.mock.calls[0][1]).toBe('second arg'); // This function was instantiated exactly twice expect(someMockFunction.mock.instances.length).toBe(2); // The object returned by the first instantiation of this function // had a `name` property whose value was set to 'test' expect(someMockFunction.mock.instances[0].name).toEqual('test');
Mock 的返回值 #
模拟功能也可以用于在测试期间将测试值注入您的代码︰
const myMock = jest.fn(); console.log(myMock()); // > undefined myMock.mockReturnValueOnce(10) .mockReturnValueOnce('x') .mockReturnValue(true); console.log(myMock(), myMock(), myMock(), myMock()); // > 10, 'x', true, true
模拟函数在使用功能连续传递风格的代码中也非常有效。 以这种风格编写的代码有助于避免需要复制存根,重新创建他们所在的真实组件的行为,有利于在使用之前将值直接注入到测试中。
const filterTestFn = jest.fn(); // Make the mock return `true` for the first call, // and `false` for the second call filterTestFn .mockReturnValueOnce(true) .mockReturnValueOnce(false); const result = [11, 12].filter(filterTestFn); console.log(result); // > [11] console.log(filterTestFn.mock.calls); // > [ [11], [12] ]
大多数现实世界的例子实际上都涉及到一个模拟功能依赖组件和配置,但该技术是相同的。 在这些情况下,尽量避免在不直接测试的任何函数内实现逻辑的诱惑。
模拟实现 #
不过,有些情况下超越指定返回值的功能是有用的,并且全面替换了模拟函数的实现。 这可以通过 jest.fn
或 mockImplementationOnce
方法对模拟功能。
const myMockFn = jest.fn(cb => cb(null, true)); myMockFn((err, val) => console.log(val)); // > true myMockFn((err, val) => console.log(val)); // > true
如果您需要定义一个模拟的函数,它从另一个模块中创建的默认实现,mockImplementation
方法非常有用︰
// foo.js module.exports = function() { // some implementation; }; // test.js jest.mock('../foo'); // this happens automatically with automocking const foo = require('../foo'); // foo is a mock function foo.mockImplementation(() => 42); foo(); // > 42
当你需要重新创建复杂行为的模拟功能,这样多个函数调用产生不同的结果时,请使用 mockImplementationOnce
方法︰
const myMockFn = jest.fn() .mockImplementationOnce(cb => cb(null, true)) .mockImplementationOnce(cb => cb(null, false)); myMockFn((err, val) => console.log(val)); // > true myMockFn((err, val) => console.log(val)); // > false
当mocked函数用完由mockImplementationOnce
定义的实现时,它将执行使用jest.fn
(如果被定义)的默认实现集:
const myMockFn = jest.fn(() => 'default') .mockImplementationOnce(() => 'first call') .mockImplementationOnce(() => 'second call'); console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn()); // > 'first call', 'second call', 'default', 'default'
对于我们有通常链接的方法(因此总是需要返回this
)的情况,我们有一个语法糖的API以.mockReturnThis()
函数的形式来简化它,它也位于所有模拟器上:
const myObj = { myMethod: jest.fn().mockReturnThis(), }; // is the same as const otherObj = { myMethod: jest.fn(function() { return this; }), };
自定义匹配器 #
最后,为了更简单地说明如何调用mock函数,我们为您添加了一些自定义匹配器函数:
// The mock function was called at least once expect(mockFunc).toBeCalled(); // The mock function was called at least once with the specified args expect(mockFunc).toBeCalledWith(arg1, arg2); // The last call to the mock function was called with the specified args expect(mockFunc).lastCalledWith(arg1, arg2);
这些匹配器是真的只是语法糖的常见形式的检查 .mock
属性。 你总可以手动自己如果是更合你的口味,或如果你需要做一些更具体的事情︰
// The mock function was called at least once expect(mockFunc.mock.calls.length).toBeGreaterThan(0); // The mock function was called at least once with the specified args expect(mockFunc.mock.calls).toContain([arg1, arg2]); // The last call to the mock function was called with the specified args expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual( [arg1, arg2] ); // The first arg of the last call to the mock function was `42` // (note that there is no sugar helper for this specific of an assertion) expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);
这些只是浅尝辄止。匹配器的完整列表,请查阅 参考文档。