AngularJS Unit testing using Karma - Part 1
Visual Studio Code – is new editor microsoft has released which can used cross-platform. We will use the same to write unit test for Angularjs using Karma.
Download for Visual Studio Code link is here
Karma is a tool that allows you to execute JavaScript code in multiple real browsers.The main purpose of Karma is to make your test-driven development easy, fast, and fun
Karma runs on Node.js and is available as a node module via NPM.
Install node.js from the link here
Open a folder in the Visual Studio Code editor as shown.
Run the following command in the node.js command prompt to install the npm packages required for setting up karma
npm install karma
Run the below command to create the configuration file <code>karma init</code> to create a configuration file(Karma.config.js) for the unit test to run. The input for this are
1)The test framework which can be used :In my case selecting the Jasmine which is a behavior driven development framework for JavaScript that has become the most popular choice for testing Angular applications
2)Capturing the browser to run the test
3)The test files path that need to be used
Install the below required npm packages required for running the tests
npm install karma-angular
angular-mocks provides mocking for your tests. This is used to inject and mock Angular services within unit tests.
npm install angular-mocks
npm install angular
The files section is updated to include the angular.min.js and angular-mocks.js as shown
Gulp is a JavaScript task runner. We will integrate the Karma unit tests into Gulp.js-based build.
npm install gulp
gulpfile.js file is updated to use the Karma.config.js as shown
Install the other package to run the tests in the browser
npm install karma-jasmine
npm install karma-chrome-launcher
Simple test
describe('hello', function() {
it('should work', function(){
expect(true).toBe(true);
});
})
Testing a controller
angular.module('myApp', [])
.controller('MainController', function MainController($scope) {
$scope.name = "Girish";
$scope.sayHello = function () {
$scope.greeting = "Hello " + $scope.name;
}
})
The first step is to use the module function, which is provided by angular-mocks. This loads in the module it's given, so it is available in your tests. We pass this into beforeEach, which is a function Jasmine provides that lets us run code before each test. Then we can use inject to access $controller, the service that is responsible for instantiating controllers as the controllers are not available on the global scope.
describe('MainController', function() {
beforeEach(module('myApp'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
describe('$scope.greeting', function() {
it('should create $scope.greeting when calling sayHello',
function() {
var $scope = {};
var controller = $controller('MainController', { $scope: $scope });
$scope.name = "Girish";
$scope.sayHello();
expect($scope.greeting).toEqual("Hello Girish");
});
});
});
Testing a complex controller
angular.module('app', [])
.controller('PasswordController', function PasswordController($scope) {
$scope.password = '';
$scope.grade = function() {
var size = $scope.password.length;
if (size > 8) {
$scope.strength = 'strong';
} else if (size > 3) {
$scope.strength = 'medium';
} else {
$scope.strength = 'weak';
}
};
});
describe('PasswordController', function() {
beforeEach(module('app'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
describe('$scope.grade', function() {
it('sets the strength to "strong" if the password length is >8 chars', function() {
var $scope = {};
var controller = $controller('PasswordController', { $scope: $scope });
$scope.password = '';
$scope.grade();
expect($scope.strength).toEqual('strong');
});
});
});
Testing a Service : When an Angular application needs some data from a server, it calls the $http service, which sends the request to a real server using $httpBackend service. With dependency injection, it is easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify the requests and respond with some testing data without sending a request to a real server. There are two ways to specify what test data should be returned as http responses by the mock backend when the code under test makes http requests:
$httpBackend.expect - specifies a request expectation
$httpBackend.when - specifies a backend definition
The $httpBackend used always responds to requests asynchronously. Since we are mocking $httpBackend we need to use $httpBackend flush() method, which allows the test to explicitly flush pending requests. This preserves the async API of the backend, while allowing the test to execute synchronously.
angular
.module('MyApp', [])
.controller('MyController', MyController);
// The controller code
function MyController($scope, $http) {
$http.get('https://api.github.com/users/girishgoudar').success(function(data, status, headers) {
$scope.data = data;
});
}
describe('MyController', function() {
var $httpBackend, $rootScope, createController, authRequestHandler;
// Set up the module
beforeEach(module('MyApp'));
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
authRequestHandler = $httpBackend.when('GET', 'https://api.github.com/users/girishgoudar')
.respond(200, { data: 'Girish' });
// Get hold of a scope (i.e. the root scope)
$rootScope = $injector.get('$rootScope');
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
createController = function() {
return $controller('MyController', {'$scope' : $rootScope });
};
}));
it('should get data from github', function() {
$httpBackend.expectGET('https://api.github.com/users/girishgoudar');
var controller = createController();
$httpBackend.flush();
expect($rootScope.data).toEqual({ data: 'Girish' });
});
});
Now run the gulp task(test) from Visual Studio Code to see the tests running successfully
In the next post we will see how to use protractor which is end-to-end test framework for AngularJS applications.
Resources: