Testing AngularJS applications – – Agenda



Testing AngularJS applications – – Agenda

1 0


testing-lecture


On Github tszewcow / testing-lecture

Testing AngularJS applications

Tomasz Szewców

Agenda

Tools Structuring tests Karma configuration Testing Controllers Testing Services Testing Directives E2E tests configuration Protractor locators

Exercises

Test a controller Add a page object

Tools - unit testing

  • Karma - tool used to spawn a web server which loads your application's source code and executes your tests.
  • Jasmine - BDD framework for JavaScript, provides functions to help with structuring tests and making assertions.

https://docs.angularjs.org/guide/unit-testing

Tools - e2e testing

  • Selenium WebDriver - compact Object Oriented API, which wraps user interactions into methods that can be used to drive the browsers.
  • Protractor - end to end test framework for AngularJS applications built on top of WebDriver.

https://docs.angularjs.org/guide/e2e-testing

Structuring tests

describe('A suite', function() {
      it('contains spec with an expectation', function() {
        expect(true).toBe(true);
      });
});

Karma configuration

// install Karma
npm install karma
// install plugins
npm install karma-jasmine
npm install karma-phantomjs-launcher
npm install karma-chrome-launcher
// run Karma
./node_modules/karma/bin/karma start / init / run
// alternative
npm install -g karma-cli
karma start / init / run

http://karma-runner.github.io/0.13/intro/installation.html

Karma configuration file

// can be created with karma init command
module.exports = function (config) {
	config.set({
		basePath: '',
		frameworks: [],
		files: [],
	hostname: 'localhost',
		port: 9876,
		autoWatch: false,
		browsers: [],
		singleRun: false,
	})
};
// for debugging in a browser:
// - set single run to true
// - select other browser

http://karma-runner.github.io/0.8/config/configuration-file.html

Testing a controller

describe('SampleCntl tests', function() {
	'use strict';

	var $scope;

	beforeEach(module('someModule'));

	beforeEach(inject(function($controller, $rootScope){
		$scope = $rootScope.$new();
		$controller('SampleCntl', {$scope: $scope});
	}));

	describe('some suite', function() {
		it('some spec', function() {
			// given 
			// when 
			$scope.someFunction();
			// then
		});
	});
});

Testing a controller - alternative

describe('SampleCntl tests', function() {
	'use strict';

	var cntl;

	beforeEach(module('someModule'));

	beforeEach(inject(function($controller){
		cntl = $controller('SampleCntl', {});
	}));

	describe('some suite', function() {
		it('some spec', function() {
			// given 
			// when 
			cntl.someFunction();
			// then
		});
	});
});

Exercise - test a controller

// 1. create a sample angular module
angular.module('sampleModule', []);

// 2. create a calculator controller with 2 functions
angular.module('sampleModule').controller('CalculatorCntl', function(){
	'use strict';

	this.factorial = function(n){};
	this.divide = function(a, b){};
});

// 3. specify appropriate files in the karma config file

// 4. implement controller's functionality using TDD

Testing controller with mocks

// sample controller code
angular.module('someModule').controller('SomeCntl', function($location){
	'use strict';
	
	this.goToDialog = function(path){
		$location.path(path);
	};
});

// test code

var cntl, locationMock = {
	path: angular.noop
};
			
beforeEach(inject(function($controller){
	// injection of mocked $location service
	cntl = $controller('SomeCntl', {$location: locationMock});
}));

Testing a service

describe('data service tests', function () {
	  'use strict';

	  var someDataService;
	  
	  beforeEach(module('app'));

	  beforeEach(inject(function (_someDataService_) {
		someDataService = _someDataService_;
	  }));

	  describe('get data method', function () {
		  it('should return data', function () {
			  // given
			  var data = [];
			  // when
			  data = someDataService.getData();
			  // then
			  expect(data.length).toEqual(10);
		  });
	  });
});

Testing service with mocks

// sample service code
angular.module('someModule').factory('serviceUnderTests', function('otherService'){
	'use strict';
	var data = [];
	
	return {
		getData: function(){
			angular.copy(otherService.getData(), data);
		},
		getCurrent: function(){
			return data;
		}
	};
});

// test code
var otherServiceMock = {getData: function(){return [1,2,3]}};
var serviceUnderTests;

beforeEach(function(){
	module('someModule');
	
	module(function($provide){
		// injecting other service with $provide service
		$provide.value('otherService', otherServiceMock);
	);
});
beforeEach(function(_serviceUnderTests_){
	serviceUnderTests = _serviceUnderTests_;
});

Testing with $httpBackend

var booksData, $httpBackend;

beforeEach(inject(function (_booksData_, _$httpBackend_) {
	booksData = _booksData_;
	$httpBackend = _$httpBackend_;
}));

afterEach(function () {
	// then
	$httpBackend.verifyNoOutstandingExpectation();
	$httpBackend.verifyNoOutstandingRequest();
});

it('should load books', function () {
	// given
	var searchParams = {title: 'title', author: 'author'}, books = [], response = [
		{id: 0, title: 'title1'},
		{id: 1, title: 'title2'}
	];
	$httpBackend.expectGET('/books-management/books-list/books.json?author=author&title=title').respond(response);
	// when
	booksData.getBooks(searchParams).then(function (response) {
	books = response.data;
	});
	$httpBackend.flush();
	// then
	expect(books).toEqual(response);
	});

Testing a directive

describe('testing directive', function() {
          'use strict';

          var $compile, $rootScope;

          beforeEach(module('moduleName'));
          beforeEach(inject(function(_$compile_, _$rootScope_){
              $compile = _$compile_;
              $rootScope = _$rootScope_;
          }));

          it('should replace the directive with an appropriate content', function() {
              // given when
              var element = $compile('<directive-name></directive-name>')($rootScope);
              $rootScope.$digest();
              // then
              expect(element.html()).toContain('expected content');
          });
});

E2E tests configuration

// install protractor globally with the node package manager
npm install -g protractor
// download webdriver 
webdriver-manager update
// start selenium server
webdriver-manager start
// prepare the configuration file
exports.config = {
	seleniumAddress: 'http://localhost:4444/wd/hub',
	specs: ['todo-spec.js']
};
// run e2e tests
protractor [name-of-config-file]

Configuration file options

// running tests in other browsers
exports.config = {
  seleniumAddress: 'http://localhost:4444/wd/hub',
  specs: ['spec.js'],
  capabilities: {
	browserName: 'firefox'
  }
}

// running tests in many browsers
exports.config = {
  seleniumAddress: 'http://localhost:4444/wd/hub',
  specs: ['spec.js'],
  multiCapabilities: [{
	browserName: 'firefox'
  }, {
	browserName: 'chrome'
  }]
}

https://github.com/angular/protractor/blob/master/docs/referenceConf.js

Protractor locators

// by binding
element(by.binding('item.name'));

// by model
element(by.model('item.name'));

// by css
element(by.css('some-css'));

// shorthand for css selectors
$('my-css') // the same as element(by.css('my-css'))

// by button text
element(by.buttonText('buttonText'));

// by tag name
element(by.tagName('tag-name'));

// by repeater
element.all(by.repeater('repeater'));

https://angular.github.io/protractor/#/api?view=ProtractorBy

Actions

var el = element(locator);

// click on the element
el.click();

// send keys to the element (usually an input)
el.sendKeys('my text');

// clear the text in an element (usually an input)
el.clear();

// get the value of an attribute, for example, get the value of an input
el.getAttribute('value');

https://angular.github.io/protractor/#/api?view=ElementFinder

Page Objects

  • The methods represent the services that the page offers
  • Try not to expose the internals of the page
  • Generally don't make assertions
  • Methods return other PageObjects
  • Need not represent an entire page
  • Different results for the same action are modelled as different methods

https://code.google.com/p/selenium/wiki/PageObjects

Debugging

// set breakpoint
browser.pause();
// continue to the next step
c
// enter interactive mode
repl
// exit debugging
ctrl + C

Exercise - create a table list PO

// 1. create a page object for tables list dialog
// - page object should have nextPage and getNumOfTables function
// - nextPage has to click on the next page button (use for example by.css selector)
// - getNumOfTables has to retrieve number of rows (use for example by.repeater selector)	  

// 2. use page object in a test together with signIn page object
// - sign in to table management
// - assert number of rows on the first page
// - move to the second page
// - assert number of tables on the second page
Testing AngularJS applications Tomasz Szewców