I am trying to test $http calls inside services which, upon $http response, store the response data in service itself (does not return response to controller). Most examples I found (even AngularJs documentation) are testing $http calls in controller. For ex:
app.factory('dataFactory', function($http){
return {
getData: function(){
return $http.get('https://some-url');
}
}
});
app.controller('MainCtrl', function($scope, dataFactory, $http) {
$scope.name = 'World';
dataFactory.getData().success(function(data){
$scope.data = data;
})
});
The unit test for this code is:
describe('with httpBackend', function() {
beforeEach(inject(function($controller, $rootScope, $httpBackend) {
$scope = $rootScope.$new();
$httpBackend.when('GET', 'https://some-url')
.respond({things: 'and stuff'});
MainCtrl = $controller('MainCtrl', { $scope: $scope });
$httpBackend.flush();
}));
it('should set data to "things and stuff"', function() {
expect($scope.data).toEqual({
things: 'and stuff'
});
});
});
But in my services, I am making the calls in following fashion:
app.service('dataService', function($http) {
var self = this;
this.getData = function() {
$http.get('https://some-url/')
.then(
function success(response) {
self.data = response.data
},
function error(msg) {
console.log(msg);
}
);
};
});
For this, I will need to unit test the service and not the controller.
EDIT: Below is the unit-test I've written (which is passing, but not sure it's the correct approach):
describe('.getData()', function() {
beforeEach(inject(function($httpBackend) {
$httpBackend.when('GET', 'https://some-url/')
.respond({data: 'sample data'});
dataService.getData();
$httpBackend.flush();
}));
it('should store data correctly', function() {
expect(dataService.data).toEqual({data: 'sample data'});
});
});
Need some help regarding the unit-testing approach I should follow to test services with $http calls (and store data).
Related
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;
});
}
I am trying to test an AngularJS service carService, but the $httpBackend does not seem to work.
//carService
angular.module('services').factory('carService',
function($http) {
return {
getTypes: function() {
return $http.get('/api/cars/types');
}
};
});
Can anybody explain why the response is null?
describe("Services", function () {
beforeEach(module("app.services"));
describe("Car services", function () {
var service, $httpBackend;
beforeEach(inject(function($injector) {
service = $injector.get('carService');
$httpBackend = $injector.get('$httpBackend');
$httpBackend.when('GET', "/api/cars/types").respond(["Toyota", "Honda", "Tesla"]);
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('getTypes - should return 3 car manufacturers', function () {
service.getTypes().then(function(response) {
expect(response.length).toEqual(3); //the response is null
});
$httpBackend.flush();
});
});
});
Try this:
expect(response.data.length).toEqual(3);
The response object returned by a $http request has the response data within the data property (docs).
I have created an angular service which I am testing without any issue, I then started trying to inject a dependency into the tests when I started having an issue.
I want to make sure that a function in the dependency has been called but it's coming back undefined.
Here's my service:
angular.module('enigmaApp.services', [])
.factory('auth', function($window, $http, SS){
var auth = {};
auth.register = function(user){
return $http.post('/register', user)
.success(function(data){
if(data.token){
SS.setObj('appToken', data.token);
}
});
};
return auth;
});
My test:
describe('Auth Service Tests', function () {
var $httpBackend, auth, defer, registerReqHandler, setObjSpy, SS, SSMock, user;
beforeEach(module('enigmaApp'));
beforeEach(function () {
// Create spies
setObjSpy = jasmine.createSpy('setObj');
SSMock = {
setObj: setObjSpy
}
SS = SSMock;
module(function ($provide) {
$provide.value('SS', SSMock);
});
});
beforeEach(inject(function (_$httpBackend_, $injector, $q) {
$httpBackend = _$httpBackend_;
defer = $q.defer();
registerReqHandler = $httpBackend.when('POST', '/register').respond(defer.promise);
auth = $injector.get('auth');
}));
afterEach(function () {
$httpBackend.flush();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('auth.register(user)', function () {
beforeEach(function () {
user = {
email: 'bwayne#wayneenterprise.com',
first_name: 'Bruce',
last_name: 'Wyane',
password: 'password123'
};
});
it('should call SS.setObj on successful registration', function () {
$httpBackend.expectPOST('/register').respond({ token: 'fakeToken' });
auth.register(user);
expect(SS.setObj).toHaveBeenCalled();
});
});
When I run the tests I get a failed test that says "Expected spy setObj to have been called." Any idea on what I'm doing wrong here? I thought I set up a mock for SS.setObj and provided it to the module,
One problem you have is that it seems you aren't actually returning your auth object in your factory:
angular.module('enigmaApp.services', [])
.factory('auth', function($window, $http, SS){
var auth = {};
auth.register = function(user){
return $http.post('/register', user)
.success(function(data){
if(data.token){
SS.setObj('appToken', data.token);
}
});
};
// need to return auth here...
return auth;
});
update
Move your flush call into your it block
it('should call SS.setObj on successful registration', function () {
$httpBackend
.expectPOST('/register')
.respond({ token: 'fakeToken' });
auth.register(user);
$httpBackend.flush();
expect(SS.setObj).toHaveBeenCalled();
});
afterEach blocks are for clean up. Jasmine assumes your test is over by that point so your flush() call won't have any effect.
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 trying to test a simple service for learning purposes..However; I can't figure out how it must be done:
service:
.factory('myService', function($http) {
var myService = {
async: function() {
var promise = $http.get('test.json').then(function (response)
{
return response.data;
});
return promise;
}
};
return myService;
});
controller:
myService.async().then(function(d) {
$scope.data = d;
$scope.e = $scope.data.txt;
});
test:
'use strict';
describe("myService", function(){
beforeEach(module("testingExpApp"));
var myService,
$httpBackend;
beforeEach(inject(function(myService, _$httpBackend_){
myService = myService;
$httpBackend = _$httpBackend_;
}));
describe("get", function(){
it('should return test.json data', function () {
var url = "../mock/test.json";
var x = $httpBackend.expectGET(url).respond(200, 'txt from json');
// flush response
$httpBackend.flush();
expect(x).toBe('txt from json');
});
});
});
I get 'no pending request to flush!'
I just want to test that myservice.get() get the test.json file data..I have tried everything but can't get it working..
Any tips?
Thanks a lot in advance!
What I was missing is to call service function
it had to be:
it('should return test.json data', function () {
var url = "../../mock/test.json";
$httpBackend.expectGET(url).respond(200, 'data from test.json');
//Execute service func here..
myServiceFunc.async().then(function(result) {
console.log(result);
expect(result).toEqual('data from test.json');
});
$httpBackend.flush();
});