angular.module('mine', []).factory('MyFactory', ['$http', '$q', function
MyFactory($http, $q) {
return {
getData: function() {
var deferred = $q.defer(),
url = "http://...";
$http.jsonp(url)
.then(
function(response) {
deferred.resolve(response.data);
},
function(error) {
return $q.reject('Error retrieving data');
}
);
return deferred.promise;
}
};
}
]);
function MyController(MyFactory) {
var self = this;
self.getData = function() {
MyFactory.getData().then(
function(result) {
self.contacts = result;
},
function(error) {
console.log('Error retrieving data: ', error);
}
);
};
}
I am trying to unit test if data from factory go correctly to controller. Here is my unit testing code using Jasmine:
describe('component', () => {
let $componentController,contactsList,ctrl,$q,$rootScope;
beforeEach(angular.mock.module('mine'));
beforeEach(inject((_$componentController_,_MyFactory_, _$q_, _$rootScope_) => {
$componentController = _$componentController_;
ctrl = $componentController('myComponent',null);
$q = _$q_;
contactsList = _MyFactory_;
$rootScope = _$rootScope_;
}));
it('should ... ', function() {
spyOn(contactsList, "getData").and.returnValue(
$q.when({
message: 'awesome message'
}));
ctrl.getData();
$rootScope.$apply();
expect(ctrl.contacts.message).toBe('awesome message');
});
});
However, I am doing something wrong and I am getting the error: I am getting the following error: Possibly unhandled rejection: Error retrieving data thrown.I guess the problem lies on spy method. I'd appreciate any help.
Related
Here is part of my code:
angular.module('mine',[]).factory('MyFactory', ['$http','$q', function
MyFactory($http,$q) {
return {
getData: function() {
var deferred = $q.defer(),
url = "http://...";
$http.jsonp(url)
.then(
function (response) {
deferred.resolve(response.data);
},
function (error) {
return $q.reject('Error retrieving data');
}
);
return deferred.promise;
}
};
}]);
function MyController(MyFactory) {
var self = this;
self.getData= function( ) {
MyFactory.getData().then(
function(result) {
self.contacts = result;
},
function(error) {
console.log('Error retrieving data: ', error);
}
);
};
self.getData();
}
angular.module('mine').component('myComponent', {
templateUrl: '..',
controller: MyController
});
I am trying to unit test if data from factory go correctly to controller. Here is my unit testing code using Jasmine:
describe('component', () => {
let $componentController,contactsList,ctrl,$q,$rootScope;
beforeEach(angular.mock.module('mine'));
beforeEach(inject((_$componentController_,_MyFactory_, _$q_, _$rootScope_) => {
$componentController = _$componentController_;
ctrl = $componentController('myComponent',null);
$q = _$q_;
contactsList = _MyFactory_;
$rootScope = _$rootScope_;
}));
it('should ... ', function() {
spyOn(contactsList, "getData").and.returnValue(
$q.when({
message: 'awesome message'
}));
ctrl.getData();
$rootScope.$apply();
expect(ctrl.contacts.message).toBe('awesome message');
});
});
For some reason, the above test is not running; I am getting the following error: Possibly unhandled rejection: Error retrieving data thrown. Do you have any idea why? What is wrong?
You should inject MyFactory to $componentController as second argument in your tests. So instead of this:
ctrl = $componentController('myComponent',null);
use this:
ctrl = $componentController('myComponent', {
MyFactory: _MyFactory_
});
angular.module('mine', []).factory('MyFactory', ['$http', '$q', function
MyFactory($http, $q) {
return {
getData: function() {
var deferred = $q.defer(),
url = "http://google.com";
$http.jsonp(url)
.then(
function(response) {
deferred.resolve(response.data);
},
function(error) {
return $q.reject('Error retrieving data');
}
);
return deferred.promise;
}
};
}
]);
function MyController(MyFactory) {
this.getData = function() {
MyFactory.getData().then(
function(result) {
this.contacts = result;
}.bind(this),
function(error) {
console.log('Error retrieving data: ', error);
}
);
};
this.getData();
}
angular.module('mine').component('myComponent', {
controller: MyController
});
describe('component', () => {
let $componentController, contactsList, $q, $rootScope, ctrlFactory;
beforeEach(angular.mock.module('mine'));
beforeEach(inject((_$componentController_, _MyFactory_, _$q_, _$rootScope_) => {
$componentController = _$componentController_;
ctrlFactory = () => {
return $componentController('myComponent', null, {
MyFactory: _MyFactory_
});
}
$q = _$q_;
contactsList = _MyFactory_;
$rootScope = _$rootScope_;
}));
it('should ... ', function() {
const message = 'awesome message'
spyOn(contactsList, "getData").and.returnValue(
$q.when({
message
}));
const ctrl = ctrlFactory()
ctrl.getData();
$rootScope.$apply();
expect(ctrl.contacts.message).toBe(message);
});
});
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.11/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.11/angular-mocks.js"></script>
I would like to test $resource success and error callbacks in my controller. I don’t want to use $httpBackend as that would be used to test the data service. It seems that there is no way to do it though - the only solution I have found is to use promises instead which I could either resolve or reject. Does this sound right? Anyway, here is what I have at the moment - currently it only tests whether the $resource get() is called:
The controller:
angular
.module('myModule')
.controller('MyCtrl', MyCtrl);
MyCtrl.$inject = [
'dataService'
];
function MyCtrl(
dataService
) {
var vm = this;
vm.getData = getData;
function getData() {
dataService.getData().get(function(response) {
// stuff to test
},
function(error) {
// stuff to test
});
}
The test:
describe('Controller: MyCtrl', function() {
var MyCtrl;
var rootScope;
var scope;
var dataServiceMock = {
getData: jasmine.createSpy('getData')
};
beforeEach(function()
inject(function($controller, $rootScope) {
rootScope = $rootScope;
scope = $rootScope.$new();
MyCtrl = $controller('MyCtrl as vm', {
dataService: dataServiceMock,
});
});
});
describe('vm.getData()', function() {
beforeEach(function() {
dataServiceMock.getData.and.returnValue({
get: jasmine.createSpy('get')
});
});
it('gets the data', function() {
scope.vm.getData();
expect(dataServiceMock.getData().get).toHaveBeenCalled();
});
});
});
Try this
function getData (query) {
var deferred = $q.defer();
var httpPromise = $resource(query,{},{
post:{
method:"GET",
isArray: false,
responseType: "json"
}
});
httpPromise.post({}, {},
function(data) {
try {
var results = {}
results.totalItems = data.response;
deferred.resolve(results);
} catch (error) {
console.log(error.stack);
deferred.reject();
}
},
function(error) {
deferred.reject();
}
);
return deferred.promise;
}
Can someone please tell me the best way to run tests on my controller function getData and the factory function too. I've very confused and don't know where to start. How would you write tests for the code below?
myApp.controller('myController', ['$scope', 'myFactory', function ($scope, myFactory) {
$scope.getData = function(id) {
var promise = myFactory.GetData('/dta/GetData?Id=' + id);
promise
.then(function (success) {
$scope.result = success;
}, function (error) {
$scope.error = true;
});
}
});
myApp.factory('myFactory', ['$http', function ($http) {
return {
GetData: function (url) {
return $http.get(url)
.then(function (response) {
return response.data;
}, function (error) {
return error;
});
}
}
}]);
You'll want to test each component in isolation (that's what unit tests are for). So something like this for the controller
describe('myController test', () => {
let scope, myFactory;
beforeEach(() => {
myFactory = jasmine.createSpyObj('myFactory', ['GetData']);
module('your-module-name');
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
$controller('myController', {
$scope: scope,
myFactory: myfactory
});
});
});
it('getData assigns result on success', inject(function($q) {
let id = 1, success = 'success';
myFactory.GetData.and.returnValue($q.when(success));
scope.getData(id);
expect(myFactory.GetData).toHaveBeenCalledWith('/dta/GetData?Id=' + id);
scope.$digest(); // resolve promises
expect(scope.result).toBe(success);
}));
it('getData assigns error on rejections', inject(function($q) {
myFactory.GetData.and.returnValue($q.reject('error'));
scope.getData('whatever');
scope.$digest();
expect(scope.error).toEqual(true);
}));
});
For your factory, you would create a separate describe and inject and configure $httpBackend. There are plenty of example in the documentation.
FYI, you should omit the error handler in your factory, ie
return $http.get(url).then(response => response.data);
or if you don't like ES2015
return $http.get(url).then(function(response) {
return response.data;
});
as you are currently converting a failed request into a successful promise.
In fact, I'd go a bit further to make your GetData factory more useful than a mere $http wrapper
GetData: function(id) {
return $http.get('/dta/GetData', {
params: { Id: id }
}).then(function(res) {
return res.data;
});
}
Here is my factory in my app.js
app.factory('userInfoFacrory', ['$http' , "$q", function($http,$q){
return {
getNames:function(){
var differed = $q.defer();
$http.get("http://localhost/ang/api/v1/users/names")
.success(function(data) {
differed.resolve(data);
}).error(function(msg) {
differed.reject(msg);
});
return differed.promise;
}
}
}])
I use this factory in my controller like bellow , and it works fine :
app.controller('mainController', ['$scope','userInfoFacrory','$log', function($scope,userInfoFacrory,$log){
var promise = userInfoFacrory.getNames();
promise.then(function (data) {
$log.info(data); // I get my data correctly here
}, function (msg) {
$log.error(data);
})
}])
And here , I've tried to write a test unit , with karma-jasmine
describe('userInfoFacrory', function() {
var factory ,$rootScope,$scope,$q,onTaskComplete , promise;
beforeEach(function() {
module("testApp");
inject(function ($injector) {
$q = $injector.get("$q");
factory = $injector.get("userInfoFacrory");
$rootScope = $injector.get("$rootScope");
$scope = $rootScope.$new();
promise = factory.getNames(); // this function comes from my factory which returns a promise
});
});
it('should return a promise', function() {
// This test will pass , so no error so far
expect(typeof promise.then).toEqual('function');
});
});
But I can't figure out how to test to so if my promise will have my data ( that comes from my api ) or not , any suggestion would be appreciated.
thanks
it('should return a promise resolved with the http response data if the http request is successful', inject(function($httpBackend) {
var expectedData = 'fake data';
$httpBackend.expectGET('http://localhost/ang/api/v1/users/names').respond(expectedData);
var promise = factory.getNames();
var actualData;
promise.then(function(result) {
actualData = result;
});
$httpBackend.flush();
expect(actualData).toEqual(expectedData);
}));
it('should return a promise rejected with the http response data if the http request is in error', inject(function($httpBackend) {
var expectedData = 'fake data';
$httpBackend.expectGET('http://localhost/ang/api/v1/users/names').respond(400, expectedData);
var promise = factory.getNames();
var actualData;
promise.catch(function(result) {
actualData = result;
});
$httpBackend.flush();
expect(actualData).toEqual(expectedData);
}));
Working plunkr: http://plnkr.co/edit/NfO6KXWLs1QT5HG8MK0J?p=preview
Note that your code is correct, but doesn't really leverage the chaining capabilities of promises. It could simply be written as
getNames: function() {
return $http.get("http://localhost/ang/api/v1/users/names")
.then(function(response) {
return response.data;
}, function(response) {
return $q.reject(response.data);
});
};
}
Working plunkr: http://plnkr.co/edit/C5x8wRYCQ0wetjozEd0a?p=preview
I'm trying to test my response interceptor but I have a hard time figuring out how to mock the $window object. Here is my interceptor code :
'use strict';
angular.module('Domain.handlers')
.config(function($httpProvider) {
$httpProvider.responseInterceptors.push('UnauthorizedInterceptor');
})
.factory('UnauthorizedInterceptor', function($q, $injector, $window, ENV) {
return function(promise) {
var success = function(response) { return response; };
var error = function(response) {
if (response.status === 401) {
$window.location.href = ENV.account + '/oauth/authorize?client_id=' + ENV.clientId + '&redirect_uri=' + ENV.app + '/oauth/callback&response_type=token';
}
return $q.reject(response);
};
return promise.then(success, error);
};
});
And here is my spec :
'use strict';
describe('Domain.handlers.response', function() {
var UnauthorizedInterceptor,
httpProvider,
$httpBackend,
$http,
token = '123456789';
beforeEach(module('Domain.handlers', function($httpProvider) {
httpProvider = $httpProvider;
}));
beforeEach(inject(function(_UnauthorizedInterceptor_, _$httpBackend_, _$http_) {
UnauthorizedInterceptor = _UnauthorizedInterceptor_;
$httpBackend = _$httpBackend_;
$http = _$http_;
}));
describe('UnauthorizedInterceptor', function() {
it('should be defined', function() {
expect(UnauthorizedInterceptor).toBeDefined();
});
describe('HTTP status', function() {
describe('is 200 OK', function() {
it('should return a 200 status', function() {
$httpBackend.expectGET('http://api.domain.com/clients').respond(200, {});
$http.get('http://api.domain.com/clients');
$httpBackend.flush();
});
});
describe('is 401 Unauthorized', function() {
it('should redirect to accounts.domain.com', inject(function($window) {
$httpBackend.expectGET('http://api.domain.com/clients').respond(401, {});
$http.get('http://api.domain.com/clients');
expect($window.location.href).toEqual('http://accounts.domain.com/oauth/.....');
$httpBackend.flush();
}));
});
});
});
});
I've got a : Expected 'http://localhost:8080/context.html' to equal 'http://accounts.domain.com/oauth/.....'. Any help on how to mock properly the $window object or more generally how to test a 401 + redirection case?
You should structure your interceptor definition using the more recent syntax. Your URL construction should also be in a service so that it can easily be mocked in tests.
.factory('UnauthorizedInterceptor', function($q, $window, OtherService) {
var service = {
responseError: handleUnauthorized
};
return service;
function handleUnauthorized(rejection) {
if (rejection.status === 401) {
$window.location.href = OtherService.getUnauthorizedRedirectURL();
}
return $q.reject(rejection);
}
});
Doing so will let you test it just like any other factory without having to worry about the internal implementations of $http interceptors, or having to mock responses with $httpBackend.
describe('Domain.handlers.response', function() {
var $window,
UnauthorizedInterceptor,
OtherService,
redirectUrl = 'someUrl';
beforeEach(module('Domain.handlers'));
beforeEach(function () {
$window = { location: { href: null } };
module(function($provide) {
$provide.value('$window', $window);
});
});
beforeEach(inject(function(_UnauthorizedInterceptor_, _OtherService_) {
UnauthorizedInterceptor = _UnauthorizedInterceptor_;
OtherService = _OtherService_;
spyOn(OtherService, 'getUnauthorizedRedirectURL').andReturn(redirectUrl);
}));
describe('UnauthorizedInterceptor', function() {
it('should be defined', function() {
expect(UnauthorizedInterceptor).toBeDefined();
});
it('should have a handler for responseError', function () {
expect(angular.isFunction(UnauthorizedInterceptor.responseError)).toBe(true);
});
describe('when HTTP 401', function () {
beforeEach(function () {
var rejection = { status: 401 };
UnauthorizedInterceptor.responseError(rejection);
});
it('should set window location', function () {
expect($window.location.href).toBe(redirectUrl);
});
});
describe('when not HTTP 401', function () {
beforeEach(function () {
var rejection = { status: 500 };
UnauthorizedInterceptor.responseError(rejection);
});
it('should not set window location', function () {
expect($window.location.href).not.toBe(redirectUrl);
});
});
});
});
Here is an example of the responseError interceptor and the corresponding jasmine spec.
angular.module('interceptorDemo').factory('redirectInterceptor', ['$q', '$window', function($q, $window) {
'use strict';
function handleUnauthorizedAccess(config) {
if (401 === config.status) {
$window.location = '/signIn/';
}
return $q.reject(config);
}
return {
responseError: handleUnauthorizedAccess
};
}]);
The interceptor intercepts the ajax request, if the request is failed, then if the status code is 401 then user is redirected to signIn page.
Jasmine spec for the same is:
describe('redirectInterceptor specs', function() {
var redirectInterceptor, $q;
beforeEach(module('interceptorDemo'));
beforeEach(function() {
$window = {
location: {
href: null
}
};
module(function($provide) {
$provide.value('$window', $window);
});
});
beforeEach(inject(function(_redirectInterceptor_, _$q_) {
redirectInterceptor = _redirectInterceptor_;
$q = _$q_;
spyOn($q, 'reject');
}));
describe('redirectInterceptor specs', function() {
it('should redirect to signIn page for unauthorized access', function() {
var response = {
status: 401,
config: {}
};
var promise = redirectInterceptor.responseError(response);
expect($window.location).toBe('/singIn/');
expect($q.reject).toHaveBeenCalled();
});
it('should not redirect to signIn page for error code other than unauthorized access', function() {
var response = {
status: 404,
config: {}
};
var promise = redirectInterceptor.responseError(response);
expect($window.location).toEqual({
href: null
});
expect($q.reject).toHaveBeenCalled();
});
});
});
We have spied on the $q so we can also test that the reject is called for the 401 error.