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 badges5 Answers
Reset to default 8Instead 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.