Promise resolves too late in unit test - angularjs

Unit testing an app using Firebase and angularFire. Mocking Firebase with mockfirebase.
In this test, the promise is resolved after the test is finished:
describe('the service api', function() {
var promiseResolved;
beforeEach(function() {
// Inject with expected values
_setup();
promiseResolved = jasmine.createSpy('promiseResolved');
});
it('should resolve to a obj', function() {
var obj = objService.getObjFromRefString('1234/q1w2');
obj.$loaded().then(promiseResolved);
obj.$ref().flush();
expect(promiseResolved).toHaveBeenCalled(); // fails
});
});
This approach seems to work for the angularFire tests - see line 125.
If i use Jasmine async done feature:
it('should resolve to a obj', function(done) {
var obj = objService.getObjFromRefString('1234/q1w2');
obj.$ref().flush();
obj.$loaded().then(function() {
console.log('resolved');
promiseResolved();
expect(promiseResolved).toHaveBeenCalled();
done();
});
});
It fails with message "Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL".
The console.log is shown before.
The promise seems to resolve just after the timeout. I tried calling $rootScope.$apply() with no changes.
How can i make the promise resolve right away so the test can pass?

This solved it:
$timeout.flush();
Updated test:
it('should resolve to a obj', function() {
var obj = objService.getObjFromRefString('1234/q1w2');
obj.$loaded().then(promiseResolved);
obj.$ref().flush();
$timeout.flush();
expect(promiseResolved).toHaveBeenCalled(); // great success
});

Related

Jasmine unit test for angular service In controller

I'm new to jasmine framework. I've gone through some tutorials and learned and started writing unit tests. 'facing one issue here is the description.
I have a controller where i can invoke a service call to get the data. See the code below.
$scope.getEmpInfo = function() {
EmpService.getInfo($scope.empid)
.then(function(data) {
$scope.empData = data;
$scope.populateEmpData($scope.empData);
}, function(reason) {
//do nothing
}
}
Now, i want to write a unit test for the above method. Im able to make a spy on serice using promise but i wasnt able to spy $scope.populateEmpData(). here is my test case.
describe('Emp data', function() {
var d, scope;
beforeEach(function() {
module("emp");
module("emo.info");
});
describe('empcontroller', function() {
beforeEach(inject(function($q,_EmpService_, $controller,$rootScope){
d = $q.defer();
empService = _EmpService_;
spyOn(empService,"getInfo").and.returnValue(d.promise);
scope = $rootScope.$new();
empCtrl = $controller("empController", {
$scope: scope,
});
}));
it('should get the Employee information ', function() {
scope.getEmpInfo();
spyOn(scope,'populateEmpData');
expect(EmpService.getInfo).toHaveBeenCalled();
//Here im getting the error.
expect(scope.populateEmpData).toHaveBeenCalled();
});
});
});
Please help resolve this issue. Thanks in advance.
It's because you are not resolving promise. You will have to make change in spyOn.
- spyOn(empService,"getInfo").and.callFake(function() {
return {
then : function(success, error) {
success();
}
} }
Now, it will go into the success callback and will try to call $scope.populateEmpData();
You're never resolving your promise. And you need to call $scope.$apply().
Why is this necessary? Because any promises made with the $q service
to be resolved/rejected are processed upon each run of angular’s
digest cycle. Conceptually, the call to .resolve changes the state of
the promise and adds it to a queue. Each time angular’s digest cycle
runs, any outstanding promises will be processed and removed from the
queue.
Unit Testing with $q Promises in AngularJS
Check it out above link it will help you.

Finally clause of promise never getting executed in Jasmine test

I have this jasmine test, and the finally clause on a promise appears to not be getting executed, as I get the error:
PhantomJS 2.1.1 (Mac OS X 0.0.0) Service: petsFactory .getPetsAsync() should return a list of pets FAILED
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
My test file looks like:
'use strict';
describe('Service: petsFactory', function () {
// load the service's module
beforeEach(module('smokeTestApp'));
// instantiate service
var petsFactory;
beforeEach(inject(function (_petsFactory_) {
petsFactory = _petsFactory_;
}));
describe('.getPetsAsync()', function () {
it('should return a list of pets', function (done) {
var testPets = function (pets) {
expect(Array.isArray(pets)).toBe(true);
}
var failTest = function(error) {
expect(error).toBeUndefined();
};
petsFactory
.getPetsAsync()
.then(testPets)
.catch(failTest)
.finally(done);
});
});
});
The relevant factory method looks like:
var getPetsAsync = function () {
return $q.when(pets);
};
The contents of the pets variable is totally synchronous, the promise is just a wrapper on a synchronous value that is there immediately.
What is going wrong here?
From the documentation:
When testing promises, it's important to know that the resolution of
promises is tied to the digest cycle. That means a promise's then,
catch and finally callback functions are only called after a digest
has run. In tests, you can trigger a digest by calling a scope's
$apply function. If you don't have a scope in your test, you can
inject the $rootScope and call $apply on it. There is also an example
of testing promises in the $q service documentation.
So simply inject $rootScope and use $apply:
petsFactory
.getPetsAsync()
.then(testPets)
.catch(failTest)
.finally(done);
$rootScope.$apply();

Angularjs Mocha test $q promises without $rootScope.$apply

I have this service:
angular.module('domeeApp')
.factory('streamWidget', streamWidgetFactory);
function streamWidgetFactory($q) {
return {
loadContent: function() {
return $q(function(resolve, reject) {
resolve('test');
})
}
}
}
I'm testing it with karma/mocha/chai:
describe('streamWidget', function() {
beforeEach(module('domeeApp'));
var streamWidget;
var $timeout;
beforeEach(inject(function(_$timeout_, _streamWidget_) {
streamWidget = _streamWidget_;
$timeout = _$timeout_;
}));
it('should load new content', function(done) {
streamWidget.loadContent()
.then(function(res) {
expect(res).to.equal('test');
done();
})
.catch(function(){})
$timeout.flush();
});
});
Since $q promises doesn't work well with mocha i'm following this answer, which says to add $timeout.flush() to force the .then method of the promise to be executed.
The problem is, after calling .flush(), all my app wakes up and i start to get this errors from angular-mocks:
Error: Unexpected request: GET /partials/page/view/index.
I know about $httpBackend, but it would be insane to mock ALL the requests my app is making on startup.
Is there a way to make $q promises work with mocha without calling $timeout.flush() or $rootScope.$apply()?
As shown here, chai-as-promised can be used to assert $q promises.
With this setup
chaiAsPromised.transferPromiseness = function (assertion, promise) {
assertion.then = promise.then.bind(promise);
if (!('$$state' in promise))
return;
inject(function ($rootScope) {
if (!$rootScope.$$phase)
$rootScope.$digest();
});
};
digest cycles will be triggered automatically on promise assertions, executing the whole promise chain.
In this case the spec
it('...', () => {
...
expect(...).to.eventually...;
expect(...).to.eventually...;
$rootScope.$digest();
});
can omit $digest() call and become
it('...', () => {
...
expect(...).to.eventually...;
expect(...).to.eventually...;
});
Notice that $q promises are synchronous, they shouldn't be returned from Mocha spec or call done callback.
Here's an alternative strategy that we use because we never actually need $httpBackend, but it sometimes (randomly) fails making requests for templates used by directives (even though those templates are available in $templateCache):
beforeEach(function() {
module('app', function($provide) {
// This is using jasmine, but the idea is the same with mocha.
// Basically just replace $httpBackend with a function that does nothing.
$provide.constant('$httpBackend', jasmine.createSpy('$httpBackend'));
});
});
Of course, if you actually use $httpBackend in other cases, then this won't work, as you'll need it to mock response objects.

How to test an error response in angular's $httpBackend?

We are using angular 1.2.x (we have to due to IE8). We are testing with Karma and Jasmine. I want to test the behavior of my modules, in case the server responds with an error. According to the angular documentation, I should just simply prepare the $httpBackend mock like this (exactly as I'd expect):
authRequestHandler = $httpBackend.when('GET', '/auth.py');
// Notice how you can change the response even after it was set
authRequestHandler.respond(401, '');
This is what I am doing in my test:
beforeEach(inject(function($injector) {
keepSessionAliveService = $injector.get('keepSessionAliveService');
$httpBackend = $injector.get('$httpBackend');
$interval = $injector.get('$interval');
}));
(...)
describe('rejected keep alive request', function() {
beforeEach(function() {
spyOn(authStorageMock, 'get');
spyOn(authStorageMock, 'set');
$httpBackend.when('POST', keepAliveUrl).respond(500, '');
keepSessionAliveService.start('sessionId');
$interval.flush(90*60*1001);
$httpBackend.flush();
});
it('should not add the session id to the storage', function() {
expect(authStorageMock.set).not.toHaveBeenCalled();
});
});
But the test fails, because the mock function is being called and I can see in the code coverage that it never runs into the error function I pass to the §promise.then as second argument.
Apparently I am doing something wrong here. Could it have to with the older angular version we're using?
Any help would be appreciated!
Something like this:
it("should receive an Ajax error", function() {
spyOn($, "ajax").andCallFake(function(e) {
e.error({});
});
var callbacks = {
displayErrorMessage : jasmine.createSpy()
};
sendRequest(callbacks, configuration);
expect(callbacks.displayErrorMessage).toHaveBeenCalled();

Spy doesn't get called using async jasmine and requirejs

I have a setup of AngularJS application that uses RequireJS to download and register services on-demand. I also use Jasmine for testing. I am trying to test if a function is called in the callback of a require() call that is executed inside of a module definition. Look at the following file I have and want to test:
define(['app'], function(app) {
app.registerService('myService', function($injector) {
this.someMethod = function() {
require(['some-other-file'], function() {
var someOtherService = $injector.get('someOtherService');
console.log("first");
someOtherService.bla();
});
};
});
});
I want to test that when myService.someMethod() is called, someOtherService.bla() is also called. This is my test file:
define(['some-file', 'some-other-file'], function() {
//....
it('should test if someOtherService.bla() is called', function(done) {
inject(function($rootScope, myService, someOtherService) {
spyOn(someOtherService, 'bla');
myService.someMethod();
$rootScope.$digest();
setTimeout(function() {
console.log("second");
done();
}, 500);
expect(someOtherService.bla).toHaveBeenCalled();
});
});
});
The console output shows that the statements are executed in the right order:
"first"
"second"
But the test fails, because the spy method never gets called. Why is that and how can I fix it? I very much appreciate your help.

Resources