Writing Easy-ToTest Code Ynon Perek ynon@ynonperek.com http://ynonperek.com
Problem #1 How do you write hard to test code ?
Code Flags • Use global state • Use static methods • Mix object construction with business logic • Mixing find-what-i-need logic with business logic • Write LONG functions • Use many conditionals • Dependency hell • Long inheritance hierarchies
Isolating Logic ItemWidget jQuery Parser TreeWidget Button Array.sort Data Supplier Data Object
Isolating Logic ItemWidget jQuery Parser TreeWidget Button Array.sort Data Supplier Data Object
Isolating Logic ItemWidget Test TreeWidget Button Data Supplier
Isolating Logic Main ItemWidget TreeWidget Test Button Data Supplier
The Code function TreeWidget(ItemWidget, dataSupplier) {
 // ...
 } function TreeWidget() {
 var dataSupplier = new DataSupplier('/music/collection');
 }
If you can isolate it, you can test it
What Can You Test ? colors = ['red', 'blue', 'green', 'yellow', 'cyan', 'magenta'];
 
 $('#btn').html('Click Me');
 
 
 $('#btn').on('click', function() {
 var idx = $('body').attr('data-color');
 idx = Number(idx) + 1 || 0;
 $('body').attr('data-color', idx);
 if ( Number(idx) >= 0 ) {
 $('body').css("background", colors[idx]);
 } else {
 $('body').css('background', colors[0]);
 }
 });
Dependencies • Colors array • DOM structure • jQuery
Let’s Try This One function ColorChanger(colors_array, $btn_el, $body_el) {
 var self = this;
 var _current_color = 0;
 
 self.init = function() {
 $btn_el.html('Click Me');
 $btn_el.on('click', self.apply_next_color);
 };
 
 
 }
 self.apply_next_color = function() {
 $body_el.css('backgroundColor', colors_array[_current_color]);
 _current_color += 1;
 };
 var c = new ColorChanger(colors, $('#btn'), $('body'));
 c.init();
Now you can easily test: • Code iterates over all colours • Code works well on all possible colours array • Colour iteration is circular
Takeaways • Refactoring code can make it easier to test • The goal: • Isolated logic • Clear replaceable dependencies
Agenda • Dependency Injection • Data / DOM separation • Component based architecture • Design Patterns
Dependency Injection • State all your dependencies at the top • Separate object creation and lookup from business logic
DI Framework • A framework that does object creation for you • Some frameworks also manage object lifecycle
Famous DI global.myapp.controller(
 'Home', 
 ['$scope', '$routeParams', 'Notebooks', 
 function($scope, $routeParams, Notebooks) {
 // ...
 }]);
Famous DI 
 require(["helper/util"], function(util) {
 });
Vanilla DI • You don’t really need a framework
Q &A
Data / DOM Business logic JS write read (event handlers) DOM API HTMLDivElement
Event Handlers • Get the data • Call testable handler function $('#username').on('input', function() {
 var newValue = this.value;
 self.checkUsername(newValue);
 });

Mixing Lookups $('#btn').on("click", function() {
 if ( $('#page1').is(':hidden') == true ) {
 $('#page1').show();
 $('#page2').hide();
 } else {
 $('#page1').hide();
 $('#page2').show();
 }
 });
 $('#page1').show();
Non Mixed Version function Toggle(pages) {
 var active = 0;
 
 function toggle() {
 pages[active].hide();
 active = (active + 1) % pages;
 pages[active].show();
 }
 
 pages[0].show();
 return toggle;
 
 }
 $('#btn').on('click', Toggle([$('#page1'), $('#page2')]));

Testing Non-Mixed Version • Setup fake dependencies var FakePage = function() {
 _visible = false;
 return {
 show: function() { _visible = true; },
 hide: function() { _visible = false; },
 visible: function() { return _visible; }
 }
 } ;

Testing Non-Mixed Version • Inject and test var toggle = Toggle([p1, p2]);
 
 expect(p1.visible).to.be.true;
 expect(p2.visible).to.be.false; 
 
 toggle();
 expect(p1.visible).to.be.false;
 expect(p2.visible).to.be.true;
Mixing Lookups • Separate lookup code from business logic • Test interesting parts -> business logic
Components Based Architecture
Guidelines • Well defined components with a clear API • Dependencies for each component are injected upon creation • System is a tree of components
Components Home Page Sidebar Content
Reducing Dependencies • Task: Clicking a menu item in the sidebar should change active item in $content • Is $content a dependency for $sidebar ?
Code From Sidebar $('.menu .item').on('click', function() {
 var item_id = $(this).data('id');
 $content.set_active_item(item_id);
 });
Direct Connection Problems • It doesn’t scale • Requires tester to mock many components
Solution: Observer Pattern • All components share a “hub” • No direct messages between components • Easy on the testing
Using Events $('.menu .item').on('click', function() {
 var item_id = $(this).data('id');
 $hub.trigger('active_item_changed', item_id);
 }); Sidebar $hub.on('active_item_changed', set_active_item); Content
Testing Events for ( var i=0; i < items.length; i++ ) {
 hub.trigger('active_item_change', i);
 expect($('#content').html()).to.eq(items[i]); } 

The Pattern
JS Observer • Observer is just a function • Notify by calling it
Q &A
Code Flags • Use global state • Use static methods • Mix object construction with business logic • Mixing find-what-i-need logic with business logic • Write LONG functions • Use many conditionals • Dependency hell • Long inheritance hierarchies
Thanks For Listening • Ynon Perek • http://ynonperek.com • ynon@ynonperek.com

How to write easy-to-test JavaScript