Testing JS callback with timeout using Jasmine - javascript

I need to test some code in Angular with Jasmine, but the thing is that I can't do this because of $timeout call. So the code looks like this:
$scope.add = function() {
SomeService.add(id, function() {
$timeout(function() {
$scope.showSuccessMessage();
}, 1000)
}, function() {})
};
So the test code is:
describe('method add', function() {
it('should add', function() {
spyOn(SomeService, 'add').and.callFake(function(id, successCallback, errorCallback) {
spyOn(scope, 'showSuccessMessage');
successCallback();
expect(scope.showSuccessMessage).toHaveBeenCalled();
});
scope.add();
expect(SomeService.add).toHaveBeenCalled();
});
});
And the problem is that because of the timeout call I can't check that showSuccessMessage() has been called. I know about Jasmine's ability to work with timeouts but in that case I can't find a working way because of calling it in the callback.

You can flush your timeout by using $timeout.flush() after calling the original function. That should allow you to access the successCallback.
Also, I would put the spy and expect of the showSuccessMessage outside the other spy
describe('method add', function() {
it('should add', function() {
spyOn(SomeService, 'add').and.callFake(function(id, successCallback, errorCallback) {
successCallback();
});
spyOn(scope, 'showSuccessMessage');
scope.add();
$timeout.flush();
expect(SomeService.add).toHaveBeenCalled();
expect(scope.showSuccessMessage).toHaveBeenCalled();
});
});

Im not very familiar with the workings of angular, I hope this helps:
You can use the done function for asynchronous code:
it('should add', function (done) {
$scope.successCallback = function () {
// successCallback was called
done();
}
$scope.add();
});

Related

Angular Test does not call a promise callback

Environment:
Angular 1.5.8
Unit Tests with Karma/Jasmine
This is my controller, which aims to get a value from $stateParams and uses that, to perform a DELETE request, later on.
Right now it should ask the user, wether to delete the object.
This is done with a sweetalert.
I have removed ngdoc comments and arbitrary SWAL config.
ClientDeleteController.js:
angular.module('app.data').controller('ClientDeleteController', [
'$stateParams', '$q',
function ($stateParams, $q) {
'use strict';
var vm = this;
vm.clientId = $stateParams.id;
vm.promptDeferred = null;
vm.prompt = function () {
// create promise
var d = $q.defer();
vm.promptDeferred = d;
// create prompt
swal({ .... }, vm.swalCallback);
};
vm.swalCallback = function (confirmed) {
if (confirmed) {
console.info('resolving...');
vm.promptDeferred.resolve();
} else {
console.info('rejecting...');
vm.promptDeferred.reject();
}
};
vm.delete = function () {
vm.prompt();
vm.promptDeferred.promise.then(vm.performDelete);
};
vm.performDelete = function () {
console.info('performing');
};
}]);
This is the test suite:
ClientDeletecontrollerSpec.js
describe('Controller:ClientDeleteController', function () {
var controller
, $httpBackend
, $rootScope
, $controller
, scope
, $q
, $stateParams = {id: 1}
, resolvePromise = true
;
swal = function (options, callback) {
console.info('i am swal, this is callback', callback+'', resolvePromise);
callback(resolvePromise);
};
beforeEach(function () {
module('app');
module('app.data');
module('ui.bootstrap');
module(function ($provide) {
$provide.service('$stateParams', function () {
return $stateParams;
});
});
inject(function (_$controller_, _$rootScope_, _$q_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
$q = _$q_;
scope = $rootScope.$new();
controller = $controller('ClientDeleteController', {$scope: scope, $q: $q});
});
});
describe('basic controller features', function () {
it('should be defined', function () {
expect(controller).toBeDefined();
});
it('should get the client id from the state params', function () {
expect(controller.clientId).toBeDefined();
expect(controller.clientId).toEqual($stateParams.id);
});
});
fdescribe('client delete process', function () {
it('should ask the user if he really wants to delete the client', function () {
spyOn(controller, 'prompt').and.callThrough();
controller.delete();
expect(controller.prompt).toHaveBeenCalled();
});
it('should create a promise', function () {
controller.prompt();
expect(controller.promptDeferred).toBeDefined();
});
it('should delete when the user clicked yes', function () {
spyOn(controller, 'performDelete').and.callThrough();
controller.delete();
expect(controller.performDelete).toHaveBeenCalled();
});
it('should not delete when the user clicked no', function () {
spyOn(controller, 'performDelete').and.callThrough();
resolvePromise = false;
controller.delete();
expect(controller.performDelete).not.toHaveBeenCalled();
});
});
});
The test should delete when the user clicked yes fails, the test should not delete when the user clicked noreturns a false positive.
The console.info(...) within the swal mock logs the correct callback function. The logs in the function itself are also logged, which tells me, the callback is fired.
Since in the next line, I call either vm.promptDeferred.resolve() resp. .reject(), I expect the call to actually happen.
Nonetheless, the test result is Expected spy performDelete to have been called..
I have mocked swal in another test the same way and it works fine.
I don't get why the promise won't be resolved.
Notice: When I don't store the promise directly on the controller but return it from prompt() and use the regular .prompt().then(...), it won't work either.
The logs are the same and I like to split functions as much as possible, so it is easier to understand, easier to test and to document.
There are hundreds of other tests in this application but I can't see, why this one won't work as expected.
Thank you for any insight.
What happens if you do $rootScope.$digest() just after invoking delete in the test?
In my experience something like that is necessary to make angular resolve promises during tests (either that or $rootScope.$apply() or $httpBackend.flush())

Jasmine 2 Spec has no expectations

I have the following test:
describe('when invoked', function() {
it('should check something', function() {
_.each(someData, function(obj, index) {
expect(obj[index].lable).toBe('foo');
});
});
});
When I run Jasmine 2.2.0 it get the following error:
Spec 'SpecLabel function when invoked return value should check something' has no expectations.
Am I missing something? In Jasmin 1.x we could do this. Have expect inside a for each, or even a for loop.
How can I fix these type of tests? And what are the docs for these situations? The Jasmine website is not really helpful.
A quick workaround can be refactoring your tests to:
describe('when invoked', function () {
it('should check something', function () {
var success = true;
_.each(someData, function (obj, index) {
success &= obj[index].lable === 'foo';
});
expect(success).toBeTruthy();
});
});

Resetting ajax mock calls count in Jest tests

I have test structure represented below, my code uses ajax calls so I'm mocking as represented in the Jest tutorial:
describe('it behavior', function() {
it('is as it is', function() {
jQuery.ajax.mock.calls[0][0].success({
data: []
});
...
});
it('is is not as it was', function() {
jQuery.ajax.mock.calls[1][0].success({
data: [1, 2, 3]
});
...
});
});
What bothers me most is this jQuery.ajax.mock.calls[X][0] in every case, mostly because I made typos or forget to increment call number in mock.
Is it possible to reset mock call count in afterEach callback or some pattern for test organisation in that case? Preferably without any extra test dependencies.
Try this:
describe('something', function() {
beforeEach(function() {
jQuery.ajax = jest.genMockFunction()
})
it('does something', function() {
// do something
})
}

Create a service to repeat functions in controller

I'm trying to write a service that simulates a polling functionality. My code is the following:
app.service('poller', ['$timeout',
function($timeout) {
return ({
poll
})
function poll(e) {
$timeout(function() {
poll(e);
}, 5000);
}
}
]);
When I inject it in my controller I try to use it like this:
poller.poll($scope.getNewMessages());
The weird thing is that it's only called once. Also when I try to use console log in the service like console.log(e) I get undefined. What am I doing wrong?
You need to pass function as value to the poller function and you need to call the function:
app.service('poller', ['$timeout', function($timeout) {
return ({
poll
});
function poll(e) {
e();
$timeout(function() {
poll(e);
}, 5000);
}
}
]);
poller.poll($scope.getNewMessages);

Node Mocha Chai Async - Everything Passing even when it should fail

I was attempting to teach myself to use a Testing framework for automating tests instead of having to do them by hand. After a bit of trial and error, I finally got the unit tests to start passing ... but now, my problem is everything is passing regardless of if it should or not.
Currently I have the following code:
describe('create {authName, authPW}', function() {
it('no name', function() {
init({
path: ':memory:',
callback: function() {
var arg;
arg = {};
//arg['authName'] = 'Name';
arg['authPW'] = 'Pass';
arg['callback'] = function(r) {
// r.should.equal('create error');
r.should.equal('foobar');
done();
};
create(arg);
}
});
});
});
as you can guess ... r should NOT equal 'foobar'
What am I doing wrong here?
When creating async tests with mocha you need to let him know when it is done
describe('an asynch piece of code', function() {
var foo = new bar();
it('should call the callback with a result', function( done ) {
foo.doAsynchStuff( function( result ) {
result.should.be.ok;
done();
});
});
});
If done is present as an argument on the it then mocha will wait for the done to be called. It has a timeout of 2 seconds, that if exceeded fails the test. You can increase this timeout:
it('should resolve in less than 10 seconds', function( done ) {
this.timeout( 10000 );
foo.doAsynchStuff( function( result ) {
result.should.be.ok;
done();
});
}
it('no name', function(done) {
done has to be an argument of the function passed to it()

Categories