How to use Jasmine spies on an object created inside another method? - javascript

Given the following code snippet, how would you create a Jasmine spyOn test to confirm that doSomething gets called when you run MyFunction?
function MyFunction() {
var foo = new MyCoolObject();
foo.doSomething();
};
Here's what my test looks like. Unfortunately, I get an error when the spyOn call is evaluated:
describe("MyFunction", function () {
it("calls doSomething", function () {
spyOn(MyCoolObject, "doSomething");
MyFunction();
expect(MyCoolObject.doSomething).toHaveBeenCalled();
});
});
Jasmine doesn't appear to recognize the doSomething method at that point. Any suggestions?

Alternatively, as Gregg hinted, we could work with 'prototype'. That is, instead of spying on MyCoolObject directly, we can spy on MyCoolObject.prototype.
describe("MyFunction", function () {
it("calls doSomething", function () {
spyOn(MyCoolObject.prototype, "doSomething");
MyFunction();
expect(MyCoolObject.prototype.doSomething).toHaveBeenCalled();
});
});

When you call new MyCoolObject() you invoke the MyCoolObject function and get a new object with the related prototype. This means that when you spyOn(MyCoolObject, "doSomething") you're not setting up a spy on the object returned by the new call, but on a possible doSomething function on the MyCoolObject function itself.
You should be able to do something like:
it("calls doSomething", function() {
var originalConstructor = MyCoolObject,
spiedObj;
spyOn(window, 'MyCoolObject').and.callFake(function() {
spiedObj = new originalConstructor();
spyOn(spiedObj, 'doSomething');
return spiedObj;
});
MyFunction();
expect(spiedObj.doSomething).toHaveBeenCalled();
});

Related

How to create a javascript functional 'class' so that I can access a 'method' from outside and inside the function

I am creating a function that handles a bunch of stuff around pagenating and sorting a table. It contains a key function that submits the db query and updates the display table.
I want to be able to access that inner function/method from both inside the function and also from outside on the object created.
testFunction = function() {
keyMethod = function() {
console.log('ya got me');
};
document.getElementById('test').addEventListener('click', function (e) {
keyMethod();
});
keyMethod();
};
myTest = new testFunction();
myTest.keyMethod();
testFunction = function() {
this.keyMethod = function() {
console.log('ya got me');
};
document.getElementById('test').addEventListener('click', function (e) {
// would have to use bind here which then messes up trying to
// find the correct target etc.
keyMethod();
});
this.keyMethod();
};
myTest= new DrawShape();
myTest.keyMethod();
Creating it the first way means that the keyMethod function is available everywhere within the testFunction but I cant call it from outside.
Creating it the second way means I can do myTest.keyMethod but I then cant call it from within an inner function without using bind everywhere.
Is there a better way..?
You could replace the function provided as callback with an arrow function or use bind the function first like you already said.
testFunction = function() {
this.keyMethod = function() {
console.log('ya got me');
};
// Replace callback by simply providing the function to call.
// This works as long as you don't use the `this` keyword inside the
// provided function.
document.getElementById('test').addEventListener('click', this.keyMethod);
// If your callback method does use the `this` keyword you can either use an
// arrow function or bind the function up front.
document.getElementById('test').addEventListener('click', event => this.keyMethod());
document.getElementById('test').addEventListener('click', this.keyMethod.bind(this));
this.keyMethod();
};
console.log("constructor output:");
myTest = new testFunction();
console.log(".keyMethod() output:");
myTest.keyMethod();
console.log("click event output:");
<button id="test">test</button>

Error in testing a Meteor helper function with Jasmine

I have a helper function in MeteorJS as given below:
Template.cars.helpers({
models : function() {
var currentUserId = Meteor.userId();
return cars.find({userId: currentUserId});
}
});
I am trying to write a Jasmine test which will check if the Meteor.userId() function is called once when the models helper is called. I have mocked the Meteor.userId() function,and my code is given below:
describe("test cars collection", function() {
beforeEach(function() {
var Meteor = {
userId: function() {
return 1;
}
};
});
it("userId should be called once", function() {
Template.cars.__helpers[' models']();
spyOn(Meteor, 'userId').and.callThrough();
expect(Meteor.userId.calls.count()).toBe(1);
});
});
However, the result is showing Expected 0 to be 1. I am new to Jasmine and do not really know how to make the correct call to the Meteor.userId() function, while calling the models helper. I think the way I am spying on it is wrong, but I have not been able to figure it out. Can somebody help please?
From the Jasmine Docs
.calls.count(): returns the number of times the spy was called
But you need to set the spy before you call the function, so you can check if your function has been called only once like so:
it("userId should be called once", function() {
spyOn(Meteor, 'userId').and.callThrough(); // <-- spy on the function call first
Template.cars.__helpers[' models'](); // <-- execute the code which calls `Meteor.userId()`
expect(Meteor.userId.calls.count()).toBe(1); // <-- Now this should work
});

How can I test for equality to a bound function when unit testing?

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

Jasmin spyOn only works when spied-on function is wrapped, but not when delegated to

In this contrived case, I have a service that takes one dependency. The service exposes one method that needs to call through to the dependency.
If I wrap this method call in its own function, then spyOn works as expected (MyService1). However, if I delegate to this service directly, then spyOn fails (MyService2).
My questions are:
Can someone please explain this behaviour?
If I want to use spyOn, must I re-write all my directly delegated methods as wrappers, or is there a way around this?
Thanks.
describe('spyOn problem', function() {
// wraps dependency function - can spy on
var MyService1 = function (dependency) {
this.continue = function() {
dependency.nextStage();
};
};
// delegates to dependecy function - cannot spy on
var MyService2 = function (dependency) {
this.continue = dependency.nextStage;
};
var dependencyMock = {
nextStage: function () {
}
};
it('should call nextStage method of MyService dependency', function () {
var service = new MyService1(dependencyMock);
spyOn(dependencyMock, 'nextStage'); // also tried with .andCallThrough()
service.continue();
expect(dependencyMock.nextStage).toHaveBeenCalled();
// Fail: "Expected spy nextStage to have been called"
});
it('should call nextStage method of MyService2 dependency', function () {
var service = new MyService2(dependencyMock);
spyOn(dependencyMock, 'nextStage');
service.continue();
expect(dependencyMock.nextStage).toHaveBeenCalled();
// Pass
});
});
Jasmine Spies works on the objects, which means for spyOn(dependencyMock, 'nextStage'), the function nextStage on the object dependencyMock will be replaced with a jasmine spy function with this statement.
In MyService2 test case, the spy is installed after actually assigning the function nextStage to continue, which means continue will refer to the actual function nextStage and not to spy. By modifying the test case as below, the spy will be assigned instead.
it('should call nextStage method of MyService2 dependency', function () {
spyOn(dependencyMock, 'nextStage');
var service = new MyService2(dependencyMock);
service.continue();
expect(dependencyMock.nextStage).toHaveBeenCalled();
// Pass
});
In the MyService1 test case, even though you are calling service.continue(), it internally operates on 'dependencyMask` object for which spy is installed.

Is this the correct way to spy on a function in Jasmine?

I have been trying to spy on the original function, but I think I am not doing it right.
Based on the accepted answer of this SO question I wrote this spec:
testSpec.js
describe("Test:", function() {
var user = "foo";
var pass = "bar";
it("Expects login() will be called", function(){
var loginSpy = spyOn(window, 'login').andCallThrough();
login(user, pass);
expect(loginSpy).toHaveBeenCalled();
});
});
Now my test passes of course. But when I remove the line login(user, pass) the test fails. I have chained the spy with andCallThrough(). Now in my test.js I am calling login(user, pass) on document.ready so it is being called I guess. But I am still confused as to how can I get to spy on the login() function that actually is being executed from my test.js script. I dont think even with andCallThrough() it is spying on login() present in test.js. Anything I am missing out?
The problem is the use of document.ready in your test.js. Doing this, the test will run immediately but the call to login will be done after document.ready so it will be called but after the test was run. You should refactor your code so the function that is passed to document.ready can be called separately.
before:
$.ready(function(){
login('foo', 'bar');
})
after:
//init.js
function init(){
login('foo', 'bar');
}
//index.html
<script src="init.js"></script>
<script>
$.ready(init)
</script>
Now you can call the init function in your test
describe("Test:", function() {
var user = "foo";
var pass = "bar";
it("Expects login() will be called", function(){
var loginSpy = spyOn(window, 'login').andCallThrough();
init();
expect(loginSpy).toHaveBeenCalled();
});
});

Categories

Resources