• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
How to write easy-to-test JavaScript
 

How to write easy-to-test JavaScript

on

  • 417 views

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

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

Statistics

Views

Total Views
417
Views on SlideShare
340
Embed Views
77

Actions

Likes
0
Downloads
5
Comments
0

2 Embeds 77

http://ynonperek.com 69
http://www.ynonperek.com 8

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    How to write easy-to-test JavaScript How to write easy-to-test JavaScript Presentation Transcript

    • 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