I'm writing a test to simply check that an endpoint is being called by a service. I'm using $httpBackend to mock this response, however the test is still hitting the real backend endpoint, and not using the $httpBackend mock.
Service
function getOptions() {
return $http.get('/apiv1/something').then(function (data) {
return [{
name: "eGo C"
image: "http://www.google.co.uk/logo.png"
}];
};
}
Test
describe('product service', function () {
var $httpBackend, service, $rootScope;
var failTest = function (error) {
expect(error).toBeUndefined();
};
beforeEach(module('appName'));
beforeEach(inject(function (_$httpBackend_, productService, _$rootScope_) {
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
service = productService;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('getOptions', function () {
it('should make a call to the backend', function () {
var handler,
myThings,
errorStatus,
mockResponce;
mockResponce = [
{
name: 'evod kit',
img: 'img.jpg'
}
];
myThings = [];
errorStatus = '';
handler = {
success: function(data) {
console.log("success " + data);
myThings = data;
},
error: function(data) {
console.log("error " + data);
errorStatus = data;
}
};
spyOn(handler, 'success').and.callThrough();
spyOn(handler, 'error').and.callThrough()
$httpBackend.when('GET', '/apiv1/something').respond(mockResponce);
service.getOptions().then(handler.success, handler.error);
$httpBackend.flush();
console.log("MY THINGS ", myThings);
// PROBLEM!!! == FROM THE SERVICE -> [{name: "eGo C"
// image: "http://www.google.co.uk/logo.png"
// }]
});
});
try this instead :
let $rootScope
beforeEach(angular.mock.inject(function( _$httpBackend_, _$rootScope_){
$rootScope = _$rootScope_
$rootScope.httpbackend = _$httpBackend_
}
Essentially.. I'm an idiot. In the service I was returning a hardcoded array as the backend is yet to be built. $httpBackend was doing its job and returning the new data, but of course, it was not getting returned by the service. Sigh. I need a beer.
Related
I wrote some unit tests for an angular factory that mock the response with a fake promise using $q.resolve({}). I'm now writing the controller test and want to call the factory from the controller and assign the resolved mock factory response to the controller scope (vm), and verify the data. Is this the proper way to test a factory from within a controller?
factory test
describe('getVICs()', function() {
var vicFactory, $q, $httpBackend, result;
beforeEach(module('vicModule'));
beforeEach(inject(function(_vicFactory_, _$q_, _$httpBackend_) {
vicFactory = _vicFactory_;
$q = _$q_;
$httpBackend = _$httpBackend_;
}));
beforeEach(function() {
result = {};
spyOn(vicFactory, 'getVICs').and.callThrough(function() {
var deferred = $q.defer();
return deferred.promise;
});
});
it('should be defined', function() {
expect(vicFactory.getVICs()).toBeDefined();
});
it('should return list of vics', function() {
var vicList = {
"results": [
{
"version": 5
},
{
"version": 1
}
]
};
var qs = '?sort=version&dir=DESC&page=1&limit=10&status=ACTIVE';
$httpBackend.expectGET('/path/to/my/api' + qs).respond(200, vicList);
$httpBackend.whenGET('http://localhost:3000/path/to/my/api' + qs).respond(200, $q.when(vicList));
vicFactory.getVICs(qs)
.then(function(response) {
result = response;
});
// flush pending HTTP requests
$httpBackend.flush();
expect(result.data.results).toBeDefined();
});
});
factory
function getVICs(query) {
return $http.get('/path/to/my/api' + query)
.then(function(response) {
return response;
});
}
controller test
describe('vicController', function() {
var $controller, vicController, vicFactory, $q, $scope, $httpBackend, deferred;
var vicList = {
"results": [
{
"version": 5
},
{
"version": 1
}
]
};
var qs = '?sort=version&dir=DESC&page=1&limit=10&status=ACTIVE';
beforeEach(module('vicModule'));
beforeEach(inject(function(_$controller_, _vicFactory_, _$q_, _$rootScope_, _$httpBackend_, _$httpParamSerializer_) {
$controller = _$controller_;
vicFactory = {
getVICs: function () {
deferred = $q.defer();
return deferred.promise;
}
};
$q = _$q_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
spyOn(vicFactory, 'getVICs').and.callThrough();
vicController = $controller('vicController', {vicFactory: vicFactory});
deferred.resolve(vicList);
}));
it('should be defined', function() {
expect(vicController).toBeDefined();
});
it('should call getVICs() on load', function() {
$httpBackend.expectGET(''/path/to/my/api?' + qs).respond(200, vicList);
$scope.$digest();
expect(vicFactory.getVICs).toHaveBeenCalledWith(qs);
expect($scope.vicList).toEqual(vicList);
});
});
controller
angular.module('vicModule')
.controller('vicController', vicController);
vicController.$inject = [
'vicFactory'
];
function vicController(vicFactory) {
var vm = this;
var qs = '?sort=version&dir=DESC&page=1&limit=10&status=ACTIVE';
vicFactory.getVICs(qs)
.then(function(response) {
console.log('vic controller success');
console.log(response);
vm.vicList = response;
}, function(response) {
console.log('vic controller error');
console.log(response);
});
}
Fixed
Got this working by injecting $q and $scope, setting vicFactory in beforeEach, updating spy, and then resolving the deferred with the expected data set after controller initialization. The controller test spec has been updated, hope this help someone.
Hello I am new to AngularJS. I am trying to test angular service ForumService. For that I have to mock the response that I get while calling the service method. I did something but I don't know if its right or not. I am getting an error
"ForumService should use Function FAILED"
"TypeError: service.getForums is not a function"
This is the service that I am testing
(function() {
'use strict';
function ForumService($q, $http, config, Forum) {
var service = {};
/**
* Sends a GET request to backend API for all forums.
*
* #return {Promise}
*/
service.getForums = function(onSuccessCallback, onErrorCallback) {
$http.get(config.apiBaseUrl + '/api/forums')
.then(
function handleSuccess(response) {
onSuccessCallback(response.data.data);
},
function handleError(response) {
onErrorCallback(response.data.error);
}
);
};
/**
* Sends a GET request to backend API for all forums.
*
* #return {Promise}
*/
service.getForumsPromise = function() {
var q = $q.defer();
$http.get(config.apiBaseUrl + '/api/forums')
.then(
function success(response) {
q.resolve(buildForumArray(response.data.data));
},
function error(response) {
q.reject(response.data.error);
}
);
return q.promise;
};
function buildForumArray(data) {
var forumArray = [];
data.forEach(function(forumData) {
forumArray.push(new Forum(forumData));
});
return forumArray;
}
return service;
}
ForumService.$inject = [
'$q',
'$http',
'config',
'Forum'
];
angular
.module('app.services')
.factory('ForumService', ForumService);
})();
The following is the code where I am testing the first method service.getForums()
'use strict';
describe('ForumService', function() {
var service, $q, config, httpBackend;
beforeEach(module('app.services'));
beforeEach(module('app.models'));
beforeEach(module(function($provide) {
$provide.service('config', function() {
this.apiBaseUrl = "localhost";
});
$provide.service('ForumService', function() {
this.constructor = jasmine.createSpy('ForumService')
});
$provide.service('Forum', function() {
this.constructor = jasmine.createSpy('Forum')
});
}));
//2.
beforeEach(function() {
inject(function($injector) {
service = $injector.get('ForumService');
httpBackend = $injector.get('$httpBackend');
$q = $injector.get('$q');
});
});
// 5. make sure no expectations were missed in your tests.
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('should use Function', function() {
var returnData = [
{
id: 1,
name: "Programming Questions",
description: "Please post all Questions you have in regards to programming here"
}, {
id: 2,
name: "OOP",
description: "Object Oriented Programming"
}
];
console.info('foo');
httpBackend.when('GET', 'localhost/api/forums').respond(200, returnData);
service.getForums().then(function(response) {
console.info(response); // to see the response
expect(response.data.id).toBe(1);
expect(response.data.name).toBe("Programming Questions");
expect(response.data.description).toBe("Please post all Questions you have in regards to programming here");
});
httpBackend.flush();
});
});
And this is my model class
(function() {
'use strict';
angular
.module('app.models')
.factory('Forum', Forum);
Forum.$inject = [];
function Forum() {
/**
* Forum prototype (constructor function).
*
* #param data
* #constructor
*/
function Forum(data) {
var self = this;
if (angular.isDefined(data)) {
self.id = data.id;
self.name = data.name;
self.description = data.description;
} else {
self.id = 0;
self.name = '';
self.description = '';
}
}
return Forum;
}
})();
There were are lot of issues with the test cases and the code you've written. Fixed a few of them here
Do read the inline comments for the explainations
This should be the definition of you getForums method:
service.getForums = function(onSuccessCallback, onErrorCallback) {
$http.get(config.apiBaseUrl + '/api/forums').then(function handleSuccess(response) {
// You'll get the data inside response.data and not response.data.data
// onSuccessCallback(response.data.data);
onSuccessCallback(response.data);
}, function handleError(response) {
onErrorCallback(response.data.error);
});
};
If you really wanted to return promise from getForumsPromise method, you could have simply done this:
service.getForumsPromise = function() {
return $http.get(config.apiBaseUrl + '/api/forums');
};
$http.get returns a promise anyways.
And this is how you should be writing the test case:
'use strict';
describe('ForumService', function() {
var returnData = [{
id: 1,
name: "Programming Questions",
description: "Please post all Questions you have in regards to programming here"
}, {
id: 2,
name: "OOP",
description: "Object Oriented Programming"
}];
//Below line of code is not required.
// var service, $q, config, httpBackend;
beforeEach(module('app.services'));
beforeEach(module('app.models'));
beforeEach(module(function($provide) {
$provide.service('config', function() {
this.apiBaseUrl = "localhost";
});
// Below line of code is not required.
// $provide.service('ForumService', function() {
// this.constructor = jasmine.createSpy('ForumService')
// });
// $provide.service('Forum', function() {
// this.constructor = jasmine.createSpy('Forum')
// });
}));
//2.
// Instead of injecting the services like this
// beforeEach(function() {
// inject(function($injector) {
// service = $injector.get('ForumService');
// httpBackend = $injector.get('$httpBackend');
// $q = $injector.get('$q');
// });
// });
// Inject them like this
beforeEach(inject(function(_ForumService_, _$httpBackend_) {
ForumService = _ForumService_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', 'localhost/api/forums').respond(200, returnData);
}))
// 5. make sure no expectations were missed in your tests.
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should use Function', function() {
// This data should be outside a specific it block so that it could be reused.
// Moved it outside.
// var returnData = [{
// id: 1,
// name: "Programming Questions",
// description: "Please post all Questions you have in regards to programming here"
// }, {
// id: 2,
// name: "OOP",
// description: "Object Oriented Programming"
// }];
console.info('foo');
// This should be inside beforeEach block so that it could be reused.
// httpBackend.when('GET', 'localhost/api/forums').respond(200, returnData);
// You call httpBackend's flush right after the call to your service's method and then expect.
// Also your expectations are wrong. So you might get errors.
// Fixing those.
// service.getForums().then(function(response) {
// console.info(response); // to see the response
// expect(response.data.id).toBe(1);
// expect(response.data.name).toBe("Programming Questions");
// expect(response.data.description).toBe("Please post all Questions you have in regards to programming here");
// });
// httpBackend.flush();
// Like this.
var successCallback = function(data) {
expect(data.length).toEqual(2);
expect(data[0].id).toBe(1);
expect(data[0].name).toBe("Programming Questions");
expect(data[0].description).toBe("Please post all Questions you have in regards to programming here");
}
var errorCallback = function(error) {
}
ForumService.getForums(successCallback, errorCallback);
$httpBackend.flush();
});
});
Hope this helps
I'm trying to figure out these karma tests to test my Angular app. I can figure out simple things. But I am having problems with one that connects to my database.
I am wanting to test the $scope.getMultipleOptions function in the controller below:
$scope.options = [];
$scope.getMultipleOptions("form_field_1", $scope.options);
$scope.getMultipleOptions = function (key, opts) {
var id = key.replace("form_field_", "");
var promise = DynamicFormFactory.GetData('/DynamicForm/GetMultipleOptions?form_field_id=" + id);
promise.then(
function (success) {
angular.forEach(success, function (o) {
opts.push(o);
});
},
function (error) {
// Error
}
);
}
This is what my factory/service looks like:
dynamicFormApp.factory('DynamicFormFactory', ['$http', function ($http) {
return {
GetData: function (url) {
return $http.get(url)
.then(function (response) {
return response.data;
}, function (error) {
return error;
});
}
}]);
And this is what I've done to set up my karma test
describe('DynamicFormController', function () {
beforeEach(module('dynamicFormApp'));
var $controller;
var $rootScope;
beforeEach(inject(function (_$controller_, _$rootScope_) {
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
$rootScope = _$rootScope_;
}));
describe('$scope.getMultipleOptions', function () {
var $scope, controller;
beforeEach(function () {
$scope = $rootScope.$new();
controller = $controller('DynamicFormController', { $scope: $scope });
$scope.fields = [];
});
it('$scope.getMultipleOptions', function () {
var key = "form_field_15";
var expectedResult = [{ desc: "DESCRIPTION", id: "ID" }];
$scope.getMultipleOptions(key, $scope.fields);
expect($scope.fields).toEqual(expectedResult);
});
});
});
The test always fails as $scope.fields is always empty. How do I do this?
I am learning BDD with Jasmine and Karma.
I'm following John Papa's style guide and I have some difficulties testing my code.
I want to test this kind of controller function (from John Papa's guide):
function getAvengers() {
return dataservice.getAvengers()
.then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
With this factory:
function dataservice($http) {
return {
getAvengers: getAvengers
};
function getAvengers() {
return $http.get('/api/maa')
.then(getAvengersComplete)
.catch(getAvengersFailed);
function getAvengersComplete(response) {
return response.data.results;
}
function getAvengersFailed(error) {
// Fail
}
}
}
I've made these test:
describe("avengers controller", function() {
var controller, avengers;
beforeEach(function() {
module('app');
});
beforeEach(inject(function(_$rootScope_, _$controller_, _$q_, _dataservice_) {
dataserviceMock = _dataservice_;
controller = _$controller_;
vm = controller("AvengerController", { dataservice: dataserviceMock });
$q = _$q_;
rootScope = _$rootScope_;
avengers = [{"id": 1}, {"id": 2}];
}));
it('should be created successfully', function () {
expect(vm).toBeDefined();
});
it('should have called `dataservice.getAvengers`', function() {
spyOn(dataserviceMock, 'getAvengers').and.callFake(function(){
var deferred = $q.defer();
return deferred.promise;
});
vm.getAvengers();
expect(dataserviceMock.getAvengers).toHaveBeenCalledTimes(1);
});
// What I'm trying to test
// Test 1
it('should have Avengers', function() {
spyOn(dataserviceMock, 'getAvengers').and.callFake(function(){
var deferred = $q.defer();
deferred.resolve([{"id": 1}, {"id": 2}]);
return deferred.promise;
);
vm.getAvengers();
expect(vm.avengers.length).toEqual(avengers.length);
});
// Test 2
it('should have Avengers', function() {
spyOn(dataserviceMock, 'getAvengers').and.returnValue(avengers);
vm.getAvengers();
expect(vm.avengers.length).toEqual(avengers.length);
});
});
I tried two different ways but it didn't work and I can't figure out how to test that avengerController.getAvengers() defines vm.avengers equal to the returned data avengers from the factory call.
I found a solution:
it("should have avengers", function() {
deferred = $q.defer();
spyOn(dataserviceMock, 'getAvengers').and.returnValue(deferred.promise);
deferred.resolve(customers);
vm.getAvengers();
expect(dataserviceMock.getAvengers).toHaveBeenCalled();
scope.$digest();
expect(vm.avengers).toBeDefined();
expect(vm.avengers).toEqual(avengers);
});
I had to return a promise and use the scope.$digest() to simulate the scope life cycle as mentionned here.
I'm trying to mock a service I'm using and should return a promise, the mock service is being called but I can't get the result to my test.
service function to be tested:
function getDevices() {
console.log('getDevices');
return servicesUtils.doGetByDefaultTimeInterval('devices')
.then(getDevicesComplete);
function getDevicesComplete(data) {
console.log('getDevicesComplete');
var devices = data.data.result;
return devices;
}
}
My test is:
describe('devicesService tests', function () {
var devicesService;
var servicesUtils, $q, $rootScope;
beforeEach(function () {
servicesUtils = {};
module('app.core', function ($provide) {
servicesUtils = specHelper.mockServiceUtils($provide, $q, $rootScope);
});
inject(function (_devicesService_, _$q_, _$rootScope_) {
devicesService = _devicesService_;
$q = _$q_;
$rootScope = _$rootScope_.$new();
});
});
it('getting device list', function () {
console.log('getting device list');
devicesService.getDevices().then(function (result) {
console.log(result);
expect(result).toBeDefined();
});
});
});
Mock file:
function mockServiceUtils($provide, $q) {
var servicesUtils = {};
servicesUtils.doGetByDefaultTimeInterval = jasmine.createSpy().and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
$rootScope.$digest();
return deferred.promise;
});
$provide.value('servicesUtils', servicesUtils);
return servicesUtils;
}
Your code is way too complex.
Let's assume that you want to test a service devicesService that uses another service servicesUtils, having a method that returns a promise.
Let's assume devicesService's responsibility is to call servicesUtils and transform its result.
Here's how I would do it:
describe('devicesService', function() {
var devicesService, servicesUtils;
beforeEach(module('app.core'));
beforeEach(inject(function(_devicesService_, _servicesUtils_) {
devicesService = _devicesService_;
servicesUtils = _servicesUtils_;
}));
it('should get devices', inject(function($q, $rootScope) {
spyOn(servicesUtils, 'doGetByDefaultTimeInterval').and.returnValue($q.when('Remote call result'));
var actualResult;
devicesService.getDevices().then(function(result) {
actualResult = result;
});
$rootScope.$apply();
expect(actualResult).toEqual('The transformed Remote call result');
}));
});