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);
});
});
});
Related
When mocking using $httpBackend, How do I create the mock when I do not know exactly the type of data being returned. I want the http call to be on controller initialsation. One caveat is that it will be a json object being returned.
(function () {
'use strict';
angular
.module('app')
.service('dataService', dataService);
function dataService($http) {
this.getMovies = getMovies;
////////////////
function getMovies() {
return $http.get('./src/app/movies/data.json')
.then(function (response) {
return response.data
})
}
};
})();
;
(function () {
'use strict';
angular.module('app')
.controller('moviesController', moviesController);
moviesController.$inject = ['dataService'];
function moviesController(dataService) {
var vm = this
vm.movies;
vm.getMovies = getMovies;
getMovies();
function getMovies() {
return dataService.getMovies()
.then(function (data) {
return vm.movies = data.movies;
});
}
};
}());
;
describe('moviesController', function () {
var $controller,
moviesController,
dataService,
$httpBackend;
beforeEach(angular.mock.module('app'));
beforeEach(angular.mock.module('ui.router'));
beforeEach(inject(function (_$controller_, _dataService_, _$httpBackend_) {
$controller = _$controller_;
dataService = _dataService_;
$httpBackend = _$httpBackend_;
moviesController = $controller('moviesController', { dataService: dataService });
}))
it('should be defined', function () {
expect(moviesController).toBeDefined();
});
it('should initialise with a call to dataService.getMovies()', function () {
var url = "./src/app/movies/data.json";
var movies = {};
$httpBackend.expectGET(url).respond(200, movies);
moviesController.getMovies();
expect(moviesController.movies).toEqual(movies);
$httpBackend.flush();
});
});
;
Expected undefined to equal Object({ }).
You can set the return to be an object you define in the spec.
var response = {};
it('should initialise with a call to dataService.getMovies()', function () {
$httpBackend.expectGET("./src/app/movies/data.json").respond(response);
$httpBackend.flush();
});
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 am following the angular-test-patterns guide, and I get it working with my first controller test. But when I write the next test, I get the error:
TypeError: 'undefined' is not an object (evaluating '$scope.pages.$promise')
The problem then I know is the following line:
$scope.busy = $scope.pages.$promise;
But I don't know how to deal with this, especially since I am very new in test issues with JavaScript. I looking for a correct and viable way of doing this, to point me in the right direction.
The controller:
angular.module('webvisor')
.controller('page-list-controller', function($scope,Page){
$scope.pages = Page.query();
$scope.busy = $scope.pages.$promise;
});
Service:
angular.module('webvisor').
factory('Page', ['$resource', 'apiRoot', function($resource, apiRoot) {
var apiUrl = apiRoot + 'pages/:id/:action/#';
return $resource(apiUrl,
{id: '#id'},
{update: {method: 'PUT'}}
);
}]);
Test:
'use strict';
describe('Controller: page-list-controller', function () {
var ctrl, scope, rootScope, Page;
beforeEach(function () {
module('webvisor');
module(function ($provide) {
$provide.value('Page', new MockPage());
});
inject(function ($controller, _Page_) {
scope = {};
rootScope = {};
Page = _Page_;
ctrl = $controller('page-list-controller', {
$scope: scope,
$rootScope: rootScope
});
});
});
it('should exist', function () {
expect(!!ctrl).toBe(true);
});
describe('when created', function () {
// Add specs
});
});
Mock:
MockPage = function () {
'use strict';
// Methods
this.query = jasmine.createSpy('query'); // I dont know if this is correct
return this;
};
With Mox, your solution would look like this:
describe('Controller: page-list-controller', function () {
var mockedPages = []; // This can be anything
beforeEach(function () {
mox
.module('webvisor')
.mockServices('Page') // Mock the Page service instead of $httpBackend!
.setupResults(function () {
return {
Page: { query: resourceResult(mockedPages) }
};
})
.run();
createScope();
createController('page-list-controller');
});
it('should get the pages', function () {
expect(this.$scope.pages).toEqual(resourceResult(mockedPages));
});
});
As you see, Mox has abstracted away all those boilerplate injections like $rootScope and $controller. Futhermore there is support for testing resources and promises out of the box.
Improvements
I advise you not to put the resource result directly on the scope, but resolve it as a promise:
$scope.busy = true;
Pages.query().$promise
.then(function (pages) {
$scope.pages = pages;
$scope.busy = false;
});
The Mox test is just this:
expect(this.$scope.busy).toBe(true);
this.$scope.$digest(); // Resolve the promise
expect(this.$scope.pages).toBe(mockedPages);
expect(this.$scope.busy).toBe(false);
NB: You also can store the result of createScope() into a $scope var and reuse that everywhere, instead of accessing this.$scope.
After some research and many try and error cases, I answer myself with a possible solution, but I expect to find some more usable and not too repetitive soon. For now, I am satisfied with this, using $httpBackend.
Test:
'use strict';
describe('Controller: page-list-controller', function () {
var ctrl, scope, rootScope, httpBackend, url;
beforeEach(function () {
module('webvisor');
inject(function ($controller, $httpBackend, apiRoot) {
scope = {};
rootScope = {};
httpBackend = $httpBackend;
url = apiRoot + 'pages/#';
ctrl = $controller('page-list-controller', {
$scope: scope,
$rootScope: rootScope
});
});
});
it('should exist', function () {
expect(!!ctrl).toBe(true);
});
describe('when created', function () {
it('should get pages', function () {
var response = [{ 'name': 'Page1' }, { 'name': 'Page2' }];
httpBackend.expectGET(url).respond(200, response);
httpBackend.flush();
expect(scope.pages.length).toBe(2);
});
});
});
I found this solution reading this question. This work very well, and for now, satisfied me. In future, I tried somethig like those:
angular-easy-test
mox
I'm trying to learn angular unit test with $resource.
Here I have a simple controller :
.controller('DictionaryCtrl', function ($scope, DictionaryService) {
$scope.jSearchDictionary = function () {
$scope.word = DictionaryService.getByJword({ jp: $scope.jword });
}
$scope.eSearchDictionary = function () {
$scope.word = DictionaryService.getByEword({ eng: $scope.eword });
}
})
In my view, I have 2 ng-submit (jSearchDictionary and eSearchDictionary) and i bind the corresponding word that is searched ( jword or eword ).
The service is also quite simple :
.factory('DictionaryService', function ($resource) {
return $resource('http://127.0.0.1:3000/api/nlp/words', {}, {
getByJword: { method: 'GET', params: { jp: '#jword' } },
getByEword: { method: 'GET', params: { en: '#eword' } },
})
})
Finally, here is my test.
describe('Controller: nlpCtrl', function () {
beforeEach(function () {
this.addMatchers({
toEqualData: function (expected) {
return angular.equals(this.actual, expected);
}
});
});
beforeEach(module('gakusei'));
describe('nlpCtrl', function () {
var scope,
$controller,
$httpBackend,
$stateParams,
Eword,
mockWord = [
{
"words": [
{
"readings": [
"ホッケー"
]
}
],
"count": 1
}];
beforeEach(inject(function (_$httpBackend_, $rootScope, _$controller_) {
scope = $rootScope.$new();
$controller = _$controller_;
$httpBackend = _$httpBackend_;
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should get a word', inject(function (DictionaryService) {
Eword = "englishWord";
$httpBackend.expectGET('http://127.0.0.1:3000/api/nlp/words?eng=englishWord')
.respond(mockWord[0]);
var ctrl = $controller('DictionaryCtrl', { $scope: scope });
var request = DictionaryService.getByEword({ eng: Eword })
$httpBackend.flush();
expect(scope.word).toEqualData(mockWord[0]);
expect(BasketService.getByJword).toBeTruthy();
expect(BasketService.getByEword).toBeTruthy();
}));
});
});
The problem is at the line :
expect(scope.word).toEqualData(mockWord[0]);
scope.word being undefined. Unit Testing is way over my head right now, I'm not sure of what I'm doing at all. If you have a solution to this particular problem, have any advices at all concerning all the code or are willing to message me and guide me a little, that would be awesome.
You have couple issues in your expectation and set up.
1) You are testing a controller and its scope, so do actions on the controller methods and set values on controller scope.
2) Instead of doing Eword = "englishWord"; you should set the value on the controller scope scope.eword = "englishWord";
3) Instead of calling service method directly DictionaryService.getByEword({ eng: Eword }) , you need to invoke the method on the scope, i.e scope.eSearchDictionary(); so that when the method is resolved it resolves with respective data and sets it on the scope.
4) Note that when you test against scope.word directly you may not get desired result since the result object will have additional properties like $promise on it. Since you are directly assigning the results.
5) I am not sure if you need the last 2 expectations at all.
Try:-
it('should get a word', inject(function (DictionaryService) {
scope.eword = "englishWord";
$httpBackend.expectGET('http://127.0.0.1:3000/api/nlp/words?eng=englishWord')
.respond(mockWord[0]);
$controller('DictionaryCtrl', { $scope: scope });
scope.eSearchDictionary();
$httpBackend.flush();
expect(scope.word.words[0]).toEqual(mockWord[0].words[0]);
/*I dont think you need the following expectations at all*/
expect(DictionaryService.getByJword).toBeDefined();
expect(DictionaryService.getByEword).toBeDefined();
}));
Plnkr
Some syntax of expectation utility method is different from what you are using, you can use the same that you use, i just did it for the demo
The variable you are looking for does not exist outside of those two functions. Try defining it at the top of your controller like so:
.controller('DictionaryCtrl', function ($scope, DictionaryService) {
$scope.word = '';
$scope.jSearchDictionary = function () {
$scope.word = DictionaryService.getByJword({ jp: $scope.jword });
}
$scope.eSearchDictionary = function () {
$scope.word = DictionaryService.getByEword({ eng: $scope.eword });
}
})
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.