TAVERNA Components - Semantically annotated and sharable units of functionality
Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io
-
Upload
rangleio -
Category
Technology
-
view
284 -
download
1
description
Transcript of Writing Testable & Sharable Code in AngularJS: Xiyang Chen from rangle.io
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