I am writing unit tests for angularjs application. I am using karma,jasmine as the tools for writing and testing the unit test cases.
Below are code components:
app.js
(function() {
"use strict";
angular.module("indirectTax.core", []);
angular.module('indirectTax', ['mgcrea.ngStrap', 'ngRoute', 'ngSanitize', 'cfp.hotkeys', 'ngUpload', 'LocalStorageModule', 'indirectTax.core', 'angulartics', 'ng.shims.placeholder', 'brantwills.paging', 'ngMask']).config(['$routeProvider',
function($routeProvider) {
$routeProvider.when('/dashboard', {
templateUrl: 'dashboard/dashboard.html',
controller: 'DashboardController as ctrl'
}).when('/terms/:id', {
templateUrl: 'terms/terms.html',
controller: 'TermsController as ctrl'
}).otherwise({
redirectTo: '/dashboard'
});
}
]).config(["$httpProvider",
function($httpProvider) {
// Disable IE9 Ajax Request Caching.
if (!$httpProvider.defaults.headers.get) {
$httpProvider.defaults.headers.get = {};
}
}
]).config(['hotkeysProvider',
function(hotkeysProvider) {
// Override the original hotkeys template, in order to add a wrapper
hotkeysProvider.template = "<div ng-include=\"'hotkeys-template.html'\"></div>";
hotkeysProvider.cheatSheetDescription = 'Show / Hide current shortcuts';
}
]).run(['initData', 'core.lib.common', '$rootScope', 'hotkeys', 'localStorageService', '$location',
function(initData, common, $rootScope, hotkeys, localStorageService, $location) {
common.setConfig(initData.configFile);
common.currentUser = initData.currentUser;
$rootScope.$on('$viewContentLoaded', function(event) {
angular.element('#main-footer').fadeIn(500);
});
}
]).constant("toastr", toastr).constant("interact", interact).constant("html2canvas", html2canvas);
})();
users-details-controller.js
var UsersDetailsController = (function () {
function UsersDetailsController($controller, userService, $location, $route, common) {
this.userService = userService;
this.$location = $location;
this.$route = $route;
this.common = common;
this.loading = true;
this.predicate = "userName";
this.reverse = false;
this.searchKey = "";
this.users = [];
this.activeAmount = 0;
this.inactiveAmount = 0;
this.checkPermissions();
this.init();
}
// Only root users should have access to the "User Details" page
UsersDetailsController.prototype.checkPermissions = function () {
if (this.common.currentUser.role !== this.common.constants.ROOT_ROLE) {
this.$location.path("/dashboard");
}
};
UsersDetailsController.prototype.init = function () {
this.getUserList();
};
///////////
//API Calls
//Get selected question set info
UsersDetailsController.prototype.getUserList = function () {
var self = this;
self.loading = true;
self.userService.getUsersList().then(function (data) {
self.loading = false;
self.users = data.users;
self.activeAmount = data.activeUsersCount;
self.inactiveAmount = data.inactiveUsersCount;
}, function (data) {
self.loading = false;
});
};
UsersDetailsController.$inject = ['$controller', 'core.api.user', '$location', '$route', 'core.lib.common'];
return UsersDetailsController;
})();
angular.module('indirectTax').controller('UsersDetailsController', UsersDetailsController);
users-details-controller-spec.js
describe("user-details-controller", function () {
// Before each test runs we're going to need ourselves an Angular App to test - go fetch!
beforeEach(function () {
module("indirectTax"); // module is an alias for angular.mock.module
});
// Tests for the sageDetail controller
describe("usersDetailsController ->", function () {
var $rootScope, $scope, $q, $location, common, userService, //controller depedencies
usersDetailsController; // controller
// Before each test runs set up the controller using inject - an alias for angular.mock.inject
beforeEach(inject(function (_$controller_, _$rootScope_, _$q_, _$location_, _common_, _userService_) {
$scope = {};
// Note how each parameter is prefixed and suffixed with "_" - this an Angular nicety
// which allows you to have variables in your tests with the original reference name.
// So here we assign the injected parameters to the describe-scoped variables:
$rootScope = _$rootScope_;
$q = _$q_;
$location = _$location_;
common = _common_;
userService = _userService_;
// create a usersDetails controller and inject the dependencies we have set up
usersDetailsController = _$controller_("UsersDetailsController", {
$scope: $scope,
$location: $location,
common: common,
userService: userService
});
}));
it("controller should have initial predicate value 'userName'", function () {
// tests this code has executed:
//this.predicate = "userName";
expect(usersDetailsController.predicate).toBe("userName");
});
});
});
SpecRunner.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Jasmine Spec Runner v2.3.4</title>
<link rel="shortcut icon" type="image/png" href="libs/jasmine-2.3.4/jasmine_favicon.png">
<link rel="stylesheet" href="libs/jasmine-2.3.4/jasmine.css">
<script src="libs/jasmine-2.3.4/jasmine.js"></script>
<script src="libs/jasmine-2.3.4/jasmine-html.js"></script>
<script src="libs/jasmine-2.3.4/boot.js"></script>
<!-- inject:vendor:js -->
<script src="libs/jquery.js"></script>
<script src="libs/angular.js"></script>
<script src="libs/angular-mocks.js"></script>
<script src="libs/angular-local-storage.js"></script>
<script src="libs/angular-route.js"></script>
<script src="libs/angular-sanitize.js"></script>
<script src="libs/angular-shims-placeholder.js"></script>
<script src="libs/angular-strap.js"></script>
<script src="libs/angular-strap.tpl.js"></script>
<script src="libs/angulartics.js"></script>
<script src="libs/deferred-bootstrap.js"></script>
<script src="libs/hotkeys.js"></script>
<script src="libs/html2canvas.js"></script>
<script src="libs/interact.js"></script>
<script src="libs/moment.js"></script>
<script src="libs/ng-upload.js"></script>
<script src="libs/ngMask.min.js"></script>
<script src="libs/paging.js"></script>
<script src="libs/toastr.js"></script>
<!-- endinject -->
<!-- inject:js -->
<script src="core/app.js"></script>
<script src="core/lib/init.js"></script>
<script src="core/api/user-service.js"></script>
<script src="core/lib/analytics.js"></script>
<script src="core/lib/common.js"></script>
<script src="core/lib/ng-interact.js"></script>
<!-- include source files here... -->
<script src="admin/users-details-controller.js"></script>
<!-- include spec files here... -->
<script src="spec/admin/users-details-controller-spec.js"></script>
</head>
<body>
</body>
</html>
Now I am triggering the http-server and trying to run the SpecRunner.html file. But I am getting the below mentioned error:
init.js
var Core;
(function (Core) {
var Lib;
(function (Lib) {
var Init = (function () {
function Init($http, $q, common, user) {
this.$http = $http;
this.$q = $q;
this.common = common;
this.user = user;
this.initData = new Core.Models.InitData();
}
Init.prototype.resolve = function () {
var self = this;
var deferred = this.$q.defer();
this.$q.all([this.loadConfig()]).then(function () {
return self.getCurrentUser(self.getAPIURL());
}).then(function () {
deferred.resolve(self.initData);
});
return deferred.promise;
};
Init.prototype.loadConfig = function () {
var self = this;
var deferred = this.$q.defer();
self.$http.get(self.common.constants.CONFIG_FILE).then(function (response) {
self.initData.configFile = response.data;
deferred.resolve();
});
return deferred.promise;
};
Init.prototype.getCurrentUser = function (api_url) {
var self = this;
var deferred = this.$q.defer();
self.user.getCurrent(api_url).then(function (user) {
self.initData.currentUser = user;
deferred.resolve();
});
return deferred.promise;
};
Init.prototype.getAPIURL = function () {
if (this.initData.configFile) {
return this.initData.configFile.API_URL;
}
return this.common.constants.API_URL;
};
Init.$inject = ["$http", "$q", "core.lib.common", "core.api.user"];
return Init;
})();
Lib.Init = Init;
angular.module("indirectTax.core").service("core.lib.init", Init);
angular.element(document).ready(function () {
deferredBootstrapper.bootstrap({
element: document.body,
module: "indirectTax",
injectorModules: "indirectTax.core",
resolve: {
initData: ["core.lib.init", function (init) {
return init.resolve();
}]
}
});
});
})(Lib = Core.Lib || (Core.Lib = {}));
})(Core || (Core = {}));
Can anyone guide me to resolve the error?
Related
I am new to angular and creating my first application using angular in it and getting this error.
"[$injector:unpr] Unknown provider: dataServiceProvider <- dataService <- firstctrl"
Below is my code for which i am getting the error.
my aspx page:
<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
<script type="text/javascript" src="../Scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
<script type="text/javascript" src="/_layouts/15/sp.js"></script>
<script src="../Scripts/angular.js"></script>
<script src="../app/app.js"></script>
<script src="../app/shared/services/dataService.js"></script>
<script src="../app/controller/firstctrl.js"></script>
<meta name="WebPartPageExpansion" content="full" />
</asp:Content>
<asp:Content ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
Page Title
</asp:Content>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
<div ng-app="rootapp" ng-controller="firstctrl">
{{first}} {{last}}
</div>
</asp:Content>
App.js
(function () {
'use strict';
angular
.module('rootapp', [
'ngRoute'
])
.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider
.when('/', {
controller: 'firstctrl',
templateURL: ''
})
.otherwise({ redirectTo: '/' });
}]);
})();
Controller JS
firstctrl.js
(function () {
'use strict';
var hostweburl;
var appweburl;
var SPLanguage;
var SPClientTag;
var SPProductNumber;
var context = SP.ClientContext.get_current();
var user = context.get_web().get_currentUser();
var UserDetails = "";
var ProjectList = "";
var queryStringValue = {};
var defaultQueryStringVal = '';
var ready = false;
var digest = "";
var UserEmailID = "";
var ExtLog = "";
var lineno = 0;
var country = '';
var ip = '';
var city = '';
var SettingsList = '';
var CurrentUser;
var btntext;
var UID;
var ConsolidatedValue;
var LiceKey;
var checkboxval;
var HostFlag;
angular
.module('rootapp', [])
.controller('firstctrl', ["$scope", "dataService", function ($scope, dataService) {
$scope.first = "John";
$scope.last = "Doe";
parseQueryString();
//replaced my api-key value from x
TogetCountry("https://api.ipdata.co/?api-key=xxxxxxxxxxxxxxxxxxxxxxxxxx");
function parseQueryString() {
var qstr = document.URL.split("?")[1].split("&");
var a = qstr;
for (var i = 0; i < a.length; i++) {
var b = a[i].split('=');
queryStringValue[decodeURIComponent(b[0])] = decodeURIComponent(b[1] || '');
}
SPLanguage = queryStringValue.SPLanguage || '';
SPClientTag = queryStringValue.SPClientTag || '';
SPProductNumber = queryStringValue.SPProductNumber || '';
hostweburl = queryStringValue.SPHostUrl;
appweburl = queryStringValue.SPAppWebUrl;
defaultQueryStringVal = "SPHostUrl=" + hostweburl + "&SPLanguage=" + SPLanguage + "&SPClientTag=" + SPClientTag + "&SPProductNumber=" + SPProductNumber + "&SPAppWebUrl=" + appweburl;
console.log("defaultQueryStringVal-----", defaultQueryStringVal);
}
function TogetCountry(url) {
console.log("TogetCountry");
dataService.GetCountry(url).then(function (result) {
console.log("result", result);
});
console.log("---------" + country, city, ip);
}
}]);
})();
dataservice.js
(function () {
'use strict';
angular
.module('rootapp')
.service('dataService', ['$http', '$q', function ($http, $q) {
var service = {};
service.GetCountry = function (URL) {
var deferred = $q.defer();
$http.get(URL).then(function (result) {
deferred.resolve(result.data);
}, function () {
deferred.reject();
});
return deferred.promise;
}
return service;
}]);
})();
Appreciate if get any help in resolving this.
You're defining rootapp twice. Once in App.js and once in firstctrl.js. You need to remove the [] from .module('rootapp', []) in firstctrl.js.
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.
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>
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>
I am new in writing unit test case using jasmine. I am trying to test a resource based service in a controller fetching real data from server but not getting the desired results. Please provide your inputs where I am doing wrong. Below is the unit test I have written :
describe('NewStarterController', function() {
beforeEach(module('myApp'));
var scope, ctrl, $resource;
var context = 'http://localhost.com:8085/AngularPrototype/home';
beforeEach(inject(function(_$resource_, $rootScope, $controller) {
$resource = _$resource_;
NewStarter = {
getNSData : function() {
return $resource(context + '/selfservice/newstarter/get', {}, {
get : {
method : 'GET'
}
});
}
};
scope = $rootScope.$new();
$controller('NewStarterController', {
$scope : scope,
NewStarter : NewStarter
});
}));
it('should test new starter data', function() {
NewStarter.getNSData().get({}).$promise.then(function(response) {
scope.refData = response;
scope.data();
});
expect(scope.data.length).toEqual(2);
expect(NewStarter.getNSData().get.length).toEqual(2);
expect(NewStarter.getNSData().get()).toEqual('formTitleNode');
});
});
Below is the jasmine test result ::
Jasmine 1.3.1 revision 1354556913finished in 0.183s
•
No try/catchFailing 1 spec1 spec | 1 failing
NewStarterController
should test new starter data
NewStarterController should test new starter data.
Expected 0 to equal 2.
Expected 4 to equal 2.
Expected { $promise : { $$state : { status : 0 } }, $resolved : false } to equal 'formTitleNode'.
get.json -> represents the data that is returned from server(Data returned from RestController) on hitting the url : http://localhost.com:8085/AngularPrototype/home/selfservice/newstarter/get
SpecRunner.html-> is used to run the jasmine unit test. Below are the contents of this file :
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Jasmine Demo</title>
<script>
libraries here
</script>
</head>
<body>
<!-- Include Angular and Angular Mocks-->
<script>libraries here </script>
<!-- Include your source files here... -->
<script src="services/services.js"></script>
<script src="myApp.js"></script>
<script src="controller/selfservice/NewStarterController.js"></script>
<!-- Include your spec files here... -->
<script src="jasmineunittest/NewStarterControllerTest.js"></script>
<!-- Jasmine execution script (Standard Jasmine Bolierplate)-->
<script type="text/javascript">
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.execute();
</script>
</body>
</html>
Below is the controller code ( NewStarterController.js ) which is unit tested :
myApp.controller("NewStarterController", ['$scope','$modal', '$state', '$sce', 'NewStarter', function($scope,$modal, $state, $sce, NewStarter) {
$scope.data = function() {
NewStarter.getNSData().get({}, function(response) {
$scope.refData = response;
$scope.formTitle = [];
$scope.displayOptions = [];
angular.forEach($scope.refData.formTitleNode, function(contentItem)
{
var content = -1;
var title = -1;
angular.forEach(contentItem.fieldList, function(fieldBean)
{
if(fieldBean.fieldName == 'TITLE')
{
title = fieldBean.fieldValue;
}
if(fieldBean.fieldName == 'FORMATTEDCONTENTS')
{
content = fieldBean.fieldValue;
}
})
$scope.formTitle.push({title:title, content:content});
})
angular.forEach($scope.refData.displayOptionsNode, function(contentItem)
{
var nsContent = -1;
var nsTitle = -1;
angular.forEach(contentItem.fieldList, function(fieldBean)
{
if(fieldBean.fieldName == 'TITLE')
{
nsTitle = fieldBean.fieldValue;
}
if(fieldBean.fieldName == 'FORMATTEDCONTENTS')
{
nsContent = fieldBean.fieldValue;
}
})
$scope.displayOptions.push({nsTitle:nsTitle, nsContent:nsContent});
$scope.selectedOption = '';
})
$scope.hasNewStarterSubmitted = $scope.refData.hasNewStarterSubmitted;
$scope.submittedSelectedOption = $scope.refData.submittedSelectedOption;
$scope.submittedStudentLoan = $scope.refData.submittedStudentLoan;
$scope.nsAlreadySubmittedValidationMsg = $scope.refData.nsAlreadySubmittedValidationMsg;
console.log('Success', response);
}, function(error) {
//alert(JSON.stringfy(error))
$scope.contentLoadErrorMsg=error.data;
//console.error('ERR', error);
});
}
}]);
Below is the code in services.js
myApp.factory('NewStarter', function($resource, context) {
return {
getNSData: function() {
return $resource(context + '/selfservice/newstarter/get', {}, {get: {method:'GET'}});
},
submitNSForm: function() {
return $resource(context + '/selfservice/newstarter/submit', {}, {submit: {method:'POST'}});
}
}
});
Below is the code in myApp.js
var myApp = angular.module('myApp', ['ngResource', 'ngSanitize', 'ui.bootstrap', 'ui.router']);
myApp.constant('context', '/AngularPrototype/home');
myApp.constant('projectName', '/AngularPrototype');
myApp.config(function($stateProvider, $urlRouterProvider, projectName) {
$urlRouterProvider.otherwise('/home');
$stateProvider .state('newStarter', {
url:'/newstarter',
templateUrl: projectName + '/newstarter',
controller: 'NewStarterController'
})
});
Thanks!
Below i tried as per your suggestion but getting error while running the unit test:
describe('NewStarterController', function() {
beforeEach(module('myApp'));
var scope, postDefer,NewStarter;
beforeEach(inject(function(_NewStarter_, $rootScope, $controller,$q) {
NewStarter = _NewStarter_;
postDefer = $q.defer();
spyOn(NewStarter,'getNSData').and.returnValue(postDefer.promise);
scope = $rootScope.$new();
$controller('NewStarterController', {
$scope : scope,
NewStarter : NewStarter
});
}));
it('should test new starter data', function() {
NewStarter.getNSData().get({});
postDefer.resolve();
scope.$apply();
expect(scope.data.length).toEqual(2);
});
});
Below are the errors while running the test :
Error:[$injector:unpr] Unknown provider: NewStarterProvider <- NewStarter
typeError: Unable to get property 'getNSData' of undefined or null reference
Since it is a unit test, you need to focus on testing the single unit, you should try to mock all dependencies as much as possible. Having say that, using real $resource in your unit test is not desirable.
What I think you should do is: -
1. Mock NewStarter.getNSData(), or whatever service methods get called in controller
2. Return a fake resource object
3. mock get method of the fake resource (as it is what's used in your controller) and return a promise
4. resolve the promise to test your success handler.
Check out this plunker http://plnkr.co/edit/8l3mY1?p=preview
var mockResource = {get: function(){}}, $q;
beforeEach(inject(function($rootScope, $controller, _$q_, NewStarter) {
$scope = $rootScope.$new();
$q = _$q_;
spyOn(NewStarter, 'getNSData').and.callFake(function() {
return mockResource;
});
ctrl = $controller('MainCtrl', {
$scope: $scope
});
}));
it('should return value', function() {
var responseObj = {id: 1, name:'test'};
spyOn(mockResource, 'get').and.callFake(function(data, func){
var deferred = $q.defer();
deferred.resolve(responseObj);
return deferred.promise.then(func);
});
$scope.data();
expect(mockResource.get).toHaveBeenCalled();
$scope.$apply();
expect($scope.refData).toEqual(responseObj);
});