Currently i got stuck in writing a unit test for my angular controller. I have a $scope Function which makes an ajax request and after resolving all promises it assigns the fetched data to $scope.products. But it does not work for me and i don't know what i'm doing wrong here!
controller
$scope.products = [];
// $q.all is used because i've some other data sources too
$scope.query = function (term) {
$q.all([
DataService.autocomplete.products(term)
]).then(function (results) {
$scope.products = results[0].data.content;
});
};
Dataservice
// dataservice return value
return {
autocomplete: {
products: function (term) {
// _makeRequest is a wrapper for a $http call
return _makeRequest('autocomplete/products', term);
}
}
}
Unit-Test
describe('[Autocomplete] AutocompleteCtrl', function () {
var $scope, DataService;
beforeEach(module('Autocompleter'));
beforeEach(inject(function ($rootScope, $controller, _$q_, _DataService_) {
var deferred = _$q_.defer();
$scope = $rootScope.$new();
DataService = _DataService_;
$controller('AutocompleteCtrl', {$scope: $scope});
deferred.resolve(['resolveData']);
spyOn(DataService.autocomplete, 'products').and.returnValue(deferred.promise);
}));
describe('Query', function () {
it('should resolve promise', function () {
$scope.query('term');
$scope.$apply();
expect($scope.products).toBe(['resolveData']);
});
});
});
Test-Result
TypeError: 'undefined' is not an object (evaluating 'results[0].data.content')
Your controller expects the DataService.autocomplete.products() function to return a promise, and expects this promise to be resolved with an object which has a data attribute, since you're doing:
results[0].data.content
In your test, you resolve the fake promise with the following value:
['resolveData']
So, instead of getting an object looking like
{
data:
{
content: 'someValue'
}
}
the controller receives ['resolveData'].
Obviously, accessing the data attribute of ['resolveData'] will lead to an undefined value.
You are expecting .data.content from the result of DataService.autocomplete.products().
Therefore, you should change your mock data from:
deferred.resolve(['resolveData']);
to this instead:
deferred.resolve({ data: { content: ['resolveData'] } });
Hope this helps.
Related
I am new to Unit Testing, I am trying to test a controller that is dependent on a factory that makes the calls to a URL to fetch data.
Below is my controller code(The sqResultReviewTable module code is in a separate file):
(function () {
angular.module('sqResultReviewTable')
.controller('sqResultReviewTableController', fetchJsonData); //fetchJsonData is the controller function name.
function fetchJsonData($scope, fetchDataFromJsonFile) { // fetchDataFromJsonFile is the the service that return the data that is fetched fron the JSON file.
labResultData = fetchDataFromJsonFile.getOrders().then(function (returnedArray) { //function call to the service method getOrders and storing the resultant array in a varaible called labResultData.
$scope.labResultData = returnedArray; //Assigning labresultData to the $scope.
console.log($scope.labResultData);
});
}
})()
And here is my factory service(The factory service is in a separate file):
(function () {
angular.module('sqResultReviewTable')
.factory('fetchDataFromJsonFile', ['$http', function ($http) {
return {
getOrders: function () { //getOrders is function which can be used to fetch data from the JSON file. It return an Array.
return $http({
method: 'GET',
url: '/ResultReview/GetDataFromService',
headers: {
'Content-Type': 'application/json'
}
})
.then(function (response) {
return JSON.parse(response.data); //Returning the array that is fetched from the JSON file.
});
}
};
}]);
})();
Below is test case that I have tried:
describe('sqResultReviewTable', function () {
beforeEach(angular.mock.module('sqResultReviewTable'));
var $controller;
beforeEach(angular.mock.inject(function(_$controller_){
$controller = _$controller_;
}));
describe('resultReviewTableController', function () {
it('should have an array of length 4', function () {
var $scope = {};
var controller = $controller('sqResultReviewTableController', { $scope: $scope });
var len = $scope.labResultData.length;
expect(len).toBe(4);
});
});
});
I am getting this error:
TypeError: Cannot read property 'length' of undefined
Any help regarding this is appreciated. Thank you.
Edit 1:
I also tried in this manner, but the result is same
TypeError: Cannot read property 'length' of undefined
describe('resultReviewTableController', function () {
beforeEach(module('sqResultReviewTable'));
var $scope;
describe('Table Controller', function () {
beforeEach(inject(function ($rootScope, $controller) {
$scope = $rootScope.$new();
$controller('sqResultReviewTableController', {$scope : $scope});
}));
it('should contain an array of length 4', function () {
expect($scope.labResultData.length).toBe(4);
});
});
});
I cannot get the test result to pass I'm using a very basic implementation to understand testing deeper.
I have a factory which returns a promise, accessed from my controller. I want to test that the call succeeds and assigns the response to the repos var. Following is the code:
'use strict';
angular.module('app')
.factory('searchServ', function ($timeout, $q, $http) {
return {
fetch: function(user) {
var deferred = $q.defer();
$timeout(function(){
$http({method: 'GET', url: 'https://api.github.com/users/' + user + '/repos'}).then(function(repos) {
deferred.resolve(repos.data);
}, function(reason){
deferred.reject(reason.status);
console.log(reason);
});
}, 30);
return deferred.promise;
}
};
})
.controller('MainCtrl', function ($scope, searchServ) {
$scope.results = function(user) {
$scope.message = '';
searchServ.fetch(user).then(function (repos) {
if(repos.length){
$scope.message = '';
$scope.repos = repos;
}
else{
$scope.message = 'not found'
}
}, function (){
$scope.message = 'not found';
});
};
});
//Test
'use strict';
describe('MainCtrl', function () {
var scope, searchServ, controller, deferred, repos = [{name: 'test'}];
// load the controller's module
beforeEach(module('app'));
beforeEach(inject(function($controller, $rootScope, $q) {
searchServ = {
fetch: function () {
deferred = $q.defer();
return deferred.promise;
}
};
spyOn(searchServ, 'fetch').andCallThrough();
scope = $rootScope.$new();
controller = $controller('MainCtrl', {
$scope: scope,
fetchGithub: fetchGithub
});
}));
it('should test', function () {
expect(scope.test).toEqual('ha');
});
it('should bind to scope', function () {
scope.results();
scope.$digest();
expect(scope.message).toEqual('');
//expect(scope.repos).not.toBe(undefined);
});
});
Running the test gives me the following error :
TypeError: undefined is not a function (evaluating 'spyOn(searchServ, 'fetch').andCallThrough()') in test/spec/controllers/main.js (line 15)
Any idea how I can test this such that it tests the scope binding as well as the async call?
There are a lot of issues with your code.
I've created this Plunkr for the purpose. index.js is the file with your code and test cases. I've edited most of the part according to the conventions and best-practices.
There are a few pointers I wanted to give you:
Since $http returns a promise, you should use that, instead of resolving the promise and creating another promise from your method. Not sure why is timeout used. So I removed $q and $timeout from searchServ's dependencies.
I did the same in the test case by removing the deferred variable that you used.
You should be using angular-mocks.js to mock your services and other dependencies instead of defining a service inside your test case(The way you have did.)
You should create separate describe blocks for testing different parts of your code(a controller in this case).
Hope this helps!
I am trying to write the test cass for the factory which is returing a JSON response.
But I am getting the error:
Error: [$injector:unpr] http://errors.angularjs.org/1.4.1/$injector/unpr?p0=serviceProvider%20%3C-%20service
at Error (native)
Here is my code:
(function () {
angular.module('uspDeviceService',[]).factory('getDevice', GetDevice);
GetDevice.$inject = ['$http'];
function GetDevice($http) {
getDeviceList = function() {
return $http.get("static/test-json/devices/device-list.json");
}
return {
getDeviceList: getDeviceList
}
}
}());
Code for Test case:
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
angular.mock.inject(function ($injector) {
//Injecting $http dependencies
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
})
});
console.log('Injection Dependencies is done');
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device-list.json").respond("Response found!");
httpBackend.flush();
}))
})
});
I am new to Angular Unit testing, can anyone please help me, where I am going wrong..
Two things that jump out at me:
Your angular.module declaration is defining a module, not getting the module. I would encourage you to split that up so that it's a fair bit more clear what your intent is.
angular.module('uspDeviceService', []);
angular.module('uspDeviceService').factory('getDevice', GetDevice);
It likely works as-is, but clarity is important.
What is...service? It's not defined anywhere in your code, and Angular can't find it either, hence the error message. You may be looking to get getDevice instead. Also, name your test variable with respect to what it actually is, so you don't confuse yourself.
// defined above
var getDevice;
// while injecting
getDevice = $injector.get('getDevice');
Supposing that you have an angularjs controller myController defined in myModule. The controller do some action when the api call is success and shows a flash message when api returns success = false. The your controller code would be something like
angular.module('myModule')
.controller( 'myController', function ( $scope,flashService, Api ) {
Api.get_list().$promise.then(function(data){
if(data.success) {
$scope.data = data.response
}
else{
flashService.createFlash(data.message, "danger");
}
});
});
Now to test both success = true and success = false we
describe('myController', function(){
var $rootScope, $httpBackend, controller, flashService;
var apilink = 'http://apilink';
beforeEach(module('myModule'));
beforeEach(inject(function(_$httpBackend_,_$rootScope_, _$controller_, _flashService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
flashService = _flashService_;
controller = _$controller_("myController", {$scope: $rootScope});
}));
it('init $scope.data when success = true', function(){
$httpBackend.whenGET(apilink)
.respond(
{
success: true,
response: {}
});
$httpBackend.flush();
expect($rootScope.data).toBeDefined();
});
it('show flash when api request failure', function(){
spyOn(flashService, 'createFlash');
$httpBackend.whenGET(apilink)
.respond(
{
success: false
});
$httpBackend.flush();
expect(flashService.createFlash).toHaveBeenCalled();
});
});
You are always going to mock the response because here we are testing the javascript code behaviour and we are not concerned with the Api. You can see when success the data is initialized and when success is false createFlash is called.
As far as test for factory is concerned you can do
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
});
});
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device- list.json").respond("Response found!");
var result = getDevice.getDeviceList();
httpBackend.flush();
expect(result).toEqual('Response found!');
}));
});
});
I'm trying to write a karma/jasmine test and I would like some explanations about how mocks are working on a service which is returning a promise. I explain my situation :
I have a controller in which I do the following call :
mapService.getMapByUuid(mapUUID, isEditor).then(function(datas){
fillMapDatas(datas);
});
function fillMapDatas(datas){
if($scope.elements === undefined){
$scope.elements = [];
}
//Here while debugging my unit test, 'datas' contain the promise javascript object instead //of my real reponse.
debugger;
var allOfThem = _.union($scope.elements, datas.elements);
...
Here is how my service is :
(function () {
'use strict';
var serviceId = 'mapService';
angular.module('onmap.map-module.services').factory(serviceId, [
'$resource',
'appContext',
'restHello',
'restMap',
serviceFunc]);
function serviceFunc($resource, appContext, restHello, restMap) {
var Maps = $resource(appContext+restMap, {uuid: '#uuid', editor: '#editor'});
return{
getMapByUuid: function (uuid, modeEditor) {
var maps = Maps.get({'uuid' : uuid, 'editor': modeEditor});
return maps.$promise;
}
};
}
})();
And finally, here is my unit test :
describe('Map controller', function() {
var $scope, $rootScope, $httpBackend, $timeout, createController, MapService, $resource;
beforeEach(module('onmapApp'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('maps.ctrl', {
'$scope': $scope
});
};
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
var response = {"elements":[1,2,3]};
it('should allow user to get a map', function() {
var controller = createController();
$httpBackend.expect('GET', '/onmap/rest/map/MY-UUID?editor=true')
.respond({
"success": response
});
// hope to call /onmap/rest/map/MY-UUID?editor=true url and hope to have response as the fillMapDatas parameter
$scope.getMapByUUID('MY-UUID', true);
$httpBackend.flush();
});
});
What I really want to do is to have my response object ( {"elements:...}) as the datas parameter of the fillMapDatas function. I don't understand how to mock all the service things (service, promise, then)
So you want to test, if your service responses as expected? Then, this is something you would rather test on the service. Unit test promise based methods could look like this:
var mapService, $httpBackend, $q, $rootScope;
beforeEach(inject(function (_mapService_, _$httpBackend_, _$q_, _$rootScope_) {
mapService = mapService;
$httpBackend = _$httpBackend_;
$q = _$q_;
$rootScope = _$rootScope_;
// expect the actual request
$httpBackend.expect('GET', '/onmap/rest/map/uuid?editor=true');
// react on that request
$httpBackend.whenGET('/onmap/rest/map/uuid?editor=true').respond({
success: {
elements: [1, 2, 3]
}
});
}));
As you can see, you don't need to use $injector, since you can inject your needed services directly. If you wanna use the correct service names throughout your tests, you can inject them with prefixed and suffixed "_", inject() is smart enough to recognise which service you mean. We also setup the $httpBackend mock for each it() spec. And we set up $q and $rootScope for later processing.
Here's how you could test that your service method returns a promise:
it('should return a promise', function () {
expect(mapService.getMapUuid('uuid', true).then).toBeDefined();
});
Since a promise always has a .then() method, we can check for this property to see if it's a promise or not (of course, other objects could have this method too).
Next you can test of the promise you get resolves with the proper value. You can do that setting up a deferred that you explicitly resolve.
it('should resolve with [something]', function () {
var data;
// set up a deferred
var deferred = $q.defer();
// get promise reference
var promise = deferred.promise;
// set up promise resolve callback
promise.then(function (response) {
data = response.success;
});
mapService.getMapUuid('uuid', true).then(function(response) {
// resolve our deferred with the response when it returns
deferred.resolve(response);
});
// force `$digest` to resolve/reject deferreds
$rootScope.$digest();
// make your actual test
expect(data).toEqual([something]);
});
Hope this helps!
I have a ParseService, that I would like to mock in order test all the controllers that are using it, I have been reading about jasmine spies but it is still unclear for me. Could anybody give me an example of how to mock a custom service and use it in the Controller test?
Right now I have a Controller that uses a Service to insert a book:
BookCrossingApp.controller('AddBookCtrl', function ($scope, DataService, $location) {
$scope.registerNewBook = function (book) {
DataService.registerBook(book, function (isResult, result) {
$scope.$apply(function () {
$scope.registerResult = isResult ? "Success" : result;
});
if (isResult) {
//$scope.registerResult = "Success";
$location.path('/main');
}
else {
$scope.registerResult = "Fail!";
//$location.path('/');
}
});
};
});
The service is like this:
angular.module('DataServices', [])
/**
* Parse Service
* Use Parse.com as a back-end for the application.
*/
.factory('ParseService', function () {
var ParseService = {
name: "Parse",
registerBook: function registerBook(bookk, callback) {
var book = new Book();
book.set("title", bookk.title);
book.set("description", bookk.Description);
book.set("registrationId", bookk.RegistrationId);
var newAcl = new Parse.ACL(Parse.User.current());
newAcl.setPublicReadAccess(true);
book.setACL(newAcl);
book.save(null, {
success: function (book) {
// The object was saved successfully.
callback(true, null);
},
error: function (book, error) {
// The save failed.
// error is a Parse.Error with an error code and description.
callback(false, error);
}
});
}
};
return ParseService;
});
And my test so far look like this:
describe('Controller: AddBookCtrl', function() {
// // load the controller's module
beforeEach(module('BookCrossingApp'));
var AddBookCtrl, scope, book;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope;
book = {title: "fooTitle13"};
AddBookCtrl = $controller('AddBookCtrl', {
$scope: scope
});
}));
it('should call Parse Service method', function () {
//We need to get the injector from angular
var $injector = angular.injector([ 'DataServices' ]);
//We get the service from the injector that we have called
var mockService = $injector.get( 'ParseService' );
mockService.registerBook = jasmine.createSpy("registerBook");
scope.registerNewBook(book);
//With this call we SPY the method registerBook of our mockservice
//we have to make sure that the register book have been called after the call of our Controller
expect(mockService.registerBook).toHaveBeenCalled();
});
it('Dummy test', function () {
expect(true).toBe(true);
});
});
Right now the test is failing:
Expected spy registerBook to have been called.
Error: Expected spy registerBook to have been called.
What I am doing wrong?
What I was doing wrong is not injecting the Mocked Service into the controller in the beforeEach:
describe('Controller: AddBookCtrl', function() {
var scope;
var ParseServiceMock;
var AddBookCtrl;
// load the controller's module
beforeEach(module('BookCrossingApp'));
// define the mock Parse service
beforeEach(function() {
ParseServiceMock = {
registerBook: function(book) {},
getBookRegistrationId: function() {}
};
});
// inject the required services and instantiate the controller
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
AddBookCtrl = $controller('AddBookCtrl', {
$scope: scope,
DataService: ParseServiceMock
});
}));
it('should call registerBook Parse Service method', function () {
var book = {title: "fooTitle"}
spyOn(ParseServiceMock, 'registerBook').andCallThrough();
//spyOn(ParseServiceMock, 'getBookRegistrationId').andCallThrough();
scope.registerNewBook(book);
expect(ParseServiceMock.registerBook).toHaveBeenCalled();
//expect(ParseServiceMock.getBookRegistrationId).toHaveBeenCalled();
});
});
You can inject your service and then use spyOn.and.returnValue() like this:
beforeEach(angular.mock.module('yourModule'));
beforeEach(angular.mock.inject(function($rootScope, $controller, ParseService) {
mock = {
$scope: $rootScope.$new(),
ParseService: ParseService
};
$controller('AddBookCtrl', mock);
}));
it('should call Parse Service method', function () {
spyOn(mock.ParseService, "registerBook").and.returnValue({id: 3});
mock.$scope.registerNewBook();
expect(mock.ParseService.registerBook).toHaveBeenCalled();
});
Following Javito's answer 4 years after-the-fact. Jasmine changed their syntax in 2.0 for calling through to real methods on spies.
Change:
spyOn(ParseServiceMock, 'registerBook').andCallThrough();
to:
spyOn(ParseServiceMock, 'registerBook').and.callThrough();
Source
Include angular-mocks.js in your project and read carefully through the following link.