Jasmine. Angular Services. Angular Promises. How to make them play together? - angularjs

I was following this example.
We have test suite like:
describe('Basic Test Suite', function(){
var DataService, httpBackend;
beforeEach(module('iorder'));
beforeEach(inject(
function (_DataService_, $httpBackend) {
DataService = _DataService_;
httpBackend = $httpBackend;
}
));
//And following test method:
it('should call data url ', function () {
var promise = DataService.getMyData();
promise.then(function(result) {
console.log(result, promise); // Don't gets here
}).finally(function (res) {
console.log(res); // And this is also missed
})
})
});
How to make jasmine + karma work with angular services, that returns promise?
I have seen this question, but looks like it's about using promises in test cases. Not about testing promises.

You need to tell jasmine that your test is asynchronous so that it waits for the promises to resolve. You do this by adding a done parameter to your test:
describe('Basic Test Suite', function(){
var DataService, httpBackend;
beforeEach(module('iorder'));
beforeEach(inject(
function (_DataService_, $httpBackend) {
DataService = _DataService_;
httpBackend = $httpBackend;
}
));
//And following test method:
it('should call data url ', function (done) {
var promise = DataService.getMyData();
promise.then(function(result) {
console.log(result, promise); // Don't gets here
done();//this is me telling jasmine that the test is ended
}).finally(function (res) {
console.log(res); // And this is also missed
//since done is only called in the `then` portion, the test will timeout if there was an error that by-passes the `then`
});
})
});
By adding done to the test method, you are letting jasmine know that it is an asynchronous test and it will wait until either done is called, or a timeout. I usually just put a call to done in my then and rely on a timeout to fail the test. Alternatively, I believe you can call done with some kind of error object which will also fail the test, so you could call it in the catch.

Related

How to I mock a service returning a promise in angularjs with done() and catch() callbacks

I want to mock a service for unit test in angularjs which looks something like this:
TranslationService.translate(args)
.then(function translated(value) {
//somecode
return;
})
.catch()
.done();
Following this answer:
How do I mock a service that returns promise in Angularjs Jasmine unit test?
This is what I did to mock it :
TranslateServiceMock = {
translate: jasmine.createSpy('translate').and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
})};
But seems like this still doesn't work, I am guessing its because of the chained 'done' and 'catch'methods,
This is the error I get:
TypeError: undefined is not a constructor (near '....done();...'
Running out of ideas why this might be happening or how to fix this..
As mentioned in the comments, done is not a part of promise object.
I got this working by stubbing the done callback :
beforeEach( function () {
module(myModule.name, function ($provide) {
// define a .done on the $q Promise
$provide.decorator('$q', function ($delegate) {
var Promise = $delegate.when().constructor;
Promise.prototype.done = angular.noop;
return $delegate;
});
$provide.factory('TranslationService', function ($q) {
var svc = jasmine.createSpyObj('TranslationService', ['translate']);
svc.translate.and.returnValue($q.when(''));
return svc;
});
});
});

karma jasmine servce promise does not resolve in unit test and $stateChangeStart

I need to have created the following unit test that relies on a promise in a service being resolved, but the finally() callback is never called. The promise works just fine in the real application. I have read in various places that I need to kick off a digest cycle but that doesn't work. I'm using ui-router and it just starts an $stateChangeStart request and tries to retrieve the template of the first state. (Hence the $httpBackend mock for that).
var $rootScope;
var scope;
var $httpBackend;
var FormulaValidator;
var mockFunctionApiBaseUrl = 'http://localhost:5555';
beforeEach(function() {
module('ps', function($provide) {
$provide.constant('functionApiBaseUrl', mockFunctionApiBaseUrl);
$provide.value('identity', {
getUsernameFromLocalStorage: function() {
console.log('getting mock username from local storage');
return 'BLAH';
},
verifyToken: function(token) {
return true;
}
});
});
beforeEach(function(done) {
inject(function(_$httpBackend_, _$rootScope_, _FormulaValidator_) {
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
FormulaValidator = _FormulaValidator_;
$httpBackend.expect('GET', mockFunctionApiBaseUrl + '/api/list/functions').respond(200, '{"MA": {}}');
$httpBackend.expect('GET', '/0.1.1/json/assets.json').respond(200, '["AAPL US EQUITY"]');
$httpBackend.expect('GET', '/null/templates/dashboard.html').respond(200, '<html></html>');
done();
})
});
afterEach(function() {
$httpBackend.flush();
});
it('Basic Validation 1', function (done) {
FormulaValidator.promise.finally(function () {
console.log('FormulaValidator.spec.promise finally');
var p = FormulaValidator.validateFormula('MA(AAPL US EQUITY, 30)');
console.log('getFunctions: ' + FormulaValidator.getFunctions().length);
expect(p).toBe(true);
done();
});
scope.$apply();
//$rootScope.$digest();
});
An $http promise will only be resolved when you flush the $httpBackend.
Flushing it in afterEach() is too late: the point of flushing $httpBackend is to tell it: OK, now you're supposed to have received requests, send back the response so that the promise is resolved with what I've told you to send back when calling $httpBackend.expect().
Read more about it is the doc.

Unit test a service with http request only angularjs

I have a function called getFrame in a service. The function just returns the $http call to controller.
angular.module('app').factory('DemoFactory', function ($http) {
function getFrame(id) {
var url = 'http://localhost:8080/frames/' + id + '/';
return $http.get(url);
}
return {
getFrame: getFrame
};
});
Now I want to write unittest for this which I am doing as follows:
describe('Service: DemoFactory', function () {
// load the service's module
beforeEach(module('app'));
// Instantiate service
var $httpBackend,
DemoFactory;
beforeEach(inject(function (_$httpBackend_, _DemoFactory_) {
$httpBackend = _$httpBackend_;
DemoFactory = _DemoFactory_;
}));
it('should send proper http request from getFrame', function () {
$httpBackend.expectGET('http://localhost:8080/frames/1/').respond(200);
DemoFactory.getFrame(1);
$httpBackend.flush();
});
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
});
With the given service my aim is to test whether getFrame is making a proper http request or not. So I think I am doing OK here. But something made me wonder that it block is not having any expect. So I need to confirm that for the service I have written I can have unit test as described. Do I need to have anything else in the unit test or can I do it any other way?

Testing an asynchronous function in an angular factory

Im new to testing and im trying to test my angular code in Jasmine. Im stuck on the problem of testing the answer from an resolved promise. Right now the test gets timed out. I would like to have the test waiting for the respons instead of just put in a mockup respons. How do i do that? Is it a bad way of making unit-tests?
angular.module("Module1", ['ng']).factory("Factory1", function($q){
function fn1(){
var deferred = $q.defer();
setTimeout(function(){ deferred.resolve(11); }, 100); // this is representing an async action
return deferred.promise;
}
return { func1 : fn1 };
});
describe('test promise from factory', function() {
var factory1, $rootScope, $q;
beforeEach(module('Module1'));
beforeEach(inject(function(Factory1, _$rootScope_, _$q_) {
factory1=Factory1;
$rootScope = _$rootScope_;
$q = _$q_;
}));
it('should be get the value from the resolved promise', function(done) {
factory1.func1().then(function(res){
expect(res).toBe(11);
done(); // test is over
});
$rootScope.$digest();
});
});
The setTimeout() block represents an async function call, and i dont want to replace it with something like $timeout.
I don't know why you wouldn't want to use the $timeout service.
But if you really want to use the setTimeout, a $rootScope.$digest() is required inside the callback.
function fn1() {
var deferred = $q.defer();
// this is representing an async action
setTimeout(function() {
deferred.resolve(11);
$rootScope.$digest(); // this is required ($timeout do this automatically).
}, 100);
return deferred.promise;
}

Angular $q.when is not resolved in Karma unit test

I use $q.when to wrap around other lib promises.
It works like a charm but when i try to run it inside Karma the promise failes to resolve (done() is never executed) even if I ran $digest and even after timeout.
Here is sample code:
describe('PouchDB', function () {
var $q, $rootScope;
beforeEach(inject(function (_$rootScope_, _$q_) {
$rootScope = _$rootScope_;
$q = _$q_;
}));
it("should run", function (done) {
function getPromise() {
var deferred = Q.defer();
deferred.resolve(1);
return deferred.promise;
}
$q.when(getPromise())
.then(function () {
done(); // this never runs
});
$rootScope.$digest();
});
Why? What is the cause of this? I really cannot get it.
Update (workaround)
I do not understand why $q.when is not resolved in Karma - it has something with nextTick function but I cannot debug the problem.
Instead I ditched $q.when and wrote simple function that converts PouchDB (or any other like Q) to $q:
.factory('$utils', function ($q, $rootScope) {
return {
to$q: function (promise) {
var deferred = $q.defer();
promise.then(function (result) {
deferred.resolve(result);
$rootScope.$digest();
});
promise.catch(function (error) {
deferred.reject(error);
$rootScope.$digest();
});
return deferred.promise;
}
}
})
From How to resolve $q.all promises in Jasmine unit tests? it seems the trick is:
$rootScope.$apply();
I just had the same problem and this works for me; the promises are resolved on making this call.
I've adjusted variable and injected dependency names on this to keep things clear as test writing continues. If done() is a function inside your (controller? directive? service/factory?) then it should be called when the test runs without trying to pass it in as a dependency. Ideally done() should be spied upon, but without knowing where it comes from it is impossible to show you how to set up the spy function.
The only other detail missing is that you have no expect() in this test suite. Without it I have no way to know what you are expecting to be asserted.
describe('PouchDB', function () {
var scope, db, q, rootScope;
beforeEach(
inject(
function(_$rootScope_, _$q_){
rootScope = _$rootScope_;
scope = rootScope.$new();
q = _$q_;
}
)
);
it("should run", function(){
//spy should be constructed here
function getPromise() {
var deferred = q.defer();
deferred.resolve(1);
return deferred.promise;
}
q.when(getPromise)
.then(function () {
done();
});
scope.$digest();
//assertion should be here
});
});

Resources