How to spyOn chained method call to Restangular in Jasmine? - angularjs

I am using jasmine to write unit tests for an angularJS factory that returns a call to Restangular. Take for example,
function funcToBeTested(params) {
return Restangular.all('/foo').getList(params);
}
It is straightforward to test whether or not all() is being called on Restangular. How can I test whether getList() gets called on the returned Restangular object after all() has returned?
At the moment, I have
beforeEach(function() {
module('ui.router');
module('myService');
module('restangular');
});
describe('funcToBeTested', function() {
beforeEach(function() {
httpBackend.expectGET('/foo').respond([]);
});
it('should call all on Restangular', function() {
spyOn(Restangular, 'all').and.callThrough();
funcToBeTested({});
httpBackend.flush();
expect(Restangular.all).toHaveBeenCalled();
});
});
If I try to follow that same pattern and do something along the lines of
expect(Restangular.all.getList).toHaveBeenCalled();
things stop working. I was not able to find any literature on this. Any help is appreciated.

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.

How do I mock angular's $http with sinon?

I am trying to do a simple mock of angular's $http with sinon in a Mocha test.
But my spy never has any results in it no matter what I try.
searchResource.typeAhead is my function under test. It calls $http based on its arguments and I want to make sure the request is correct.
searchResource.typeAhead returns a promise, but I tried putting the checking code in .then() and it never executes.
suite('Search Resource', function() {
var injector = angular.injector(['cannonball-client-search', 'cannonball-client-core']);
var searchResource = injector.get('SearchResource');
suite('#typeAhead()', function () {
setup(function () {
this.server = sinon.fakeServer.create();
this.server.respondWith('GET',
config.endpoints.search.typeAhead,
[200, {'Content-Type': 'application/json'}, '[{ "id": 12, "comment": "Hey there" }]']);
this.spyhttp = sinon.spy(injector.get('$http'));
});
teardown(function () {
this.server.restore();
});
test('positive', function (done) {
searchResource.typeAhead(
'expl',
[{entityType: 'itementity'}],
[{createdBy: 'Eric'}, {createdBy: 'Tal'}],
10
);
this.server.respond();
expect(this.spyhttp.calledWith({
method: 'get',
url: config.endpoints.search.typeAhead +
'?query=expl&filter=entityType:itementity&orfilter=createdBy:Eric&orfilter=createdBy:Tal&limit=10'
})).to.be.true();
done();
});
});
});
The problem lies outside of Sinon mocking.
If angular.injector is used directly instead of suggested angular.mock.module and angular.mock.inject helpers, the one is on his own with it and his knowledge of Angular injector.
The obvious downside is that the injector won't be torn down automatically after each spec (while it would be when angular.mock.module is used), so all nested specs operate on the same instance of Angular injector.
At this point
var searchResource = injector.get('SearchResource');
SearchResource service instance was already injected with unmocked $http, that's the end of the story. Even if it wouldn't, there's no chance that Angular will ever know that this.spyhttp spy should be used instead of original $http service. Its methods can be spied after the instantiation
sinon.spy($http, 'get');
but not $http function itself.
The strategy for testing with angular.injector may be
var $httpSpy;
var injector = angular.injector([
'cannonball-client-search',
'cannonball-client-core',
function ($provide) {
$provide.decorator('$http', function ($delegate) {
return ($httpSpy = sinon.spy($delegate));
});
}
]);
// injector.get('$http') === $httpSpy;
Notice that this will make Sinon spy on $http function, not on its methods.
If the question is about how Angular mocks should be approached with Sinon, then it's as easy as that. Otherwise this may indicate an XY problem, and the other answer addresses it directly ($httpBackend and the way $http embraces it are there exactly to make the burden of mocking XMLHttpRequest requests non-existent).
Angular was built with testing in mind. The previous comments aren't suggesting that you cannot use sinon to mock out $http, its just not common practice and it definitely won't be as easy to do as it is with $httpBackend.
I would personally only be using sinon to mock any dependencies which don't belong to Angular as such. It's easy enough to provide mock responses with $httpBackend:
$httpBackend.when('GET', '/url').respond({
mock: 'response'
});
Now any request to '/url' with use the mock response object. I'm sure $httpBackend has some other complicated wizardry built in to handle other things like interceptors perhaps?

$httpBackend.flush() method throws Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting

I am trying to unit test my AngularJS application using Karma and Jasmine. I want to mock the $http service. For that, I am using the $httpBackend method. Below is my service that I want to test:
angular.module('MyModule').factory('MyService', function($http, $log, $parse, $q, $timeout, $filter, MyOtherService1, MyOtherService2){
var service = {};
service.getSomething = function(id){
return $http.get('/somePath/subpath/' + id);
}
});
My unit test for this service is:
describe("myTest", function(){
var myService, $httpBackend, scope, mockMyOtherService1, mockMyOtherService2;
var myResponse =
{
foo:'bar'
};
beforeEach(module("MyModule"));
beforeEach(inject(function(_MyService_, $injector){
$httpBackend = $injector.get('$httpBackend');
myService = _MyService_;
scope = $injector.get('$rootScope').$new();
mockMyOtherService1 = $injector.get('MyOtherService1');
mockMyOtherService2 = $injector.get('MyOtherService2');
}));
beforeEach(function(){
//To bypass dependent requests
$httpBackend.whenGET(/\.html$/).respond(200,'');
});
//If I uncomment the below afterEach block, the same error is shown at next line.
/*afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});*/
//This test passes successfully
it("should check if service is instantiated", function () {
expect(myService).toBeDefined();
});
//This test passes successfully
it("should expect dependencies to be instantiated", function(){
expect($httpBackend).toBeDefined();
});
//The problem is in this test
it("should get the getSomething with the provided ID", function() {
$httpBackend.whenGET('/somePath/subpath/my_123').respond(200,myResponse);
var deferredResponse = myService.getSomething('my_123');
//The error is shown in next line.
$httpBackend.flush();
//If I comment the $httpBackend.flush(), in the next line, the $$state in deferredResponse shows that the Object that I responded with is not set i.e. it does not matches the 'myResponse'.
expect(deferredResponse).toEqual(myResponse);
});
});
This is an emergency problem and I need help regarding the same as soon as possible. I will be very grateful for your answer.
The problem was I needed to inject $location in my spec files even though they are not injected in the services. After injection, all worked well! Hope this helps someone who gets stuck in the same situation.
You will get a promise from your service. So change your test code to:
//The problem is in this test
it("should get the getSomething with the provided ID", function (done) {
$httpBackend.expectGET('/somePath/subpath/my_123').respond(200,myResponse);
var deferredResponse = myService.getSomething('my_123');
deferredResponse.then(function (value) {
expect(value.data).toEqual(myResponse);
}).finally(done);
$httpBackend.flush();
});
I've recently had this problem when updating a project from Angular 1.2 to 1.4. The test code looked something like:
it('should call /something', function(){
httpBackend.expectGET('/something').respond(200);
scope.doSomething();
httpBackend.flush();
});
The error was the infdig past 10 iterations. It was caused by invoking the .flush() method. I figured out this is seemingly because there were no pending promises created within doSomething().
Once I added a promise somewhere within doSomething() or inner methods, the infdig problem went away.
I suspect - and this is 100% speculation so don't let it influence your development - this is because httpBackend does some trickery to wait for promises, which maybe involves digesting repeatedly until there's a change. Since there's no promises, there's no changes - infinite digest.

How to resolve promises in AngularJS, Jasmine 2.0 when there is no $scope to force a digest?

It seems that promises do not resolve in Angular/Jasmine tests unless you force a $scope.$digest(). This is silly IMO but fine, I have that working where applicable (controllers).
The situation I'm in now is I have a service which could care less about any scopes in the application, all it does it return some data from the server but the promise doesn't seem to be resolving.
app.service('myService', function($q) {
return {
getSomething: function() {
var deferred = $q.defer();
deferred.resolve('test');
return deferred.promise;
}
}
});
describe('Method: getSomething', function() {
// In this case the expect()s are never executed
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
});
done();
});
// This throws an error because done() is never called.
// Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
it('should get something', function(done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
expect(1).toEqual(2);
done();
});
});
});
What is the correct way to test this functionality?
Edit: Solution for reference. Apparently you are forced to inject and digest the $rootScope even if the service is not using it.
it('should get something', function($rootScope, done) {
var promise = myService.getSomething();
promise.then(function(resp) {
expect(resp).toBe('test');
});
$rootScope.$digest();
done();
});
You need to inject $rootScope in your test and trigger $digest on it.
there is always the $rootScope, use it
inject(function($rootScope){
myRootScope=$rootScope;
})
....
myRootScope.$digest();
So I have be struggling with this all afternoon. After reading this post, I too felt that there was something off with the answer;it turns out there is. None of the above answers give a clear explanation as to where and why to use $rootScope.$digest. So, here is what I came up with.
First off why? You need to use $rootScope.$digest whenever you are responding from a non-angular event or callback. This would include pure DOM events, jQuery events, and other 3rd party Promise libraries other than $q which is part of angular.
Secondly where? In your code, NOT your test. There is no need to inject $rootScope into your test, it is only needed in your actual angular service. That is where all of the above fail to make clear what the answer is, they show $rootScope.$digest as being called from the test.
I hope this helps the next person that comes a long that has is same issue.
Update
I deleted this post yesterday when it got voted down. Today I continued to have this problem trying to use the answers, graciously provided above. So, I standby my answer at the cost of reputation points, and as such , I am undeleting it.
This is what you need in event handlers that are non-angular, and you are using $q and trying to test with Jasmine.
something.on('ready', function(err) {
$rootScope.$apply(function(){deferred.resolve()});
});
Note that it may need to be wrapped in a $timeout in some case.
something.on('ready', function(err) {
$timeout(function(){
$rootScope.$apply(function(){deferred.resolve()});
});
});
One more note. In the original problem examples you are calling done at the wrong time. You need to call done inside of the then method (or the catch or finally), of the promise, after is resolves. You are calling it before the promise resolves, which is causing the it clause to terminate.
From the angular documentation.
https://docs.angularjs.org/api/ng/service/$q
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));

Unit Testing Factories with nested promises in AngularJS

I'm building an app heavy on JavaScript. I want to unit test my app using Jasmine. At this time, I'm trying to figure out how to write some code that another developer wrote. This code is a factory with some promise action in it. The code looks like the following:
.factory('myFactory', function ($injector, someOtherFactory) {
return function(promise) {
return promise.then(null, function(res) {
if(res.result === -1) {
promise = someOtherFactory.tryAgain('key', function another() {
return $injector.get('$http')(response.config);
});
}
return promise;
});
};
})
It's like a testing turducken. I have no idea how to test this thing. Currently, I have the following setup:
describe('Factory: myFactory', function () {
it('should contain the service', inject(function (myFactory) {
expect(myFactory).toBeDefined();
}));
});
I know this isn't anything. However, I really have no idea how to test a nested promise as shown above. Any pointers?
Thanks

Resources