42
Writing Easy-To- Test Code Ynon Perek [email protected] http://ynonperek.com

How to write easy-to-test JavaScript

Embed Size (px)

DESCRIPTION

Common anti patterns that make your JavaScript code hard to test, and how to fix them to make it easy.

Citation preview

Page 1: How to write easy-to-test JavaScript

Writing Easy-To-Test CodeYnon Perek [email protected] http://ynonperek.com

Page 2: How to write easy-to-test JavaScript

Problem #1 How do you write hard

to test code ?

Page 3: How to write easy-to-test JavaScript

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

Page 4: How to write easy-to-test JavaScript

Isolating Logic

TreeWidget

ItemWidget

Button

Data Supplier

jQuery

Parser

Array.sort

Data Object

Page 5: How to write easy-to-test JavaScript

Isolating Logic

TreeWidget

ItemWidget

Button

Data Supplier

jQuery

Parser

Array.sort

Data Object

Page 6: How to write easy-to-test JavaScript

Isolating Logic

TreeWidget

ItemWidget

Button

Data Supplier

Test

Page 7: How to write easy-to-test JavaScript

Isolating Logic

TreeWidget

ItemWidget

Button

Data SupplierTest

Main

Page 8: How to write easy-to-test JavaScript

The Code

function TreeWidget() { var dataSupplier = new DataSupplier('/music/collection'); }

function TreeWidget(ItemWidget, dataSupplier) { // ...}

Page 9: How to write easy-to-test JavaScript

If you can isolate it, you can test it

Page 10: How to write easy-to-test JavaScript

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]); }});

Page 11: How to write easy-to-test JavaScript

Dependencies

• Colors array

• DOM structure

• jQuery

Page 12: How to write easy-to-test JavaScript

Let’s Try This Onefunction 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();

Page 13: How to write easy-to-test JavaScript

Now you can easily test:

• Code iterates over all colours

• Code works well on all possible colours array

• Colour iteration is circular

Page 14: How to write easy-to-test JavaScript

Takeaways

• Refactoring code can make it easier to test

• The goal:

• Isolated logic

• Clear replaceable dependencies

Page 15: How to write easy-to-test JavaScript

Agenda

• Dependency Injection

• Data / DOM separation

• Component based architecture

• Design Patterns

Page 16: How to write easy-to-test JavaScript

Dependency Injection

• State all your dependencies at the top

• Separate object creation and lookup from business logic

Page 17: How to write easy-to-test JavaScript

DI Framework

• A framework that does object creation for you

• Some frameworks also manage object lifecycle

Page 18: How to write easy-to-test JavaScript

Famous DI

global.myapp.controller( 'Home', ['$scope', '$routeParams', 'Notebooks', function($scope, $routeParams, Notebooks) { // ...}]);

Page 19: How to write easy-to-test JavaScript

Famous DI

require(["helper/util"], function(util) {});

Page 20: How to write easy-to-test JavaScript

Vanilla DI

• You don’t really need a framework

Page 21: How to write easy-to-test JavaScript

Q & A

Page 22: How to write easy-to-test JavaScript

Data / DOM

JS HTMLDivElement

DOM APIBusiness logic

write

read (event handlers)

Page 23: How to write easy-to-test JavaScript

Event Handlers

• Get the data

• Call testable handler function

$('#username').on('input', function() { var newValue = this.value; self.checkUsername(newValue);});

Page 24: How to write easy-to-test JavaScript

Mixing Lookups

$('#btn').on("click", function() { if ( $('#page1').is(':hidden') == true ) { $('#page1').show(); $('#page2').hide(); } else { $('#page1').hide(); $('#page2').show(); }});$('#page1').show();

Page 25: How to write easy-to-test JavaScript

Non Mixed Versionfunction 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')]));

Page 26: How to write easy-to-test JavaScript

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; } }} ;

Page 27: How to write easy-to-test JavaScript

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;

Page 28: How to write easy-to-test JavaScript

Mixing Lookups

• Separate lookup code from business logic

• Test interesting parts -> business logic

Page 29: How to write easy-to-test JavaScript

Components Based Architecture

Page 30: How to write easy-to-test JavaScript

Guidelines

• Well defined components with a clear API

• Dependencies for each component are injected upon creation

• System is a tree of components

Page 31: How to write easy-to-test JavaScript

Components

Home Page

Sidebar Content

Page 32: How to write easy-to-test JavaScript

Reducing Dependencies

• Task: Clicking a menu item in the sidebar should change active item in $content

• Is $content a dependency for $sidebar ?

Page 33: How to write easy-to-test JavaScript

Code From Sidebar

$('.menu .item').on('click', function() { var item_id = $(this).data('id'); $content.set_active_item(item_id); });

Page 34: How to write easy-to-test JavaScript

Direct Connection Problems

• It doesn’t scale

• Requires tester to mock many components

Page 35: How to write easy-to-test JavaScript

Solution: Observer Pattern

• All components share a “hub”

• No direct messages between components

• Easy on the testing

Page 36: How to write easy-to-test JavaScript

Using Events$('.menu .item').on('click', function() { var item_id = $(this).data('id'); $hub.trigger('active_item_changed', item_id);});

$hub.on('active_item_changed', set_active_item);

Sidebar

Content

Page 37: How to write easy-to-test JavaScript

Testing Events

for ( var i=0; i < items.length; i++ ) { hub.trigger('active_item_change', i); expect($('#content').html()).to.eq(items[i]); }

Page 38: How to write easy-to-test JavaScript

The Pattern

Page 39: How to write easy-to-test JavaScript

JS Observer

• Observer is just a function

• Notify by calling it

Page 40: How to write easy-to-test JavaScript

Q & A

Page 41: How to write easy-to-test JavaScript

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

Page 42: How to write easy-to-test JavaScript

Thanks For Listening

• Ynon Perek

• http://ynonperek.com

[email protected]