Spy on setTimeout and clearTimeout in Karma and Jasmine - javascript

I cannot seem to be able to spy on setTimeout and clearTimeout in my Jasmine tests, which are being run through Karma.
I have tried variations on all of this
spyOn(window, 'setTimeout').and.callFake(()=>{});
spyOn(global, 'setTimeout').and.callFake(()=>{});
spyOn(window, 'clearTimeout').and.callThrough();
clock = jasmine.clock();
clock.install();
spyOn(clock, 'setTimeout').and.callThrough();
runMyCode();
expect(window.setTimeout).toHaveBeenCalled(); // no
expect(global.setTimeout).toHaveBeenCalled(); // nope
expect(window.clearTimeout).toHaveBeenCalled(); // no again
expect(clock.setTimeout).toHaveBeenCalled(); // and no
In every case, I can confirm that setTimeout and clearTimeout have been invoked in runMyCode, but instead I always get Expected spy setTimeout to have been called.
For window, clearly this is because the test and the runner (the Karma window) are in different frames (so why should I expect anything different). But because of this, I can't see any way to confirm that these global functions have been invoked.
I know that I can use jasmine.clock() to confirm that timeout/interval callbacks have been invoked, but it looks like I can't watch setTimeout itself. And confirming that clearTimeout has been called simply isn't possible.
At this point, the only thing I can think of is to add a separate layer of abstraction to wrap setTimeout and clearTimeout or to inject the functions as dependencies, which I've done before, but I think is weird.

I was able to get it to work like this:
spyOn(window, 'setTimeout');
runMyCode();
expect(setTimeout).toHaveBeenCalled();
Just remove the 'window' object from the setTimeout call.

For those looking for a Jest solution, it has dedicated fake timer functions (which are also spyable).

Edit: Since asking this question, it looks like Jasmine has implemented Clock, which makes this kind of mocking possible. And, as Piotr Jaworski's answer points out, Facebook's Jasmine-based Jest provides its own (arguably much better) way of mocking and spying on timed tasks.
So, the rest of the answer is dated....
The only -- and only -- solution I could find for this is to use Rewire (in my case, I am required to also use Rewire-Webpack).
Rewire does allow you to replace global methods -- but once the method has been replaced, it cannot be spied upon. So, to actually to successfully use toHaveBeenCalledWith, you must wrap and proxy the mock function.
var rewire = require('rewire'),
myModule = rewire('./path/to/module');
describe(function () {
var mocks = {
setTimeout: function () { return 99: },
clearTimeout: function () {}
};
beforeEach(function () {
// This will work
myModule.__set__('setTimeout', function () {
mocks.setTimeout.apply(null, arguments)
})
// This will NOT work
myModule.__set__('clearTimeout', mocks.clearTimeout)
});
it('calls setTimeout', function () {
spyOn(mocks, 'setTimeout').and.callThrough();
spyOn(mocks, 'clearTimeout').and.callThrough();
myModule.doSomething(); // this will invoke setTimeout locally
expect(mocks.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 1000);
expect(mocks.clearTimeout).toHaveBeenCalledWith(99); // Won't work (see above)
});
});
Naturally, this will surely stop working the next time Jasmine, Rewire, Karma, Webpack... or the weather... changes (grrr). If this doesn't work for you, please leave a comment so future devs will know.

Related

Angular Unit Test, SpyOn stand-alone function

I am using Jasmine to test an Angular app and would like to test that the getItem() function within my controller is called when the ready() function of the controller is called.
--- Controller ---
var vm = this;
vm.items = [];
$ionicPlatform.ready(ready);
function ready() {
vm.items.push(getItem());
function getItem(){
var item = //do stuff to get item;
console.log('getItem called');
return item;
}
}
--- Spec ---
describe('Controller', function(){
//--- Load app with dependencies and module to test code omitted.
beforeEach(function(){
//How do I spy on getItem() to test that it was called?
//I've tried getItem = jasmine.createSpy()
//I've tried spyOn(window, 'getItem')
}
//--- Initialize the controller and a mock scope code omitted.
beforeEach(function(done){
$ionicPlatform.ready(function(){
done();
});
});
it('getItem function should get called', function(){
expect(getItem).toHaveBeenCalled();
//--- Note, getItem does not get called according to the expect statement,
//--- but does output 'getItem called' to the terminal when running the test.
});
});
Unfortunately, you've come upon a fundamental limit of Javascript unit testing with Jasmine-- you can only spyOn methods that are exposed on some object. If there is a function that is internal to another function, and not exposed in anyway, you cannot test it directly.
However, you do have two options available to you:
Expose the function in a way that it can be spied on (generally as a method of whatever Angular component you are testing).
Test it indirectly.
The first is probably relatively self-evident, but the latter may be a little confusing. Basically, you can't test directly if the getItems function is called, but the function may have downstream methods it calls or values it changes you can test. For instance, you can test that vm.items.push is larger after ready is called, or you can spyOn(console.log) and expect(console.log).toHaveBeenCalledWith('getItem called').
You can find arguments for both approaches on the internet-- I tend to prefer approach two because I don't like doing refactors solely for the purpose of testability, but many will argue that refactoring for testability generally yields better code. That choice is yours to make. Hope this helps!

Jasmine - Method not being called

var obj = {};
obj.localContext = 'firstTemp';
obj.call = function(){
obj.localContext = 'secondTemp';
};
Jasmine-spec:
it('value of localContext', function(){
spyOn(obj, 'call');
obj.call();
expect(obj.localContext).toEqual('secondTemp');
});
Why is the obj.call() method never being called? When I run the spec, the value of obj.localContext is still firstTemp instead of secondTemp
When you create a spy, the default behaviour is to replace the object with a mock that doesn't call the original. Typically you'd use it to test functionality that would otherwise call out to other APIs you don't want to be called - you can test that they would have been called, without actually calling them.
Jasmine does provide you a way to also call the original function though:
spyOn(obj, "call").and.callThrough();
See the Jasmine documentation for spies (unfortunately, linking directly to the and.callThrough section doesn't work)

Jasmine spies callThrough and callFake

I have a scenario whereby I want to call done() on a beforeEach after a callback has been invoked.
I tried to do this as follows :
spyOn(scope, 'onAdmin').and.callThrough().and.callFake(function(){done()})
But I'm not sure I get the right behaviour. Essentially what I want to achieve is to be able to call done() after each callback is done doing what it does.
UPDATE: workaround solution
scope.onAdminBackup = scope.onAdmin;
spyOn(scope, 'onAdmin').and.callFake(function(admin) {
scope.onAdminBackup();
done() ;
})
I have never chained these kinds of functions together cuz in my mind they seem to do the opposite. You are saying when I call this method -onAdmin - in the scope call it as normal. Which is what the callThrough method jasmine provides for us does.
But then you are chaining along a callFake method as well so then you say but dont actually call it call this fake function instead - very conflicting.
If you want to call spy on the method onAdmin and instead of it being fired you want it to do something else - something mocked - then use the .and.callFake(fn). Also take into account like #stefan above said - dont invoke the function - callFake is simply wanting a function as a parameter it will take care of calling it itself.
This might be more along the lines of what you are looking for, if not show us some more code.
spyOn(scope, 'onAdmin')and.callFake(done)
you are calling done right-away when you write done()
try passing in done as a value:
spyOn(scope, 'onAdmin').and.callThrough().and.callFake(done)
I found a work around way to do this. Jasmine has a method called addSpyStategy where you can add a custom strategy like callThrough or callFake. It would look something like this:
jasmine.addSpyStrategy('callThroughAndThen', (spy, fn) => {
return function() {
spy.and.callThrough();
setTimeout(() => fn(...arguments), 0);
}
});
the timeout makes sure the real function finishes before executing the custom function. Then for your spy, you can do this:
const spy = spyOn(scope, 'onAdmin')
spy.and.callThroughAndThen(spy, () => {
// your custom callback
done();
});
note: make sure to put custom strategy in a beforeEach block

Jasmine spyOn: How to make it work when the spyed function reference is passed?

I'm using jasmine to test my javascript, and I'm finding a hard case to spec. I have this function which is being passed directly to the handler:
filters.find('#per_page').change(checkList.filter);
Now, I'd like to spy on it and see if it's being called when i change that:
describe("when I change the number of items per page", function() {
beforeEach(function()
spyOn(checkList, 'filter');
$('#per_page').val('50').trigger('change');
});
it("filters the results list", function() {
expect(checkList.filter).toHaveBeenCalled();
});
});
This test fails. Suprisingly, if I change my code above to:
filters.find('#per_page').change(function() { checkList.filter(); });
it passes. Is there a way to spy on such functions passed or do I have to resort to the anonymous function case?
Yes, there is, but to do so events have to be bound after you have added the spies on your function. The reason for this is that spies replace the property with an other function (it doesn't change the function itself). In your case, what happened is that you assigned the original function and when you added the spies you replaced the property without changing the callback that was bound.
If the way your application is made you can't add your spies before doing the events binding, than the only it can work is to use anonymous function which call the proper callback.

Testing delegate callbacks using Jasmine (JavaScript)

I have a simple JavaScript function that uses two delegates to (asynchronously) get one value back:
function getMyUserName() {
context.load(user);
context.executeQueryAsync(onGetMyUserNameSuccess, onGetMyUserNameFail);
}
function onGetMyUserNameSuccess() {
return user.get_title();
}
function onGetMyUserNameFail(sender, args) {
return args.get_message();
}
The "context" and "user" variables are already set and initialized, and the first delegate ("onGetMyUserNameSuccess") is getting the correct answer. The question is how I can test the "getMyUserName" function with Jasmine?. If I use "runs", I have no way to know the response from the delegates (also I don't see any way to know if the delegates are called). I tried to set spies to mock the delegates, but probably I didn't it correctly (I'm just starting with Jasmine).
Any help will be very welcome.
Thanks in advance,
Gustavo
In most of the cases when you have to work with async code, you should call the function by yourself. But not directly but in the way your code would call it. So in your case spy on context.executeQueryAsync and use spy.mostRecentCall.args to get the reference to the function and then call them.
var async = jasmin.spyOn(context, 'executeQueryAsync');
async.mostRecentCall.args[0]()
var args = {get_message: jasmine.createSpy()}
async.mostRecentCall.args[1]({}, args);
expect(args.get_message.toHaveBeenCalled());
Note that there is the sinon framework that have a bunch of methodes to automatically call callbacks

Categories

Resources