• 前端测试框架Jest系列教程 -- Mock Functions(模拟器)


    写在前面:

      在写单元测试的时候有一个最重要的步骤就是Mock,我们通常会根据接口来Mock接口的实现,比如你要测试某个class中的某个方法,而这个方法又依赖了外部的一些接口的实现,从单元测试的角度来说我只关心我测试的方法的内部逻辑,我并不关注与当前class本身依赖的实现,所以我们通常会Mock掉依赖接口的返回,因为我们的测试重点在于特定的方法,所以在Jest中同样提供了Mock的功能,本节主要介绍Jest的Mock Function的功能。

    Jest中的Mock Function

    Mock 函数可以轻松地测试代码之间的连接——这通过擦除函数的实际实现,捕获对函数的调用 ( 以及在这些调用中传递的参数) ,在使用 new 实例化时捕获构造函数的实例,或允许测试时配置返回值的形式来实现。Jest中有两种方式的Mock Function,一种是利用Jest提供的Mock Function创建,另外一种是手动创建来覆写本身的依赖实现。

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

    function forEach(items, callback) {
      for (let index = 0; index < items.length; index++) {
        callback(items[index]);
      }
    }

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

    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 Function都带有 .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);

    在测试中,需要对函数如何被调用,或者实例化做断言时,这些 mock 成员变量很有帮助意义︰

    // 这个函数只调用一次
    expect(someMockFunction.mock.calls.length).toBe(1);
    
    // 这个函数被第一次调用时的第一个 arg 是 'first arg'
    expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
    
    // 这个函数被第一次调用时的第二个 arg 是 'second arg'
    expect(someMockFunction.mock.calls[0][1]).toBe('second arg');
    
    // 这个函数被实例化两次
    expect(someMockFunction.mock.instances.length).toBe(2);
    
    // 这个函数被第一次实例化返回的对象中,有一个 name 属性,且被设置为了 '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());

    用于函数连续传递风格(CPS)的代码中时,Mock 函数也非常有效。 以这种风格编写的代码有助于避免那种需要通过复杂的中间值,来重建他们在真实组件的行为,这有利于在它们被调用之前将值直接注入到测试中。

    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] ]

    大多数现实世界的例子实际上都涉及到将一个被依赖的组件上使用 mock 函数替代并进行配置,这在技术上(和上面的描述)是相同的。 在这些情况下,尽量避免在非真正想要进行测试的任何函数内实现逻辑。

    有些情况下超越指定返回值的功能是有用的,并且全面替换了模拟函数的实现。

    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

     当指定的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 Function起一个准确的名字,这样有助于你在测试错误的时候在输出窗口定位到具体的Function

    const myMockFn = jest
      .fn()
      .mockReturnValue('default')
      .mockImplementation(scalar => 42 + scalar)
      .mockName('add42');

    最后,为了更简单地说明如何调用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);
    
    // All calls and the name of the mock is written as a snapshot
    expect(mockFunc).toMatchSnapshot();

    这些匹配器是真的只是语法糖的常见形式的检查 .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);
    
    // A snapshot will check that a mock was invoked the same number of times,
    // in the same order, with the same arguments. It will also assert on the name.
    expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);
    expect(mockFunc.mock.getMockName()).toBe('a mock name');

    写在最后:

    本文只是简单的介绍了Mock Function的功能,更完整的匹配器列表,请查阅 参考文档

    系列教程:

       1. 前端测试框架Jest系列教程 -- Matchers(匹配器)

       2.前端测试框架Jest系列教程 -- Asynchronous(测试异步代码)

       3.前端测试框架Jest系列教程 -- Mock Functions(模拟器)

       4.前端测试框架Jest系列教程 -- Global Functions(全局函数)

  • 相关阅读:
    ioS开发之CoreLocation(GPS定位)
    iOSiOS开发之退出功能(易错)
    iOS开发之判断横竖屏切换
    iOS开发之左右抖动效果
    iOS开发之UIPopoverController
    thinkphp or查询
    Invalid left-hand side in assignment
    实现input表单从右向左输入
    thinkPhp不为空查询
    array_filter()用法
  • 原文地址:https://www.cnblogs.com/Wolfmanlq/p/8025329.html
Copyright © 2020-2023  润新知