33
Writing Testable & Shareable Code in Angular Xiyang Chen rangle.io 1 / 33

Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Embed Size (px)

DESCRIPTION

In this slideshare from the AngularJS Toronto Meetup in June 2014 Xiyang shares best practices for testing with AngularJS, using some case study examples from his project work at rangle.io. Xiyang moved to Toronto after a couple of years working for startups in the United States. A week after arriving he ended up at rangle.io, where he has made himself an essential contributor to the many startup clients rangle.io serves. He is a JavaScript fanatic with a passion for testing, scalability, and long-term code maintainability.

Citation preview

Page 1: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Writing Testable & Shareable Code inAngular

Xiyang Chen

rangle.io

1 / 33

Page 2: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Why?

2 / 33

Page 3: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Motivation #1I want adequate test coverage for my and myteam's codebase so that I can enjoy a quality sleepschedule during the week as well as tranquil anduninterrupted weekends.

3 / 33

Page 4: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Motivation #2I want my code to be clear, readable and reusablewith clearly defined specs, so that my colleages orthe suceeding dev team will be appreciative of mywork and thus offer to buy me a beer from time totime for saving them time and money.

4 / 33

Page 5: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Motivation #3I want a testable and maintanable codebase sothat my stakeholder's interest is taken care of.

5 / 33

Page 6: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

ObjectivesWrite frontend code that's friendly for Unit & E2ETestingKeep a maintainable & sharable codebase

6 / 33

Page 7: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Overall Guidelines

7 / 33

Page 8: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

No Big, Giant, Fat ControllersWrap business logic into servicesControllers should only be used to set up scopeand handle view interactions

8 / 33

Page 9: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

No DOM Manipulation insideControllers

Use service and directives instead

9 / 33

Page 10: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

This controller is trying to take over the world

angular.module('awesome-app') .controller('itemController', ['$scope', '$http', function ($scope, $http) {

function transform (items) { /* ... */ }

$scope.getItemList = function () { //Retrieve list of from remote API server $http.get('/api/item').then( function success (success) { $scope.items = transformItems(res.items); }, function error (error) { alert('No item for you!'); }); }; ...

10 / 33

Page 11: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

...continued

$scope.addItem = function (item) { $http({method: 'POST', ...}).then(...); };

$scope.updateItem = function (itemId) { $http({method: 'PUT', ...}).then(...) };

/* ... */

}]);

11 / 33

Page 12: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

...better

angular.module('awsome-app') .service('itemRetriever', [$q, $http, function ($q) { function transform () { ... } this.get = function () { var deferred = $q.defer(); $http.get('/api/items').then(function success (res) { ... }); return deferred.promise; }; });

.controller('itemController', ['$scope', itemRetriever function ($scope, itemRetriever) { itemRetriever.get().then(function (transformedItems) { $scope.items = transformedItems; });

$scope.addItem = function (item) {...};

/* ... */

}]);

12 / 33

Page 13: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Group your code into bundles(modules)

Why? Separating functionalities into modules makes it easier to isolate andtroubleshoot errors during testing

Keep modularization horizontal instead of vertical

13 / 33

Page 14: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Vertical Modularizationangular.module('app', []);angular.module('services', []);angular.module('filters', []);angular.module('controllers', ['services', 'filters']);...

14 / 33

Page 15: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

...betterangular.module('app', []);angular.module('company-api', []);angular.module('products', ['company-api']);angular.module('personnels', ['company-api']);...

15 / 33

Page 16: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

All External References Should Comefrom Dependency Injections

Put or wrap global variable and constants intoangular.value() or angular.constant()Inject them as needed

16 / 33

Page 17: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Bad for tests

.service('myService', function () { ... ThirdPartyLib.externalMethod(...); ... });

17 / 33

Page 18: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

..better

angular.module('app') .factory('ThirdPartyLib', function ($window) { return $window.ThirdPartyLib; });

.service('myService', ['ThirdPartyLib', function (ThirdPartyLib) { ... }]);

18 / 33

Page 19: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Small Controllers, Small ServicesBreak controllers into smaller sub-controllers if itgets too large

19 / 33

Page 20: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Small Controllers, Small ServicesSeparation of Concerns & Single ResponsibilityEliminate the word "and" in your service'sdescriptionServices should be loosely coupled

20 / 33

Page 21: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Too tighly coupledangular.module('app').service('productService', ['$http', 'cartService', 'statsEngine' function ($http, CartService, statsEngine) { this.getProduct = function () {...} this.updateProduct = function (id, product) {...} this.getProductStatistics = function (id) {...} this.addProductToCart = function (id) {...} ... }]);

Would be combersome to mock all thedependencies in tests

21 / 33

Page 22: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

A Better Approach

angular.module('app') .service('productLoader', ['$http', function ($http) { this.getProduct = function () {...} ... }]);

.service('productUpdater', ['$http', function ($http) { this.purchaseProduct = function () {...} ... }]);

.service('productStats', ['$http', function ($statEngine) { this.getProductStats = function () {...} ... }]);

22 / 33

Page 23: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Use Routes + Resolve PatternAvailable in $routeProvider or AngularUI RouterDefine/divide resource loading by routes. Exposestate on routesGood for E2E testing

23 / 33

Page 24: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Route Definition

angular.module("app") .config(["$routeProvider", function routeConfig($routeProvider) { $routeProvider. when("/", { controller: "itemController", templateUrl: "view/items.html", resolve: { items: function ($itemLoader, $q) { var deferred = $q.defer(); itemLoader.loadItems().then( function (items) { deferred.resolve(helper); }); return deferred.promise; } } }); }]);

24 / 33

Page 25: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

...continued

angular.module("app") .controller('itemController', ['items', function (items) { $scope.items = items; }]);

"items" will be available when controller isinitialized

25 / 33

Page 26: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Tips

26 / 33

Page 27: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Use $window, $location, $interval, instead ofwindow, location, setInterval

So that dependencies are isolated

27 / 33

Page 28: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Use $log.info(), .debug(), .error(), etc. instead ofconsole.log

So that your tests won't be litered with log messages

28 / 33

Page 29: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Use angular.copy, angular.extend, angular.forEach

29 / 33

Page 30: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Wrap external js library into their own minimalservices

30 / 33

Page 31: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Use $interval instead of $timeoutSo that E2E testing won’t return a timeout error

31 / 33

Page 32: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

Thank youSlides: http://github.com/settinghead/angular-best-practices-slides

Or follow me @settinghead or @rangleio

32 / 33

Page 33: Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io

ReferencesAngular Best Practices and anti-patternshttps://github.com/angular/angular.js/wiki/Best-PracticesJoe Eames, AngularJS Best Practices, Pluralsight.comMiško Hevery, Writing Testable Code,http://googletesting.blogspot.ca/2008/08/by-miko-hevery-so-you-decided-to.htmlMark Ethan Trostler, Testable JavaScript, O'Reilly Media, Jan 2013

33 / 33