How Testability Inspires AngularJS Design / Ran Mizrahi

556 views

Published on

Testability is a major part of design decision making in Angular`s development.

In this sessions we’ll cover what testability is, how it inspires Angular`s design and why it’s good for us.

Published in: Technology, Design
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
556
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
10
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

How Testability Inspires AngularJS Design / Ran Mizrahi

  1. 1. ng-conf 2014 Israel big thanks to our sponsors:
  2. 2. How { Testability } Inspires AngularJS Design Ran Mizrahi (@ranm8) Founder and CEO @ CoCycles
  3. 3. About { Me } • Founder and CEO of CoCycles.
 • Former Open Source Dpt. Leader of CodeOasis.
 • Architected and managed the Wix App Market Development.
 • Full-stack and hands-on software engineer.

  4. 4. What is { Testability } ? “AngularJS - Web Framework Designed with { Testability } in Mind “ How many times have you heard that?
  5. 5. What is { Testability } ? Why is { Testability } so important???
  6. 6. What is { Testability } ? “Software testability is the degree to which a software artifact supports testing in a given test context” — Wikipedia
  7. 7. What is { Testability } ? Can I maintain tests? Can I test the code? • Unmaintainable tests makes code untestable.
 • Broken tests that now one never bothers to fix
 • One tiny change - many tests break (inc. unrelated ones) • Lots of untested code / poor code coverage
 • Monkey patching.

  8. 8. { Global } State function foo() { var bar = window.bar; ! if (bar) { return null; } ! return bar; } Testing global states is difficult and forces “monkey patching” (which is not applicable in all languages)… function foo(bar) { ! ! if (bar) { return null; } ! return bar; } Use of global state: Injecting the dependency:
  9. 9. { Global } State “Insanity: doing the same thing over and over again and expecting different results.” — Einstien
  10. 10. { Global } State describe('foo', function() { // global it('should return null since `window.bar` is falsy', function() { window.bar = false; expect(foo()).to.be.null; }); //injected it('should return bar since window.bar is truthy', function() { expect(foo(‘hi’)).to.be(‘hi'); }); }); Avoiding global state makes our code more: • Reliable • Polymorphic • Decoupled
  11. 11. { Coupling } Units function doSomethingWithBar(bar) { return bar.doSomething(); } ! doSomethingWithBar(new Bar()); doSomethingWithBar(new WalkingBar()); function doSomethingWithBar() { var bar = new Bar(); return bar.doSomething(); } Coupled unit: Decoupled unit: • Coupled to specific implementation. • Can be tested only with monkey patching. • (Almost ) Impossible to test with complicated implementations. • Decoupled from specific implementation. • Can be easily tested (replaced with a mock).
  12. 12. { Coupling } Units describe('#doSomethingWithBar()', function() { var bar; beforeEach(function() { bar = { doSomething: sinon.stub().returns('something') }; }); it('should call bar.doSomething and return', function() { var result = doSomethingWithBar(bar); expect(result).to.equal('something'); expect(bar.doSomething).to.have.been.called; }); }); And it’s much easier to test (no need to handle the new operator in our tests)
  13. 13. Code { Complexity } function kill(bill) { if (bill.talker() != null) { bill.talk(); return "Bill talked"; } ! if (bill.isDead()) { bill.resurrect(); ! return "Bill Resurrected"; } ! if (bill.isAlive()) { bill.kill(); ! return "Bill is now dead"; } ! if (bill.isCool()) { bill.cool(); ! return "Bill is now cool"; } ! return "Nothing happened"; } • This code has five different possible outcomes. ! • Hard to predict, debug and maintain. ! • Testing it requires lots of code.
 • No one else but you, would dare to maintain this test (-:
 • Bad separation of concerns.
 • For desert, we need only five different version of mocks.
  14. 14. Code { Complexity } ! describe('kill', function() { var bill; ! beforeEach(function() { bill = { talker: sinon.stub(), isDead: sinon.stub(), isAlive: sinon.stub() isCool: sinon.stub() }; }); it('should make bill talk', function() { bill.talker.returns(true); ! expect(kill(bill)).to.equal('Bill talked'); }); ! it('should resurrect bill', function() { bill.isDead.returns(true); expect(kill(bill)).to.equal('Bill Resurrected'); }); ! it('should kill bill', function() { bill.isAlive(true); ! expect(kill(bill)).to.equal('Bill is now dead'); }); ! it('should notice that nothing happened', function(){ expect(kill(bill)).to.equal('Nothing happened'); }); ! it('should make bill extremely cool', function() { bill.isCool(true); ! expect(kill(bill)).to.equal('Bill is now cool'); }); }); Sorry it’s small, but it makes my point (-:
  15. 15. Object { Dependencies } function foo(bar, boo, bla, beer) { function doComplicatedStuff() { return bla.sayHi() + boo.sayPizza() + bla.sayBla() + beer.drink(); } return { getSome: function() { return doComplicatedStuff(); } }; } • Many dependencies === Too many responsibilities. • Too much mocks to maintain. • Bad separation of concerns.
  16. 16. Object { Dependencies } describe('foo', function() { var boo = {}, bar = {}, bla = {}, beer = {}; beforeEach(function() { bla.sayHi = sinon.stub().returns('hi '); boo.sayPizza = sinon.stub().returns('pizza '); bla.sayBla = sinon.stub().returns('bla '); beek.drink = sinon.stub().returns('glagla '); }); describe('#getSome()', function() { it('should call all dependencies explicitly and returns and concatenated string', function() { var foo = foo(bar, boo, bla, beer); expect(foo.getSome()).to.equal('hi pizza bla glagla'); }); }); }); • The more dependencies, the harder to test. • Too much mocks to maintain.
  17. 17. Other { Signs } Other signs that makes code untestable: ! • Constructor does heavy work.
 • Object are passed but never used directly.
 • Singletons.
 • Unit do too much jobs.
 • Side effects
  18. 18. { Testability } is Good! Testability === Code Quality Polymorphic Encapsulated Decoupled { Maintainable } and { Predictable } Code
  19. 19. { Testability } in AngularJS So, What Makes AngularJS { Testable} ???
  20. 20. { Angular’s } Approach • Angular approach is declarative and not imperative.
 • Separates controller-logic from DOM using directives and data binding.
 • Less code === less to test.
  21. 21. { Dependency } Injection • Angular inject the requested service by the function argument names (declarative approach).
 • Can also be done with an array. ! • Once requested Angular’s injector would instantiate the requested service and inject it. angular.module('myModule') .controller('MyCtrl', MyCtrl); function MyCtrl($http) { $http.get('http://google.com').then(getTheMonkey); }
  22. 22. { Dependency } Injection • Allows minifiers to preserve argument names for the dependency injection to work with. 
 • More flexible - Separates dependency declaration from your unit. angular.module('myModule') .controller('MyCtrl', ['$http', MyCtrl]); function MyCtrl($http) { $http.get('http://google.com').then(getTheMonkey); }
  23. 23. DOM - Controller { Separation } angular.module('myModule') .controller('MyCtrl', ['$scope', MyCtrl]); function MyCtrl($scope) { $scope.array = ['one', 'two', 'three']; $scope.addAnotherOne = function(string) { $scope.array.push(string); } $scope.removeFirst = function() { $scope.array.shift(); } } <div ng-controller="MyCtrl"> <ul> <li ng-repeat="property in array">{{ property }}</li> </ul> <a ng-click="removeFirst()">Remove the first property<a> </div>
  24. 24. DOM - Controller { Separation } • Two-way data-binding leaves your controller clean from DOM manipulation and makes it easier to test. 
 • Less code.
 • Decouples the view from the controller. ! ! describe('MyCtrl', function() { var createController, scope; beforeEach(inject(function($rootScope, $controller) { scope = $rootScope.$new(); createController = function() { return $controller('MainCtrl', { $scope: scope }) } })); it('should remove one property from the array', function() { var controller = createController(); // test your controller }); });
  25. 25. { Directives } • Directives handles the responsibility of DOM manipulation.
 • They separate the DOM from your code by avoiding the use of CSS selectors.
 • Easy to reuse across different applications and contexts. app.directive('sampleOne', function (){ return function(scope, elm, attrs) { elm.bind('click', function(){ elm.text(scope.$eval(attrs.sampleOne)); }); }; });
  26. 26. { Directives } describe('Testing sampleOne directive', function() { var scope, elem, directive, compiled, html; beforeEach(function (){ html = '<div sample-one="foo"></div>'; inject(function($compile, $rootScope) { scope = $rootScope.$new(); elm = angular.element(html); compiled = $compile(elm)(scope); scope.$digest(); }); }); ! it('Should set the text of the element to whatever was passed.', function() { scope.foo = 'bar'; expect(elem.text()).toBe(''); elm[0].click(); expect(elem.text()).toBe('bar'); }); });
  27. 27. { Providers/Services/Factories } • Providers allows you to separate configuration phase from run phase.
 • Separate your code to small and reusable units.
 • Providers can be easily isolated and tested. angular.module('myModule') .provider('myHttp', myHttp); function myHttp() { var baseUrl; this.baseUrl = function(value) { if (!value) { return baseUrl; } baseUrl = value; }; this.$get = ['$q', function() { // myHttp service implementation... }]; }
  28. 28. Configuration Phase Run Phase • Runs before any service was instantiated. ! • Only providers can be injected. ! • Each provider is injected with the “Provider” suffix (e.g. $locationProvider) ! • Allows to purely configure the services state. • Services state should be not be changed now (already configured during run phase). ! • Providers now cannot be injected. { Providers/Services/Factories }
  29. 29. Testing { Providers/Services/Factories } describe('myHttp', function() { ! var mockQ = { then: function(){} }, http; beforeEach(module(function($provide) { $provide.value('$q', mockQ); })); beforeEach(inject(function(myHttp) { http = myHttp; })); describe('#get()', function() { it('should return a promise', function() { // test your code here }); }); });
  30. 30. { Wrappers } angular.module('myModule') .provider('someProvider', ['$window', someProvider]); function someProvider($window, $) { this.$get = function() { $window.alert('hey there'); } } ! • Angular provides wrappers to common global objects.
 • It allows to easily test global properties without having to monkey patch the window object. ! • Wrappers are injected with dependency injection.
  31. 31. Any { Questions } ?

×