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;
});
}
Related
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;
}
I found this plnkr link on the web but I need use it with 2 or 3 more ajax calls which doesn't require an argument from the first ajax call. How can I do it with error handling?
var app = angular.module("app", []);
app.service("githubService", function($http, $q) {
var deferred = $q.defer();
this.getAccount = function() {
return $http.get('https://api.github.com/users/haroldrv')
.then(function(response) {
// promise is fulfilled
deferred.resolve(response.data);
return deferred.promise;
}, function(response) {
// the following line rejects the promise
deferred.reject(response);
return deferred.promise;
});
};
});
app.controller("promiseController", function($scope, $q, githubService) {
githubService.getAccount()
.then(
function(result) {
// promise was fullfilled (regardless of outcome)
// checks for information will be peformed here
$scope.account = result;
},
function(error) {
// handle errors here
console.log(error.statusText);
}
);
});
http://plnkr.co/edit/kACAcbCUIGSLRHV0qojK?p=preview
You can use $q.all
var promises=[
$http.get(URL1),
$http.get(URL2),
$http.get(URL3),
$http.get(URL4)
];
$q.all(promises).then(function(response){
console.log('Response of Url1', response[0]);
console.log('Response of Url2', response[1]);
console.log('Response of Url3', response[2]);
console.log('Response of Url4', response[3]);
}, function(error){
});
I have forked your plunkr with $q
First you should make deferred variable local for each ajax call you want to return a promise. So, you have to create 2-3 functions (as many as your ajax calls) and keep them in an array. Then you should use:
$q.all([ajax1,ajax2,ajax3]).then(function(values){
console.log(values[0]); // value ajax1
console.log(values[1]); // value ajax2
console.log(values[2]);}); //value ajax3
example:
function ajax_N() {
var deferred = $q.defer();
http(...).then((response) => {
deferred.resolve(response);
}, (error) => {
deferred.reject(error);
});
return deferred.promise;
}
$q.all([
ajax_1,ajax_2,ajax_3
]).then(function(values) {
console.log(values);
return values;
});
Use $q.all in this case. It will call getAccount and getSomeThing api same time.
var app = angular.module("app", []);
app.service("githubService", function($http, $q) {
return {
getAccount: function () {
return $http.get('https://api.github.com/users/haroldrv');
},
getSomeThing: function () {
return $http.get('some thing url');
}
};
});
app.controller("promiseController", function($scope, $q, githubService) {
function initData () {
$q.all([githubService.getAccount(), githubService.getSomeThing()])
.then(
function (data) {
$scope.account = data[0];
$scope.someThing = data[1];
},
function (error) {
}
);
}
});
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 attempting to implement some http.get() requests in an angular service, returning a promise.
Here is the excerpt from my initial service:
angular.module('dashboard').service('DashboardHTTP', ['$q', '$http', function ($q, $http) {
this.get_info = function () {
var deferred = $q.defer();
$http.get('/dashboard/4/api/info', { cache: true }).success(function (data) {
deferred.resolve(data);
}).error(function () {
deferred.reject('Could Not Complete Request');
});
return deferred.promise;
}
}]);
And here is an excerpt from the portion of my controller where I call the service:
DashboardHTTP.get_info().then(
function (response) {
var resp = response;
$rootScope.dash_info = resp;
},
function (response) {
return 'error';
},
function (response) {
return 'notify';
});
My questions:
I'm struggling with determining how much testing is needed for an interaction like this. I currently have the following test, which is testing at the service level, but I'm wondering if I need to test at the controller level and if so what sort of testing needs to occur?
beforeEach(inject(function (_$httpBackend_, $injector) {
service = $injector.get('DashboardHTTP');
$httpBackend = _$httpBackend_;
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('get_info', function () {
it(' should get info from the url /api/info', function () {
var returnData = { data: 'lots of data' };
$httpBackend.expectGET('/dashboard/4/api/info').respond(returnData);
var returnedPromise = service.get_info();
var result;
returnedPromise.then(function (response) {
result = response;
});
$httpBackend.flush();
expect(result).toEqual(returnData);
});
});
My goal is that I want to set $rootScope.dash_info to the response from the HTTP request made by Service.get_info(). Is my implementation in my controller appropriate? If so, how do I test that the correct data is being passed in at the controller level?
This is probably a partial answer, but here's my input:
Your call is asynchronous, therefore your test should be. Use done.
it(' should get info from the url /api/info', function (done) {
var returnData = { data: 'lots of data' };
$httpBackend.expectGET('/dashboard/4/api/info').respond(returnData);
var returnedPromise = service.get_info();
var result;
returnedPromise.then(function (response) {
result = response;
expect(result).toEqual(returnData);
done();
});
$httpBackend.flush();
});
Also, you do know that http.get returns a promise, right? It has also success and error functions, but it is still a promise.
I am attempting to build a mock service so that my unit tests can verify certain functions are called and updated accordingly. Unfortunately I cannot get this to work.
Im currently getting an error undefined is not a function on this line:
spyOn(statusService, 'getModuleStatus').andCallThrough();
My actual service looks like this:
serviceStatusServices.factory('serviceStatusAppAPIservice', function ($http) {
var serviceStatusAppAPI = {};
serviceStatusAppAPI.getModuleStatus = function () {
return $http({
method: 'JSON',
url: '/settings/getservicestatusandconfiguration'
});
}
serviceStatusAppAPI.setModuleStatus = function (module) {
return $http({
method: 'POST',
url: '/settings/setservicestatusandconfiguration',
data: { moduleId: module.ModuleId, configData: module.ConfigValues }
});
}
return serviceStatusAppAPI;
});
My update function
serviceStatusControllers.controller('serviceStatusController', ['$scope', 'serviceStatusAppAPIservice', '$filter', '$timeout', function ($scope, serviceStatusAppAPIservice, $filter, $timeout) {
$scope.update = function () {
$scope.loading = true;
serviceStatusAppAPIservice.getModuleStatus().then(function (response) {
$scope.modules = $filter('orderBy')(response.data.moduleData, 'ModuleName')
...
My tests look like this
describe('ServiceStatusController', function () {
beforeEach(module("serviceStatusApp"));
var scope;
var statusService;
var controller;
var q;
var deferred;
// define the mock people service
beforeEach(function () {
statusService = {
getModuleStatus: function () {
deferred = q.defer();
return deferred.promise;
}
};
});
// inject the required services and instantiate the controller
beforeEach(inject(function ($rootScope, $controller, $q) {
scope = $rootScope.$new();
q = $q;
controller = $controller('serviceStatusController', {
$scope: scope, serviceStatusAppAPIservice: statusService });
}));
describe("$scope.update", function () {
it("Updates screen", function () {
spyOn(statusService, 'getModuleStatus').andCallThrough();
scope.update();
deferred.resolve();
expect(statusService.getModuleStatus).toHaveBeenCalled();
expect(scope.modules).not.toBe([]);
});
});
});
Also, how do I pass any mock data returned from the service to the caller. Currently in my model I do serviceStatusAppAPI.getModuleStatus(data) then use data.Data to get out the returned JSON.
I assume if you are doing something like this in your ctrl
scope.update = function() {
serviceStatusAppAPIservice.setModuleStatus(url).then(function (data) {
scope.modules = data;
})
};
Service which returns promise
.factory('serviceStatusAppAPI', function($http, $q) {
return {
getModuleStatus: function() {
var defer = $q.defer();
$http({method: 'GET', url: '/settings/getservicestatusandconfiguration'})
.success(function(data, status, headers, config) {
// this callback will be called asynchronously
// when the response is available
defer.resolve(data);
})
.error(function(data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
window.data = data;
});
return defer.promise;
}
};
});
So in you controller you will get data like this
serviceStatusAppAPI.getModuleStatus().then(function (data) {
$scope.modules = $filter('orderBy')(data.moduleData, 'ModuleName')
})
This is how you can run your unit test case
beforeEach(function() {
var statusService = {};
module('myApp', function($provide) {
$provide.value('serviceStatusAppAPIservice', statusService);
});
statusService.modalStatus = {
moduleData: [{ModuleName: 'abc'}, {ModuleName: 'def'}]
};
inject(function ($q) {
statusService.setModuleStatus = function () {
var defer = $q.defer();
defer.resolve(this.modalStatus);
return defer.promise;
};
statusService.getModuleStatus = function () {
var defer = $q.defer();
defer.resolve(this.modalStatus);
return defer.promise;
};
});
});
beforeEach(inject(function ($rootScope, $controller, _$stateParams_) {
scope = $rootScope.$new();
stateParams = _$stateParams_;
controller = $controller;
}));
var myCtrl = function() {
return controller('ServiceStatusController', {
$scope: scope,
});
};
it('should load status', function () {
myCtrl();
scope.update();
scope.$digest();
expect(scope.modules).toBe({
status: 'active'
});
});