How to write easy-to-test JavaScript

1,118 views
960 views

Published on

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

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

  • Be the first to like this

No Downloads
Views
Total views
1,118
On SlideShare
0
From Embeds
0
Number of Embeds
125
Actions
Shares
0
Downloads
8
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

How to write easy-to-test JavaScript

  1. 1. Writing Easy-ToTest Code Ynon Perek ynon@ynonperek.com http://ynonperek.com
  2. 2. Problem #1 How do you write hard to test code ?
  3. 3. 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
  4. 4. Isolating Logic ItemWidget jQuery Parser TreeWidget Button Array.sort Data Supplier Data Object
  5. 5. Isolating Logic ItemWidget jQuery Parser TreeWidget Button Array.sort Data Supplier Data Object
  6. 6. Isolating Logic ItemWidget Test TreeWidget Button Data Supplier
  7. 7. Isolating Logic Main ItemWidget TreeWidget Test Button Data Supplier
  8. 8. The Code function TreeWidget(ItemWidget, dataSupplier) {
 // ...
 } function TreeWidget() {
 var dataSupplier = new DataSupplier('/music/collection');
 }
  9. 9. If you can isolate it, you can test it
  10. 10. 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]);
 }
 });
  11. 11. Dependencies • Colors array • DOM structure • jQuery
  12. 12. 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();
  13. 13. Now you can easily test: • Code iterates over all colours • Code works well on all possible colours array • Colour iteration is circular
  14. 14. Takeaways • Refactoring code can make it easier to test • The goal: • Isolated logic • Clear replaceable dependencies
  15. 15. Agenda • Dependency Injection • Data / DOM separation • Component based architecture • Design Patterns
  16. 16. Dependency Injection • State all your dependencies at the top • Separate object creation and lookup from business logic
  17. 17. DI Framework • A framework that does object creation for you • Some frameworks also manage object lifecycle
  18. 18. Famous DI global.myapp.controller(
 'Home', 
 ['$scope', '$routeParams', 'Notebooks', 
 function($scope, $routeParams, Notebooks) {
 // ...
 }]);
  19. 19. Famous DI 
 require(["helper/util"], function(util) {
 });
  20. 20. Vanilla DI • You don’t really need a framework
  21. 21. Q &A
  22. 22. Data / DOM Business logic JS write read (event handlers) DOM API HTMLDivElement
  23. 23. Event Handlers • Get the data • Call testable handler function $('#username').on('input', function() {
 var newValue = this.value;
 self.checkUsername(newValue);
 });

  24. 24. Mixing Lookups $('#btn').on("click", function() {
 if ( $('#page1').is(':hidden') == true ) {
 $('#page1').show();
 $('#page2').hide();
 } else {
 $('#page1').hide();
 $('#page2').show();
 }
 });
 $('#page1').show();
  25. 25. 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')]));

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

  27. 27. 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;
  28. 28. Mixing Lookups • Separate lookup code from business logic • Test interesting parts -> business logic
  29. 29. Components Based Architecture
  30. 30. Guidelines • Well defined components with a clear API • Dependencies for each component are injected upon creation • System is a tree of components
  31. 31. Components Home Page Sidebar Content
  32. 32. Reducing Dependencies • Task: Clicking a menu item in the sidebar should change active item in $content • Is $content a dependency for $sidebar ?
  33. 33. Code From Sidebar $('.menu .item').on('click', function() {
 var item_id = $(this).data('id');
 $content.set_active_item(item_id);
 });
  34. 34. Direct Connection Problems • It doesn’t scale • Requires tester to mock many components
  35. 35. Solution: Observer Pattern • All components share a “hub” • No direct messages between components • Easy on the testing
  36. 36. 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
  37. 37. Testing Events for ( var i=0; i < items.length; i++ ) {
 hub.trigger('active_item_change', i);
 expect($('#content').html()).to.eq(items[i]); } 

  38. 38. The Pattern
  39. 39. JS Observer • Observer is just a function • Notify by calling it
  40. 40. Q &A
  41. 41. 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
  42. 42. Thanks For Listening • Ynon Perek • http://ynonperek.com • ynon@ynonperek.com

×