Weird error when testing angular controller with $httpBackend - javascript

I've been trying to fix it for hours. Unfortunately with negative effect so please help me.
In my application I want to test my $http requests. I'm doing it by using $httpBackend.
Here is my controller code:
angular.module('app').controller('testController',function($scope,$http){
$http.get('/test/users').success(function(data){
console.log('wtf');
});
})
My unit test code:
describe('testController tests', function(){
var $httpBackend, $scope, createController;
beforeEach(module('app'));
beforeEach(inject(function($injector, ngTableParams, notifier,identity, $sessionStorage, $location){
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET('/test/users').respond({test: 'test'});
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function(){
return $controller('testControler', { '$scope': $scope, '$http': $httpBackend });
};
}));
it('should fetch users ', function() {
$httpBackend.expectGET('/test/users');
var controller = createController();
$httpBackend.flush();
});
});
It's not working. I always have the following error:
TypeError: Object function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) {
var xhr = new MockXhr(),
expectation = expectations[0],
wasExpected = false;
function prettyPrint(data) {
return (angula...<omitted>... } has no method 'get'
at new <anonymous> (http://localhost:9876/base/public/app/controllers/testController.js:256:11)
at invoke (http://localhost:9876/base/public/libraries/angular/angular.js:3805:17)
at Object.instantiate (http://localhost:9876/base/public/libraries/angular/angular.js:3816:23)
at $get (http://localhost:9876/base/public/libraries/angular/angular.js:6922:28)
at createController (http://localhost:9876/base/test/unit/testControllerSpec.js:70:18)
at null.<anonymous> (http://localhost:9876/base/test/unit/testControllerSpec.js:89:26)
at jasmine.Block.execute (http://localhost:9876/base/node_modules/karma-jasmine/lib/jasmine.js:1145:17)
at jasmine.Queue.next_ (http://localhost:9876/base/node_modules/karma-jasmine/lib/jasmine.js:2177:31)
at http://localhost:9876/base/node_modules/karma-jasmine/lib/jasmine.js:2167:18
Any ideas what does it mean and how to fix it?

I've found error in my code. $httpBackend shouldn't be injected into controller.

I think the problem is you need to add the dependencies to your module creation:
angular.module('app', []).controller ...
instead of
angular.module('app').controller ...

Related

Jasmine spyOn not working properly on AngularJS directive

I'm working on an AngularJS app and I'm facing some problems with Jasmine's SpyOn in a concrete directive.
The directive is quite simple, just call a service's method and when it resolves/rejects the promise acts in consequence, setting some values or another ones.
The problem: When I try to mock SignatureService.getSignatureData SpyOn does not work as I expect, and acts as if I was invoking jasmine's callThrough method over getSignatureData.
I've been using spyOn and mocks in other directives and services, and there was no problem with those.
I've been trying to solve this issue the last two days, comparing with other solutions and user's answers, but I can not find a valid solution.
Here's my code:
AngularJS directive code:
angular
.module('module_name')
.directive('signatureDirective', signatureDirective);
angular
.module('GenomcareApp_signature')
.controller('signatureDController', signatureDController);
function signatureDirective() {
return {
restrict: 'E',
templateUrl: 'components/signature/signature.directive.html',
controller: signatureDController,
controllerAs: 'ctrl',
bindToController: true
};
}
signatureDController.$inject = [
'$scope',
'$rootScope',
'$location',
'SignatureService'
];
function signatureDController($scope, $rootScope, $location, SignatureService) {
var controller = this;
$scope.$on('pdfFileLoadSuccessfully', function (data) {
console.log(data);
controller.loadPdfSucceed = true;
});
$scope.$on('pdfFileLoadFails', function (data) {
console.error(data);
controller.loadPdfError = true;
});
function loadDirectiveInitData() {
var queryParameters = atob($location.search().data);
controller.email = queryParameters.split(';')[0];
controller.phone = queryParameters.split(';')[1];
controller.docid = queryParameters.split(';')[2];
SignatureService.getSignatureData(controller.email, controller.phone, controller.docid)
.then(
function (data) {
console.log(data);
controller.stampTime = data.stamp_time;
controller.fileUrl = data.original_file.url;
},
function (error) {
console.error(error);
controller.error = true
})
.finally(
function () {
controller.endLoad = true;
})
}
loadDirectiveInitData();
}
Jasmine test code:
'use strict';
/* global loadJSONFixtures */
describe('Test :: Signature directive', function () {
beforeEach(angular.mock.module('app'));
beforeEach(module('translateNoop'));
var $q, $compile, $rootScope, controller, $scope, $httpBackend, $location, SignatureService;
beforeEach(angular.mock.inject(function (_$controller_, _$q_, _$rootScope_, _$location_, _$compile_, _$httpBackend_, _SignatureService_) {
$q = _$q_;
$compile = _$compile_;
$location = _$location_;
$scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
SignatureService = _SignatureService_;
spyOn($location, 'search').and.returnValue({data: 'dGVzdEB0ZXN0LmNvbTsrMzQ2NjY2NjY2NjY7WG9TUFFnSkltTWF2'});
$httpBackend.whenGET('components/signature/signature.directive.html').respond(200, '');
controller = _$controller_('signatureDController', {$scope: $scope});
}));
describe('Testing directive', function () {
it('Init data should be set when promise resolves/rejects', function (done) {
// SpyOn DOES NOT MOCK THE SERVICE METHOD
spyOn(SignatureService, 'getSignatureData').and.callFake(function () {
return $q.resolve({...})
});
var element = angular.element('<signature-directive></signature-directive>');
element = $compile(element)($scope);
$scope.$digest();
done();
// ... some expect stuff
});
});
});
If any one can give me some advice or solution, I would be very thankful.
Thank you very much.
UPDATE1: I don't know why, but if I do not declare the controller variable in the global beforeEach, Jasmine's spyOn mocks the method as I expect.
Now the issue is how to get the controller to test that the controller values are set as expected.
Well... I realized that the problem was that the controller was being created before all, and somehow when the service was mocked the controller ignores it.
This idea came by accident, when I paste the service's spyOn in the global beforeEach.
So I decide to create a new instance of the controller and the corresponding spyOn with the desired result inside the beforeEach of each describe.
It works. Maybe it's not the best aproach, and I encourage to anyone who have the answer to post it. I'm going to be eternally greatful.
Here's my final test code:
describe('Test :: Signature directive', function () {
beforeEach(angular.mock.module('app'));
beforeEach(module('translateNoop'));
var $q, $compile, $rootScope, $scope, $httpBackend, $location, SignatureService, test_fixture;
beforeEach(angular.mock.inject(function (_$q_, _$rootScope_, _$location_, _$compile_, _$httpBackend_, _SignatureService_) {
$q = _$q_;
$compile = _$compile_;
$location = _$location_;
$scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
SignatureService = _SignatureService_;
// controller = _$controller_;
spyOn($location, 'search').and.returnValue({data: 'dGVzdEB0ZXN0LmNvbTsrMzQ2NjY2NjY2NjY7WG9TUFFnSkltTWF2'});
$httpBackend.whenGET('components/signature/signature.directive.html').respond(200, '');
}));
describe('Testing directive when service resolve promise', function () {
var controller;
beforeEach(inject(function(_$controller_) {
spyOn(SignatureService, 'getSignatureData').and.callFake(function () {
return $q.resolve({...})
});
controller = _$controller_('signatureDController', {$scope: $scope})
}));
it('Init data should be set', function () {
// spyOn($location, 'search').and.callThrough();
var element = angular.element('<signature-directive></signature-directive>');
element = $compile(element)($scope);
$scope.$digest();
// ... some expect(...).toEqual(...) stuff and more
});
});
});
Thank you for your time.
Try to use $q.defer(), here's an example:
it('Init data should be set when promise resolves/rejects', function (done) {
// SpyOn DOES NOT MOCK THE SERVICE METHOD
spyOn(SignatureService, 'getSignatureData').and.callFake(function () {
let deferred = $q.defer();
deferred.resolve({...});
return deferred.promise;
});
var element = angular.element('<signature-directive></signature-directive>');
element = $compile(element)($scope);
$scope.$digest();
done();
// ... some expect stuff
});

Karma error - Expected undefined to be defined

I want to unit test my controller. I started with basic test assertions of expect API. But I am facing challenge in mocking scope methods inside a conditional check. I am getting an undefined error since it is not available under scope, only the global logout() method is available.
I tried mocking the localStorageService using spyOn as true to satisfy the condition, but that's still of no help. Any solution will be of great help to get me kickstarted.
Controller:
angular.module('app').controller('sampleCtrl',
function($scope, $state, $http, $rootScope, localStorageService) {
if (!(localStorageService.get('isAuthenticated'))) {
$state.go('home');
}
if (localStorageService.get('isAuthenticated') === true) {
//http post calls made here to perform certain operation on page load
$scope.someMethod = function(){
//do something
}
}
$scope.logOut = function() {
localStorageService.set('property', '');
localStorageService.set('isAuthenticated', false);
$state.go('home');
};
});
Karma:
'use strict';
describe('Controller: sampleCtrl', function() {
/** to load the controller's module */
beforeEach(module('app'));
var sampleCtrl,scope,httpBackend,deferred,rootScope;
beforeEach(inject(function ($controller,_$rootScope_,$httpBackend,$q) {
var store = {};
scope= _$rootScope_.$new(); // creates a new child scope of $rootScope for each test case
rootScope = _$rootScope_;
localStorageService = _localStorageService_;
httpBackend = $httpBackend;
httpBackend.whenGET(/\.html$/).respond('');
spyOn(localStorageService, 'set').and.callFake(function (key,val) {
store[key]=val;
});
spyOn(localStorageService, 'get').and.callFake(function(key) {
return store[key];
});
sampleCtrl = $controller('sampleCtrl',{
_$rootScope_:rootScope,
$scope:scope,
$httpBackend:httpBackend,
_localStorageService_:localStorageService
// add mocks here
});
localStorageService.set('isAuthenticated',true);
}));
/**ensures $httpBackend doesn’t have any outstanding expectations or requests after each test*/
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('sampleCtrl to be defined:',function(){
httpBackend.flush();
expect(sampleCtrl).toBeDefined();
});
// failing test case - scope.someMethod not available in scope
it('is to ensure only authenticated user can access the state methods',function(){
localStorageService.get('isAuthenticated');
httpBackend.flush();
expect(scope.someMethod).toBeDefined();
});
});
I've managed to get it work.
The problem was that localStorageService did not have isAuthenticated set to true on starting the controller. Place setting it to true before calling the controller.

AngularJs Jasmine unable to call $scope method within $rootScope method

I have a method in my controller $scope.get another method $rootScope.search. I am writing the unit tests using Karma and Jasmin.
When I am trying to test the method $rootScope.search it throwing an error, what I am doing wrong?
Code is as follows:
Ctrl.js
App.controller('Ctrl', ['$scope', '$rootScope', function($scope, $rootScope){
$scope.get = function(search){ // return some data from service };
$rootScope.search = function(data){
$scope.get(data);
};
}]);
Ctrl.spec.js
describe('Ctrl', function(){
beforeEach(module('app'));
var $controller, $scope = {}, $rootScope = {};
beforeEach(inject(function(_$controller_, _$rootScope_){
$controller = _$controller_;
$rootScope = _$rootScope_;
}));
describe('Data', function(){
beforeEach(function() {
$controller('Ctrl', { $scope:$scope, $rootScope:$rootScope});
spyOn($scope, 'get');
});
it('$rootScope.search', function(){
$rootScope.search();
expect($scope.get).toHaveBeenCalled();
});
})
});
Error
TypeError: $scope.get is not a function at Scope.$rootScope.search
Please help.
Actually I have to use the same from view as a global method.
If you want to provide global methods on $rootScope, do it from a .run block instead of a controller.
App.run([$rootScope', function($rootScope){
function get = function(search){ // return some data from service };
$rootScope.search = get;
}]);
But putting common functions in a factory is the recommended approach.
App.factory('mySearch', function(){
function get = function(search){ // return some data from service };
return { search: get };
});
Then inject the custom search in your controllers:
App.controller('Ctrl', ['$scope', 'mySearch', function($scope, mySearch){
var vm = $scope;
vm.data = mySearch.search(data);
}]);

jasmine karma unit test "Error: Unexpected request: GET /Content/json/3420_layer0.json"

Writing a simple test case.
describe('Services', function () {
describe('API', function () {
var $httpBackend, $rootScope, createController, requestHandler;
var jsonLayer0 = "/Content/json/3420_layer0.json";
// Set up the module
beforeEach(module('anbud'));
beforeEach(inject(function ($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET(jsonLayer0).respond(200, '');
$rootScope = $injector.get('$rootScope');
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
createController = function () {
return $controller('anbudTreeController', { '$scope': $rootScope });
};
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should fetch first layer', function () {
$httpBackend.expectGET(jsonLayer0);
var controller = createController();
$rootScope.getFirstLayer();
$httpBackend.flush();
});
I get:
Error: Unexpected request: GET /Content/json/3420_layer0.json
But, I do have:
var jsonLayer0 = "/Content/json/3420_layer0.json";
$httpBackend.whenGET(jsonLayer0).respond(200, '');
So, not sure what the problem is...
Full stack trace:
Error: Unexpected request: GET test
No more request expected
at $httpBackend (C:/git/angularjs/NSview/NSviewer/Scripts/angular-mocks.js:1244:9)
at $httpBackend (C:/git/angularjs/NSview/NSviewer/Scripts/angular-mocks.js:1237:11)
at sendReq (C:/git/angularjs/NSview/NSviewer/Scripts/angular.js:10515:9)
at serverRequest (C:/git/angularjs/NSview/NSviewer/Scripts/angular.js:10222:16)
at processQueue (C:/git/angularjs/NSview/NSviewer/Scripts/angular.js:14745:28)
at C:/git/angularjs/NSview/NSviewer/Scripts/angular.js:14761:27
at Scope.$eval (C:/git/angularjs/NSview/NSviewer/Scripts/angular.js:15989:28)
at Scope.$digest (C:/git/angularjs/NSview/NSviewer/Scripts/angular.js:15800:31)
at Function.$httpBackend.flush (C:/git/angularjs/NSview/NSviewer/Scripts/angular-mocks.js:1543:38)
at Object.<anonymous> (C:/git/angularjs/NSview/NSviewer/app/tests/testServiceAPI.js:54:26)
Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.4.7/$rootScope/inprog?p0=%24digest
at C:/git/angularjs/NSview/NSviewer/Scripts/angular.js:68:12
at beginPhase (C:/git/angularjs/NSview/NSviewer/Scripts/angular.js:16346:15)
at Scope.$digest (C:/git/angularjs/NSview/NSviewer/Scripts/angular.js:15780:9)
at Function.$httpBackend.verifyNoOutstandingExpectation (C:/git/angularjs/NSview/NSviewer/Scripts/angular-mocks.js:1575:38)
EDIT:
Added:
$httpBackend.expectGET(jsonLayer0);
Added:
Full stack trace
It is required to tell $httpBackend to expect a GET request.
Try the following code
it('should fetch first layer', function () {
$httpBackend.expectGET(jsonLayer0);
var controller = createController();
$rootScope.getFirstLayer();
$httpBackend.flush();
});

How to test a http request in my case

I am trying to test a rootscope http request in my case
I have something like
mainCont file
$rootScope.testLoad = $http.get('/api/testapi/product');
TestCont file
$rootScope.testLoad.success(function(product){
console.log(product)
})
Test file
describe('test', function () {
var $httpBackend, $rootScope, scope, mainCont, testCont;
beforeEach(module('myApp'));
beforeEach(inject(function (_$controller_, _$httpBackend_, _$rootScope_) {
scope = _$rootScope_.$new();
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
testCont = _$controller_('testCont', {
$scope: scope
});
mainContr = _$controller_('mainCont', {
$scope: scope
});
}));
describe('test http request', function() {
it('should check if the api is called', function() {
$httpBackend.expectGET('/api/testapi/product').respond(200);
//I am not sure how to test the call in testCont file.
})
})
});
I am not sure how to test the call in the testCont because when I run the test,
I got an error saying
TypeError: 'undefined' is not an object (evaluating '$rootScope.testLoad.success')
Can anyone help me to fix this issue? Thanks a lot!
You've defined testLoad as a function, so you need to call it by adding parentheses. Change your code to
$rootScope.testLoad().success(function(product){
console.log(product)
})

Categories