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
});
});
Related
I have to unit test my controller. First I have to create mock for my services.
Here is my service:
angular.module("demo-app")
.factory("empService",function($http){
var empService={};
empService.getAllEmployees=function(){
return $http.get("http://localhost:3000/api/employees");
}
empService.postEmployee=function(emp){
return $http.post("http://localhost:3000/api/employees",emp);
}
empService.getEmployee=function(id){
return $http.get("http://localhost:3000/api/employees/"+id)
}
empService.putEmployee=function(emp){
return $http.put("http://localhost:3000/api/employees/"+emp._id,emp)
}
empService.deleteEmployee=function(id){
return $http.delete("http://localhost:3000/api/employees/"+id);
}
empService.findEmployee=function(emp){
return $http.post("http://localhost:3000/api/employees/search",emp);
}
return empService;
})
Here is findData() method in my controller, which I am going to test:
$scope.findData=function(){
$scope.loadingEmployee=true;
var emp={};
listProp=Object.getOwnPropertyNames($scope.searchEmployee);
for(index in listProp){
if($scope.searchEmployee[listProp[index]]!=""){
emp[listProp[index]]=$scope.searchEmployee[listProp[index]];
}
}
console.log(emp);
empService.findEmployee(emp).then(function(data){
$scope.allEmployees=data.data;
console.log(data.data);
$scope.loadingEmployee=false;
});
}
How can I mock my empService.findEmployee(emp) method, so that I can test the findData() method.
My spec.js test file with mocking my service method. Here it is:
beforeEach(function(){
var emp={"name":"sanjit"};
fakeService={
getAllEmployees:function(emp){
def=q.defer();
def.resolve({data:[{"name":"sanjit"},{'name':'ssss'}]});
return def.promise;
},
findEmployee:function(emp){
var def=q.defer();
def.resolve({data:[{"name":"sanjit"}]});
console.log("working");
return def.promise;
}
};
spyOn(fakeService,'findEmployee').and.callThrough();
fakeService.findEmployee(emp);
});
beforeEach(angular.mock.inject(function($rootScope,$controller,$injector,$q){
httpBackend=$injector.get('$httpBackend');
scope=$rootScope.$new();
q=$q;
ctrl=$controller('adminEmployeeCtrl',{$scope:scope,empService:fakeService});
}));
it('findData test',function(){
scope.$apply();
scope.findData();
expect(scope.loadingEmployee).toEqual(false);
})
But I got another error:
Error: Unexpected request: GET dashboard/views/dashboard-new.html
No more request expected
But I didn't call it. Please help me
You may not have manually called GET dashboard/views/dashboard-new.html but $scope.$apply() might be triggering it somehow and you can't do anything but handle it.
You can do something like this to handle it: (after injecting it using _$httpBackend_ and assigning to $httpBackend in beforeEach)
$httpBackend.when('GET', 'dashboard/views/dashboard-new.html').respond(200);
scope.$digest();
$httpBackend.flush();
One of the most important rules when testing controllers in angularjs is you do not need to create reall http requests, just mock the functions in that service that are used by your controller. So you need to spyOn them and call fake function to return the proper value. Let's spy on one of them
/**
* #description Tests for adminEmployeeCtrl controller
*/
(function () {
"use strict";
describe('Controller: adminEmployeeCtrl ', function () {
/* jshint -W109 */
var $q, $scope, $controller;
var empService;
var errorResponse = 'Not found';
var employeesResponse = [
{id:1,name:'mohammed' },
{id:2,name:'ramadan' }
];
beforeEach(module(
'loadRequiredModules'
));
beforeEach(inject(function (_$q_,
_$controller_,
_$rootScope_,
_empService_) {
$q = _$q_;
$controller = _$controller_;
$scope = _$rootScope_.$new();
empService = _empService_;
}));
function successSpies(){
spyOn(empService, 'findEmployee').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve(employeesResponse);
return deferred.promise;
// shortcut can be one line
// return $q.resolve(employeesResponse);
});
}
function rejectedSpies(){
spyOn(empService, 'findEmployee').and.callFake(function () {
var deferred = $q.defer();
deferred.reject(errorResponse);
return deferred.promise;
// shortcut can be one line
// return $q.reject(errorResponse);
});
}
function initController(){
$controller('adminEmployeeCtrl', {
$scope: $scope,
empService: empService
});
}
describe('Success controller initialization', function(){
beforeEach(function(){
successSpies();
initController();
});
it('should findData by calling findEmployee',function(){
$scope.findData();
// calling $apply to resolve deferred promises we made in the spies
$scope.$apply();
expect($scope.loadingEmployee).toEqual(false);
expect($scope.allEmployees).toEqual(employeesResponse);
});
});
describe('handle controller initialization errors', function(){
beforeEach(function(){
rejectedSpies();
initController();
});
it('should handle error when calling findEmployee', function(){
$scope.findData();
$scope.$apply();
// your error expectations
});
});
});
}());
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.
I have the following chained sequence of promises:
$scope.promisesInProgress = true
myService.myFirstPromise(id)
.then(function(data){
$scope.firstResponse = data;
return myService.mySecondPromise(id);
})
.then(function(data){
$scope.secondResponse = data;
})
.finally(function(){
$scope.promisesInProgress = false;
});
Is the finally() callback function being called at the very end no matter what the success / failure of the previous two promises are?
For example, if myFirstPromise() returned a 400 response, mySecondPromise() will never be called - but I assume the finally() block would still be thrown? The same should be true if mySecondPromise() returns a 400 (and $scope.secondResponse is never set) and if both promises return 200s.
Angular 1.x $q service inspired by Kris Kowal's Q, based on docs:
finally(callback, notifyCallback) – allows you to observe either the fulfillment or rejection of a promise, but to do so without modifying the final value. This is useful to release resources or do some clean-up that needs to be done whether the promise was rejected or resolved. See the full specification for more information.
so yes, no matter myFirstPromise resolved or rejected, the finally() block would always be called
UPDATED,
to be noticed, the finally() block of myFirstPromise would be called before mySecondPromise resolved(or rejected), because myFirstPromise and mySecondPromise are different promise instance, and mySecondPromise promise instance created after myFirstPromise resolved
I wrote a Jasmine test to see whether the finally() block is called on each function execution no matter what the chained promises returned.
describe('myController Test Suite', function(){
var q, scope, deferred, myService;
// Initialize the Pointivo module
beforeEach(function(){
module('myApp');
});
// Mock out fake service
beforeEach(function(){
myService = {
myFirstPromise: function(){
deferred = q.defer();
// TEST ME
deferred.resolve('first promise response');
return deferred.promise;
},
mySecondPromise: function(){
deferred = q.defer();
// TEST ME
deferred.resolve('second promise response');
return deferred.promise;
},
myThirdPromise: function(){
deferred = q.defer();
// TEST ME
deferred.resolve('third promise response');
return deferred.promise;
}
};
spyOn(myService, 'myFirstPromise').and.callThrough();
spyOn(myService, 'mySecondPromise').and.callThrough();
spyOn(myService, 'myThirdPromise').and.callThrough();
});
// Assign controller scope and service references
beforeEach(inject(function($controller, $rootScope, $q){
scope = $rootScope.$new();
q = $q;
$controller('myController', {
$scope: scope,
myService: myService
});
}));
describe('finally test', function(){
it('should always hit the finally statement', function(){
scope.finallyStatementFlag = false;
scope.test();
scope.$apply();
expect(scope.finallyStatementFlag).toBeTruthy();
});
});
});
The above rests on the assumption that the controller looks like:
myApp.controller('myController', function($scope, myService){
$scope.finallyStatementFlag = false;
$scope.test = function(){
myService.myFirstPromise()
.then(function(data){
console.log(data);
return myService.mySecondPromise()
})
.then(function(data){
console.log(data);
return myService.myThirdPromise();
})
.then(function(data){
console.log(data);
})
.finally(function(){
console.log('finally statement');
$scope.finallyStatementFlag = true;
});
}
});
The above will pass even if you change any or all of the deferred.resolve() to deferred.reject() inside of the beforeEach() callback where we define myService.
Fiddle example
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.
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;
}