writing testable & sharable code in angularjs: xiyang chen from rangle.io

Post on 18-Dec-2014

290 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

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.

TRANSCRIPT

Writing Testable & Shareable Code inAngular

Xiyang Chen

rangle.io

1 / 33

Why?

2 / 33

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

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

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

5 / 33

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

6 / 33

Overall Guidelines

7 / 33

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

8 / 33

No DOM Manipulation insideControllers

Use service and directives instead

9 / 33

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

...continued

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

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

/* ... */

}]);

11 / 33

...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

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

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

14 / 33

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

15 / 33

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

Bad for tests

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

17 / 33

..better

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

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

18 / 33

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

19 / 33

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

20 / 33

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

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

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

23 / 33

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

...continued

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

"items" will be available when controller isinitialized

25 / 33

Tips

26 / 33

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

So that dependencies are isolated

27 / 33

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

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

28 / 33

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

29 / 33

Wrap external js library into their own minimalservices

30 / 33

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

31 / 33

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

Or follow me @settinghead or @rangleio

32 / 33

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

top related