最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How can I test for equality to a bound function when unit testing? - Stack Overflow

programmeradmin6浏览0评论

I want to test that an argument passed to a function is a function reference but the function reference is being passed using bind().

Consider this code which is to be tested (shortened for brevity):

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

And this unit test to check if register() was called with handler():

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

The arg doesn't equal the function reference I guess due to the bound function using bind() - how can I test that the correct function reference is being passed while still using the bind() method on it?

Note: This isn't specific to jasmine, I just thought it was appropriate because of the methods being used.

I want to test that an argument passed to a function is a function reference but the function reference is being passed using bind().

Consider this code which is to be tested (shortened for brevity):

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

And this unit test to check if register() was called with handler():

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

The arg doesn't equal the function reference I guess due to the bound function using bind() - how can I test that the correct function reference is being passed while still using the bind() method on it?

Note: This isn't specific to jasmine, I just thought it was appropriate because of the methods being used.

Share Improve this question asked Dec 16, 2015 at 9:09 Phil CooperPhil Cooper 3,1231 gold badge42 silver badges63 bronze badges
Add a comment  | 

5 Answers 5

Reset to default 8

Instead of

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

you can do

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

or

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

This works because the internal [[HasInstance]] method of the bound function delegates to the [[HasInstance]] method of the original function.

This blog post has a more detailed analysis of bound functions.

this.handler.bind(this) creates completely a new function, therefore it is not equal to bar.handler. See Function.prototype.bind().

You can pass bounded function as argument to your initialize function and then test it, e.g.:

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

I've managed to keep the test and code and work around it.

I spy on the function reference with an empty anon func, then call it when spying on the register method - if the spy gets called, I know it's passed the correct reference.

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();
});

I thought I'd add another approach that, to me, is a bit less awkward.

given a class like:

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

the full spec might look like:

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);
    });
  });
});

In my case (using jest) I just mocked the implementation of bind for the function I wanted and I tweaked it so that it returns the original function and not a bound copy of it.

Specifically here's what I tried and worked:

Code to be tested:

// 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));
}

I wanted to assert that when run is called, funcsToExecute contains bar

So I wrote the test like this:

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);
});

For your example, I suppose it would be something like this:

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);
});

though I haven't tested it.

发布评论

评论列表(0)

  1. 暂无评论