【问题标题】:How can I test for equality to a bound function when unit testing?单元测试时如何测试绑定函数的相等性?
【发布时间】:2015-12-16 09:09:00
【问题描述】:

我想测试传递给函数的参数是函数引用,但函数引用是使用bind() 传递的。

考虑这个要测试的代码(为了简洁而缩短):

initialize: function () {
    this.register(this.handler.bind(this));
}

这个单元测试检查register()是否被handler()调用:

it('register handler', function () {
    spyOn(bar, 'register');
    bar.initialize();
    expect(bar.register.calls.argsFor(0)[0]).toEqual(bar.handler);
});

我猜由于使用 bind() 的绑定函数,arg 不等于函数引用 - 如何在仍然使用 bind() 方法的同时测试是否传递了正确的函数引用?

注意:这不是茉莉花特有的,我只是认为它是合适的,因为正在使用的方法。

【问题讨论】:

    标签: javascript unit-testing jasmine


    【解决方案1】:

    代替

    expect(bar.register.calls.argsFor(0)[0]).toEqual(bar.handler);
    

    你可以的

    expect(Object.create(bar.handler.prototype) instanceof bar.register.calls.argsFor(0)[0])
      .toBe(true);
    

    expect(Object.create(bar.handler.prototype)).
      toEqual(jasmine.any(bar.register.calls.argsFor(0)[0]));
    

    这是因为绑定函数的内部 [[HasInstance]] 方法委托给原始函数的 [[HasInstance]] 方法。

    This blog post对绑定函数有更详细的分析。

    【讨论】:

      【解决方案2】:

      this.handler.bind(this) 完全创建了一个新函数,因此它不等于bar.handler。 见Function.prototype.bind()

      您可以将有界函数作为参数传递给initialize 函数,然后对其进行测试,例如:

      var handler = bar.handler.bind(bar);
      bar.initialize(handler);
      expect(bar.register.calls.argsFor(0)[0]).toEqual(handler);
      

      【讨论】:

      • 我知道 bind() 是一个新函数 - 在控制台中,它会打印为 [native code],因此它将无法通过相等性测试。不幸的是,initialize 不期待任何论据,所以我不能这样做。我可能会被卡住并且不得不跳过测试,因为我不想仅仅为了支持测试而更改代码。感谢您的回复。
      【解决方案3】:

      我已经设法保留测试和代码并解决它。

      我用一个空的 anon func 监视函数引用,然后在监视 register 方法时调用它 - 如果调用了 spy,我知道它传递了正确的引用。

      it('register handler', function () {
          spyOn(bar, 'handler').and.callFake(function(){}); // do nothing
          spyOn(bar, 'register').and.callFake(function(fn){
              fn();
              expect(bar.handler).toHaveBeenCalled();
          });
          bar.initialize();
      });
      

      【讨论】:

        【解决方案4】:

        就我而言(使用jest),我只是模拟了我想要的函数的绑定实现,并对其进行了调整,使其返回原始函数而不是它的绑定副本。

        具体来说,这是我尝试和工作的:

        要测试的代码:

        // module test.js
        
        export const funcsToExecute = [];
        function foo(func) {
            funcsToExecute.push(func);
        }
        
        export function bar(someArg) {
            // bar body
        }
        
        export function run(someArg) {
            foo(bar.bind(null, someArg));
        }
        

        我想断言当run 被调用时,funcsToExecute 包含bar

        所以我写了这样的测试:

        import * as test from 'test';
        
        it('should check that "funcsToExecute" contain only "bar"', () => {
            jest.spyOn(test.bar, 'bind').mockImplementation((thisVal, ...args) => test.bar);
        
            test.run(5);
        
            expect(test.funcsToExecute.length).toBe(1);
            expect(test.funcsToExecute[0]).toBe(test.bar);
        });
        

        对于你的例子,我想应该是这样的:

        it('register handler', function () {
            spyOn(bar, 'register');
            spyOn(bar.handler, 'bind').mockImplementation((thisVal, ...args) => bar.handler);
            bar.initialize();
            expect(bar.register.calls.argsFor(0)[0]).toBe(bar.handler);
        });
        

        虽然我没有测试过。

        【讨论】:

          【解决方案5】:

          我想我会添加另一种对我来说不那么尴尬的方法。

          给定一个类:

          class Bar {
            public initialize() {
              this.register(this.handler.bind(this));
            }
            private register(callback) {}
            private handler() {}
          }
          

          完整的规范可能如下所示:

          describe('Bar', () => {
            let bar;
          
            beforeEach(() => {
              bar = new Bar();
            });
          
            describe('initialize', () => {
              let handlerContext;
          
              beforeEach(() => {
                bar.handler = function() {
                  handlerContext = this;
                };
                bar.register = jest.fn(callback => {
                  callback();
                });
                bar.initialize();
              });
          
              it('calls register with the handler', () => {
                expect(bar.register).toHaveBeenCalledWith(expect.any(Function));
              });
          
              it('handler is context bound', () => {
                expect(handlerContext).toEqual(bar);
              });
            });
          });
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-01-03
            • 1970-01-01
            • 2021-09-24
            • 2011-12-24
            • 2010-09-26
            • 2010-09-24
            • 2019-08-09
            • 1970-01-01
            相关资源
            最近更新 更多