Test Patterns In AngularJS
Agenda
- About Me
- Why?
- Testing Types
- Test Patterns & Examples
About Me
Name: Tomas Miliauskas
Focusing on: Client Side Development
Country: Lithuania
Team: Wix Tigers
Project: Wix Hotels
Wix Hotels
- New Wix vertical
- Internal TPA
- Hybrid App
- Multiwidget
Why?
Testing Types
- Integration/e2e testing (protractor)
Testing an integration between components (scenarios).
- Unit testing (karma)
Testing a particular piece of code (unit, function).
Test Patterns & Examples
Unit Testing
AngularJS Concepts
- Module
- Controller
- Filter
- Directive
- Service
Testing Modules
Module is a container for the different parts of your application.
- Using angular-mock to control DI
- Using module() function
- Usually we don't test modules
- Mock dependencies (do it on purpose)
- Only important part: config/run blocks
- We use jasmine spies to mock.
Testing Modules (run/config)
describe('Back Office config & run phases', function () {
var $rootScope = jasmine.createSpyObj('$rootScope', ['$on', '$watch']);
var $location = jasmine.createSpyObj('$location', ['url']);
var $translate = jasmine.createSpyObj('$translate', ['use']);
var $wix = {};
var clientConfig = {};
var experiments = {};
function createModel() {
module('backOfficeApp', function ($provide) {
$provide.constant('$rootScope', $rootScope);
$provide.constant('$location', $location);
$provide.constant('$translate', $translate);
$provide.constant('$wix', $wix);
$provide.constant('clientConfig', clientConfig);
$provide.constant('experiments', experiments);
});
inject(); // We need to call this to force module to use provided mocks
}
afterEach(function () {
$rootScope.$on.reset();
$location.url.reset();
$translate.use.reset();
$wix = {};
clientConfig = {};
experiments = {};
});
describe('.run()', function () {
describe('deep linking', function () {
beforeEach(createModel);
it('should add state change success event listener', function () {
expect($rootScope.$on).toHaveBeenCalledWith('$stateChangeSuccess', jasmine.any(Function));
});
it('should take the location url', function () {
$location.url.andReturn('/rooms/list');
$rootScope.$on.mostRecentCall.args[1]();
expect($location.url.callCount).toBe(1);
});
it('should push corerct state', function () {
$location.url.andReturn('/ppicker');
$wix.Dashboard = { pushState: jasmine.createSpy() };
$rootScope.$on.mostRecentCall.args[1]();
expect($wix.Dashboard.pushState).toHaveBeenCalledWith('#/ppicker');
});
});
});
});
Testing Controllers
- Using $controller service to test our controllers.
- Passing dependencies through module, $controller service or injector.
- Create a new controller before each test.
- Don't forget to create and pass scope.
describe('Back Office: Controller', function () {
describe('RatesCtrl', function () {
var $rootScope, $controller;
var $scope, showError, promise, rates;
beforeEach(function () {
showError = jasmine.createSpy('showError');
promise = jasmine.createSpyObj('$promise', ['then']);
rates = { $promise: promise, length: 0 };
module('backOfficeApp', { showError: showError });
inject(function ($injector) {
$controller = $injector.get('$controller');
$rootScope = $injector.get('$rootScope');
});
$scope = $rootScope.$new();
$controller('RatesCtrl', {$scope: $scope, rates: rates});
});
it('should set rates', function () {
expect($scope.rates).toBe(rates);
});
it('should set isEmpty', function () {
expect($scope.isEmpty).toBe(false);
promise.then.calls[0].args[0]();
expect($scope.isEmpty).toBe(true);
});
it('should show error', function () {
promise.then.calls[0].args[1]();
expect(showError).toHaveBeenCalled();
});
});
});
Testing Controllers (example)
Testing Filters
describe('capitalize filter', function () {
var capitalize;
beforeEach(function () {
module('hotel');
inject(function ($filter) {
capitalize = $filter('capitalize');
});
});
it('returns empty string if falsy value is given', function () {
expect(capitalize(false)).toBe('');
expect(capitalize(0)).toBe('');
expect(capitalize('')).toBe('');
expect(capitalize(null)).toBe('');
expect(capitalize(undefined)).toBe('');
});
it('capitalizes first word', function () {
expect(capitalize('a few words')).toBe('A few words');
});
it('capitalizes each word', function () {
expect(
capitalize('What is love? Baby don\'t hurt me, don\'t hurt me no more.', true))
.toBe('What Is Love? Baby Don\'t Hurt Me, Don\'t Hurt Me No More.');
});
});
Using $filter service to test our filters.
Testing Directives
- Using $compile service to test our directives.
- Compile an actual HTML with a directive you want to test.
- Need to trigger digest cycle manually.
- Create a scope.
- Spying on jQuery (Lite) element methods - spyOn(element.fn, 'someMethod')
describe('wix-selected', function () {
var $scope, $compile, $rootScope, $state;
var html, elem;
var element = angular.element;
beforeEach(function () {
module('backOfficeApp', {'globalInterceptor': {}});
inject(function ($injector) {
$compile = $injector.get('$compile');
$rootScope = $injector.get('$rootScope');
$state = $injector.get('$state');
});
$scope = $rootScope.$new();
});
function compile(attrs) {
attrs = attrs || [];
html = '<a ' + attrs.join(' ') + ' ui-sref="[\'base.hello.kitty\']" href=""></a>';
elem = $compile(html)($scope);
$scope.$digest();
}
it('should not initialiaze the directive if there is no ui-sref attribute', function () {
spyOn($scope, '$on');
compile();
expect($scope.$on).not.toHaveBeenCalled();
});
it('should initialize when ui-sref attribute exists', function () {
spyOn($scope, '$on');
spyOn(element.fn, 'removeClass');
compile(['wix-selected="[\'base.hello\']"']);
expect($scope.$on).toHaveBeenCalledWith('$stateChangeSuccess', jasmine.any(Function));
expect(element.fn.removeClass).toHaveBeenCalledWith('selected');
});
it('should add class ".selected" when state name matches', function () {
$state.current.name = 'base.hello.kitty';
spyOn(element.fn, 'addClass');
compile(['wix-selected="[\'base.hello\']"']);
expect(element.fn.addClass).toHaveBeenCalledWith('selected');
});
it('should add class ".selected" when at least 1 state name matches', function () {
$state.current.name = 'base.hello.kitty';
spyOn(element.fn, 'addClass');
compile(['wix-selected="[\'base.bye\', \'base.hello\']"']);
expect(element.fn.addClass).toHaveBeenCalledWith('selected');
});
});
Testing Directives (example)
Testing Services
- Service has five recipes: value, factory, service, provider and constant.
- Testing is pretty the same.
- Services have to be injected to test.
A nice explanation: https://docs.angularjs.org/guide/providers
describe('refresher', function () {
var $window;
var storage, refresher, getTime;
var items, siteId = 'simply-wondeful-site-id';
var name = 'wix-booking-' + siteId;
var verify = 'wix-booking-verify-' + siteId;
beforeEach(function () {
items = {};
storage = jasmine.createSpyObj('storage', ['setItem', 'key', 'getItem']);
storage.setItem.andCallFake(function (name, value) { items[name] = value; });
storage.getItem.andCallFake(function (name) { return items[name]; });
getTime = jasmine.createSpy().andReturn(1406851200000);
$window = { localStorage: storage, XDate: { now: getTime } };
module('hotel', function ($provide) {
$provide.value('$window', $window);
$provide.constant('siteConfig', { id: siteId });
});
inject(function ($injector) {
refresher = $injector.get('refresher');
});
});
it('should save initial time', function () {
expect(getTime).toHaveBeenCalled();
});
it('should provide all necessary methods ', function () {
expect(refresher.engage).toEqual(jasmine.any(Function));
expect(refresher.check).toEqual(jasmine.any(Function));
});
describe('.engage()', function () {
it('should store a timestamp', function () {
getTime.andReturn(1406851200001);
refresher.engage();
expect(items[name]).toBe(1406851200001);
});
});
});
Testing Services (example)
Intergration Testing
Using Page Objects (1/2)
module.exports = (function () {
function HomePage() {
}
HomePage.prototype.open = function () {
browser.get("/home");
};
HomePage.prototype.header = function () {
return $('.header span');
};
return HomePage;
});
Using Page Objects (2/2)
var HomePage = require("./pages/home");
describe("scenario 1", function() {
var page = new HomePage();
describe("visit the home page", function() {
it('should show header') {
page.open();
expect(page.header().isDisplayed()).toBeTruthy();
}
});
});
Questions?
Test Patterns In AngularJS
By Tomas Miliauskas
Test Patterns In AngularJS
- 1,865