Unit testing call a function of a controller with Karma and Jasmine - angularjs

This is my angular controller :-
angular.module('authoring-controllers', []).
controller('NavCtrl', function($scope, $location, BasketNavigationService) {
$scope.test= function() {
$scope.testVar = BasketNavigationService.showBasketList();
};
});
TEST class
describe('NavCtrl', function() {
var scope, $location, createController;
beforeEach(inject(function ($rootScope, $controller, _$location_) {
$location = _$location_;
scope = $rootScope.$new();
createController = function() {
return $controller('NavCtrl', {
'$scope': scope
});
};
}));
it('should create $scope.testVar when calling test',
function() {
expect(scope.testVar).toBeUndefined();
scope.test();
expect(scope.testVar).toBedefined();
});
});
Getting an error when i run that test case :- scope.test() is undefined..
If i removed BasketNavigationService functionality from controller then it is working..
Please help me to solve that karma test case.

$scope.showBasketList is not a function that can be invoked with $scope.showBasketList(). It is a variable that is equal to the return value of BasketNavigationService.showBasketList().
If you want to reference that function instead it should be $scope.showBasketList = BasketNavigationService.showBasketList; in your controller.

here is the working demo , hope it helps. problem was with injecting the dependencies.
//--- CODE --------------------------
(function(angular) {
// Create module
var myApp = angular.module('myApp', []);
// Controller which counts changes to its "name" member
myApp.controller('MyCtrl', ['$scope', 'BasketNavigationService',
function($scope, BasketNavigationService) {
$scope.test = function() {
$scope.testVar = BasketNavigationService.showBasketList();;
};
}
]);
})(angular);
// ---SPECS-------------------------
describe('myApp', function() {
var scope,
controller;
beforeEach(function() {
module('myApp');
});
describe('MyCtrl', function() {
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('MyCtrl', {
'$scope': scope,
'BasketNavigationService': {
showBasketList: function() {
return null;
}
}
});
}));
it('should create $scope.testVar when calling test',
function() {
expect(scope.testVar).toBeUndefined();
scope.test();
// scope.$digest();
expect(scope.testVar).toBeDefined();
});
});
});
// --- Runner -------------------------
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
<script src="http://jasmine.github.io/1.3/lib/jasmine.js"></script>
<script src="http://jasmine.github.io/1.3/lib/jasmine-html.js"></script>
<script src="https://code.angularjs.org/1.2.9/angular.js"></script>
<script src="https://code.angularjs.org/1.2.9/angular-mocks.js"></script>
<link href="http://jasmine.github.io/1.3/lib/jasmine.css" rel="stylesheet" />
fiddle : http://jsfiddle.net/invincibleJai/pf1deoom/1/

Related

How to test changes on Component Bindings by parent element?

I have a component like follows and would like to test what the $onChange method does in case the binding myBinding changes.
I tried the whole morning, but could not find a way to solve this.
angular
.module('project.myComponent', [])
.component('myComponent', {
bindings: {
myBinding: '<'
},
template: '<div>{{$ctrl.result}}</div>',
controller: myComponentController
});
function myComponentController($filter, someService) {
var ctrl = this;
ctrl.result = 0;
$ctrl.$onChange = function (changes) {
if(angular.isDefined(changes.myBinding)) {
if(angular.isDefined(changes.myBinding.currentValue)) {
if(angular.isDefined(changes.myBinding.currentValue != changes.myBinding.previousValue)) {
myService.doSomething(changes.myBinding.currentValue).then(
function(data) {
ctrl.result = changes.myBinding.currentValue * 3;
}
);
}
}
}
}
}
I would like my test acting like it is the components parent which changes the value of the binding.
require('angular-mocks');
describe('myComponment', function() {
var element, scope;
beforeEach(inject(function(_$rootScope_, _$compile_) {
}));
fit('should display the controller defined title', function() {
// prepare test and set myBinding to 10
expect(component.result).toBe(30);
});
});
Is that possible? How?
Any hints? Plunker, CodePen or other examples?
Testing AngularJS components doesn't differ much from testing directives.
To test controller's methods / properties, you can access the instance of the component's controller using element.controller("componentName") method (componentName - is a camelCase directive / component name).
Here is example using $compile service to test the component and $onChanges hook:
angular.module('myApp', [])
.component('myComponent', {
bindings: {
myBinding: '<'
},
template: '<div>{{$ctrl.result}}</div>',
controller: 'myComponentController'
})
.controller('myComponentController', ['$filter', 'myService', function myComponentController($filter, myService) {
var ctrl = this;
ctrl.$onInit = onInit;
ctrl.$onChanges = onChanges;
function onInit() {
ctrl.result = ctrl.myBinding;
}
function onChanges(changes) {
if (angular.isDefined(changes.myBinding)) {
if (angular.isDefined(changes.myBinding.currentValue)) {
if (!angular.equals(changes.myBinding.currentValue, changes.myBinding.previousValue)) {
myService.doSomething(changes.myBinding.currentValue).then(
function (data) {
ctrl.result = data;
}
);
}
}
}
}
}])
.service('myService', ['$timeout', function ($timeout) {
return {
doSomething: function (x) {
return $timeout(function () {
return x * 3;
}, 500);
}
};
}]);
/*
TEST GO HERE
*/
describe('Testing a component controller', function() {
var $scope, ctrl, $timeout, myService;
beforeEach(module('myApp', function ($provide) {
}));
beforeEach(inject(function ($injector) {
myService = $injector.get('myService');
$timeout = $injector.get('$timeout');
}));
describe('with $compile', function () {
var element;
var scope;
var controller;
beforeEach(inject(function ($rootScope, $compile) {
scope = $rootScope.$new();
scope.myBinding = 10;
element = angular.element('<my-component my-binding="myBinding"></my-component>');
element = $compile(element)(scope);
controller = element.controller('myComponent');
scope.$apply();
}));
it('should render template', function () {
expect(element[0].innerText).toBe('10'); //initial
$timeout.flush(); //onchanges happened and promise resolved from the service
//undefined -> 10
expect(element[0].innerText).toBe('30');
});
it('should reflect to changes', function () {
spyOn(myService, "doSomething").and.callThrough();
scope.myBinding = 15; //change the binding
scope.$apply(); //we need to call $apply to pass the changes down to the component
$timeout.flush();
expect(myService.doSomething).toHaveBeenCalled(); // check if service method was called
expect(controller.result).toBe(45); // check controller's result value
});
})
});
.as-console-wrapper {
height:0;
}
<!DOCTYPE html>
<html>
<head>
<!-- jasmine -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.js"></script>
<!-- jasmine's html reporting code and css -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine-html.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/boot.js"></script>
<!-- angular itself -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
<!-- angular's testing helpers -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-mocks.js"></script>
</head>
<body>
<!-- bootstrap jasmine! -->
<script>
var jasmineEnv = jasmine.getEnv();
// Tell it to add an Html Reporter
// this will add detailed HTML-formatted results
// for each spec ran.
jasmineEnv.addReporter(new jasmine.HtmlReporter());
// Execute the tests!
jasmineEnv.execute();
</script>
</body>
</html>
You can also test your components using $componentController service. But in this case you will need to explicitly call life-cycle hooks in your tests, like:
ctrl = $componentController('myComponent', {$scope: scope}, { myBinding: 10 });
ctrl.$onInit();
To test $onChanges hook, you will need to pass a "properly" constructed changes object as argument:
angular.module('myApp', [])
.component('myComponent', {
bindings: {
myBinding: '<'
},
template: '<div>{{$ctrl.result}}</div>',
controller: 'myComponentController'
})
.controller('myComponentController', ['$filter', 'myService', function myComponentController($filter, myService) {
var ctrl = this;
ctrl.$onInit = onInit;
ctrl.$onChanges = onChanges;
function onInit() {
ctrl.result = ctrl.myBinding;
}
function onChanges(changes) {
if (angular.isDefined(changes.myBinding)) {
if (angular.isDefined(changes.myBinding.currentValue)) {
if (!angular.equals(changes.myBinding.currentValue, changes.myBinding.previousValue)) {
myService.doSomething(changes.myBinding.currentValue).then(
function (data) {
ctrl.result = data;
}
);
}
}
}
}
}])
.service('myService', ['$timeout', function ($timeout) {
return {
doSomething: function (x) {
return $timeout(function () {
return x * 3;
}, 500);
}
};
}]);
/*
TEST GO HERE
*/
describe('Testing a component controller', function () {
var $scope, ctrl, $timeout, myService;
beforeEach(module('myApp', function ($provide) {
}));
beforeEach(inject(function ($injector) {
myService = $injector.get('myService');
$timeout = $injector.get('$timeout');
}));
describe('with $componentController', function () {
var scope;
var controller;
beforeEach(inject(function ($rootScope, $componentController) {
scope = $rootScope.$new();
scope.myBinding = 10;
controller = $componentController('myComponent', {$scope: scope}, {myBinding: 10});
controller.$onInit();
}));
it('should reflect to changes', function () {
spyOn(myService, "doSomething").and.callThrough();
controller.$onChanges({myBinding: {currentValue: 15, previousValue: 10}});
$timeout.flush(); // resolve service promise
expect(myService.doSomething).toHaveBeenCalled(); // check if service method was called
expect(controller.result).toBe(45); // check controller's result value
});
})
});
.as-console-wrapper {
height:0;
}
<!DOCTYPE html>
<html>
<head>
<!-- jasmine -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.js"></script>
<!-- jasmine's html reporting code and css -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine-html.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/jasmine.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.8.0/boot.js"></script>
<!-- angular itself -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.js"></script>
<!-- angular's testing helpers -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-mocks.js"></script>
</head>
<body>
<!-- bootstrap jasmine! -->
<script>
var jasmineEnv = jasmine.getEnv();
// Tell it to add an Html Reporter
// this will add detailed HTML-formatted results
// for each spec ran.
jasmineEnv.addReporter(new jasmine.HtmlReporter());
// Execute the tests!
jasmineEnv.execute();
</script>
</body>
</html>
P.S.: $onChange is not a valid name of the component's life-cycle hook. It should be $onChanges.

How to implement a unit test for Angular JS controller with ajax service?

I have tried to write unit test for below code, but I can't able to achieve it.
Service code:
angular.module('ActivityApp').service('PersonService', [
'$http',
function ($http) {
var person = function () {
return $http({
method: 'GET',
url: '/apis/person'
});
};
return {
Person: person
}
}]);
Controller code
angular.module('ActivityApp').controller('PersonController', [
'$scope',
'PersonService',
function ($scope, PersonService) {
'use strict';
$scope.person = PersonService.Person().then(function(res) { alert(res.data) });
$scope.save = function () {
PersonService.Person().then(function (res) { alert(res.data) });
};
}]);
Unit test code:
describe("EDI controller", function () {
var $httpBackend, $rootScope, mycontroller, personService, $http, $httpBackend, $scope, deferred, $controller;
beforeEach(module('ediapp'));
beforeEach(function () {
filesAjaxService = jasmine.createSpyObj('personService', [
'Person'
]);
module(function ($provide) {
$provide.value('personService', personService);
});
});
beforeEach(inject(function (_$controller_, _$rootScope_, _personService_, _$http_, _$httpBackend_) {
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
$scope = _$rootScope_.$new();
$rootScope = _$rootScope_;
personService = _personService_;
$http = _$http_;
$httpBackend = _$httpBackend_;
mycontroller = $controller("filesCtrl", {
$scope: $scope,
PersonService: personService
});
}));
it("Called", function () {
// expect(fileController).toBeDefined();
// expect(filesAjaxService.GetAllFiles).toHaveBeenCalled();
});
afterEach(function () {
// $httpBackend.verifyNoOutstandingExpectation();
// $httpBackend.verifyNoOutstandingRequest();
});
});
When I run the above test case I get below error
TypeError: undefined is not an object (evaluating
'PersonService.Person().then(')
Can anyone provide a solution to solve this issue?
This question made me curious that I was looking for a solution because I had not done it before and it was very good for me.
i search after answer like you, and I was able to merge them together.
hope helps you [you can test it here]
var app = angular.module('app', []);
app.controller('ctrl', function($scope, httpService) {
$scope.url = "https://jsonplaceholder.typicode.com/users";
httpService.getUsers().then(function(data) {
$scope.users = data;
})
});
app.service("httpService", function($http) {
this.getUsers = function() {
return $http.get("https://jsonplaceholder.typicode.com/users")
.then(function(response) {
return response.data;
});
}
})
describe('unitTest', function() {
beforeEach(module('app'));
var $controller, $httpService;
beforeEach(inject(function($injector, _$controller_, _httpService_) {
$httpBackend = $injector.get('$httpBackend');
$controller = _$controller_;
$httpService = _httpService_;
}));
describe('getUsers', function() {
it('get users from httpService.getUsers()', inject(function() {
var $scope = {};
var controller = $controller('ctrl', {
$scope: $scope
});
$httpService.getUsers().then(function(data) {
$scope.valid = true;
$scope.response = data;
})
$httpBackend.when('GET', $scope.url).respond(200, {
foo: 'bar'
});
$httpBackend.flush();
expect($scope.valid).toBe(true);
expect($scope.response).toEqual({
foo: 'bar'
});
}));
});
});
<!-- Jasmine References -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.3/jasmine.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.3/jasmine.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.3/jasmine-html.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.3/boot.min.js"></script>
<!-- Angular and Angular Mock references -->
<script type="text/javascript" src="https://code.angularjs.org/1.4.0-rc.2/angular.min.js"></script>
<script type="text/javascript" src="https://code.angularjs.org/1.4.0-rc.2/angular-mocks.js"></script>
<div ng-app="app" ng-controller="ctrl">
<ul>
<li ng-repeat="user in users">
{{user.name}}
</li>
</ul>
</div>

How to write unit testcase for controller without using $scope in it

I need to write a test case for a method in a controller. In that controller I'm using this instead of $scope. If we use $scope we can able to write the test case as below. But how can i write a test case if i am using this in the controller.
app.controller('ExampleController', function(){
var test = this;
this.testFunction = function(){
return "Hello";
}
});
karma-test case file
describe('app module', function () {
beforeEach(module('testAngularApp'));
describe('ContentController', function () {
var scope, controller;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller;
controller('ContentController', {
$scope: $scope
});
it('Should return Hello', function () {
expect(scope.testFunction ()).toBe(true);
});
});
Here you go:
//--- CODE --------------------------
(function(angular) {
angular.module('myApp', [])
.controller('ExampleController', function() {
var vm = this;
vm.data = "HI!";
this.testFunction = function(val) {
vm.data = val;
}
});
})(angular);
// ---SPECS-------------------------
describe('myApp', function() {
describe('Example Controller', function() {
var scope, controller;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('ExampleController', {
$scope: scope
});
spyOn(controller, 'testFunction').and.callThrough();
}));
it('expect controller should be defined', function() {
expect(controller).toBeDefined();
});
it('expect scope should be defined', function() {
expect(scope).toBeDefined();
});
it('expect data should be initialized', function() {
expect(controller.data).toEqual("HI!");
});
it('expect data is updated when testFunction is called', function() {
controller.testFunction('Bye!');
scope.$apply();
expect(controller.testFunction).toHaveBeenCalled();
expect(controller.data).toEqual("Bye!");
});
});
});
// --- Runner -------------------------
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
}());
<body>
<!-- because we are testing our controller and not running we don't need a controller or even a module -->
<!-- so there is no ng-app or ng-controller in the markup -->
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine.css">
<!-- the order that these files load is critical, think twice before changing -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine-html.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/boot.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular-mocks.js"></script>
<h2>Finished jasmine unit test</h2>
</body>

How do i make this simple test in jasmine

I have the following test:
describe("An Angularjs test suite",function(){
beforeEach(module('myApp'));
var scope,controller;
beforeEach(inject(function($rootScope,$controller){
scope = $rootScope.$new(),
controller = $controller('homeController',{$scope:scope});
}));
it('should return true',function(){
expect(scope.helloWorld()).toBe('hello');
});
});
in the homecontroller i have this:
$scope.helloWorld = function(){
return 'hello';
};
test runs faulty and said 'undefined is not a function', what is the problem?
(function (angular) {
// Create module
var myApp = angular.module('myApp', []);
myApp.controller('homeController', function($scope){
$scope.helloWorld = function(){
return 'hello';
};
});
})(angular);
// ---SPECS-------------------------
describe('myApp', function () {
beforeEach(module('myApp'));
var scope,controller;
beforeEach(inject(function($rootScope,$controller){
scope = $rootScope.$new(),
controller = $controller('homeController',{$scope:scope});
}));
it('should return true',function(){
expect(scope.helloWorld()).toBe('hello');
});
});
Just create a TestCase here.
And it passed.

Why won't $scope be found in my AngularJS / Karma unit test?

My controller:
angularMoonApp.controller('SourceController', ['$scope', '$rootScope', '$routeParams', 'fileService', function ($scope, $rootScope, $routeParams, fileService) {
$scope.init = function() {
$rootScope.currentItem = 'source';
fileService.getContents($routeParams.path).then(function(response) {
$scope.contents = response.data;
$scope.fileContents = null;
if(_.isArray($scope.contents)) {
// We have a listing of files
$scope.breadcrumbPath = response.data[0].path.split('/');
} else {
// We have one file
$scope.breadcrumbPath = response.data.path.split('/');
$scope.breadcrumbPath.push('');
$scope.fileContents = atob(response.data.content);
fileService.getCommits(response.data.path).then(function(response) {
$scope.commits = response.data;
});
}
});
}
$scope.init();
}]);
My unit test:
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, $routeParams, $q, createController, fileService, deferred;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$routeParams = $injector.get('$routeParams');
$scope = $rootScope.$new();
$q = $injector.get('$q');
deferred = $q.defer();
fileService = $injector.get('fileService');
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$routeParams': $routeParams,
'fileService': fileService
});
};
}));
it("should set the current menu item to 'source'", function() {
createController();
$scope.init();
expect($rootScope.currentItem).toBe('source');
});
it("should get test the getContents call of the fileService", function() {
spyOn(fileService, 'getContents').andCallThrough();
createController();
$scope.init();
expect(fileService.getContents).toHaveBeenCalled();
});
it("should return an object with multiple files", function() {
var multipleFiles = [{path: '.DS_Store'}, {path: '.bowerrc'}];
deferred.resolve(multipleFiles);
spyOn(fileService, 'getContents').andReturn(deferred.promise);
createController();
$scope.init();
expect($scope.contents).toBe(multipleFiles);
expect($scope.breadcrumbPath).toBe('');
});
});
})();
The last test fails with:
Expected undefined to be [ { path : '.DS_Store' }, { path : '.bowerrc' } ].
Expected undefined to be ''.
Why is the $scope undefined here?
Your controller is expecting you to inject in $rootScope which you are not doing in your unit test.
You have:
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$routeParams': $routeParams,
'fileService': fileService
});
But but should have:
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$rootScope': $rootScope,
'$routeParams': $routeParams,
'fileService': fileService
});
Also, you will want to call this code:
createController();
$scope.init();
before you resolve your promise:
deferred.resolve(multipleFiles);
The scope is not undefined. What is undefined is $scope.contents and $scope.breadcrumbPath.
And that's because promise callbacks are always being called asynchronously. You need to call
$scope.$apply()
before verifying your expectations.

Resources