Writing testable js [by Ted Piotrowski]

690 views
589 views

Published on

About us
Author: Ted Piotrowski
Find me at: tpiotrowski@atlassian.com
Sample code: https://bitbucket.org/tpiotrowski/js-hcm

Presentation made for Javascript Ho Chi Minh City Meetup Group

You can find us at:
http://www.meetup.com/JavaScript-Ho-Chi-Minh-City/
https://www.facebook.com/JavaScriptHCMC
https://plus.google.com/u/0/communities/116105314977285194967

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
690
On SlideShare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
6
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Writing testable js [by Ted Piotrowski]

  1. 1. Writing testable JS by Ted Piotrowski Javascript Ho Chi Minh City
  2. 2. Excuses for not testing ● I know the code ● Test suite is hard to configure and run ● You can’t test UI ● You can’t unit test JS code
  3. 3. $(document).ready(function() { $('#new-status form').submit(function(e) { e.preventDefault(); $.ajax({ url: '/status', type: 'POST', dataType: 'json', data: { text: $('#new-status').find('textarea').val() }, success: function(data) { $('#statuses').append('<li>' + data.text + '</li>'); $('#new-status').find('textarea').val(''); } }); }); }); source: https://github.com/kjbekkelund/writings/blob/master/published/understanding-backbone.md/
  4. 4. Documentation ● Impossible to write a good test without a good specification ● If you don’t have time to write a test, at least write documentation ○ it will allow others to write tests later
  5. 5. /** * adds two numbers together */ function sum(a, b) assert(sum(1, 2), 3);
  6. 6. /** * adds two numbers together */ function sum(a, b) assert(sum(1, ‘a’), ?);
  7. 7. /** * adds two numbers together, * otherwise returns null */ function sum(a, b) assert(sum(1, ‘a’), null);
  8. 8. Dependencies ● Can’t write good tests unless you understand what external objects the code depends on ● loose coupling ● use requirejs, almond.js, squire.js ● move dependencies up the call stack and inject
  9. 9. Scope ● An assertion should only rely on the method being tested ● What is a “unit”? ● is $(function() { }) a unit?
  10. 10. // Should we stub addTwo, addOne? // When we limit scope, we forfeit integration function addThree(a) { var x = addTwo(a); var y = addOne(x); return y; }
  11. 11. Testing with the DOM ● Use a DOM fragment / jQuery fragment ● Inject it into the module constructor
  12. 12. function Word() { this.el = $('.word'); // external dependency } Word.prototype.setText = function(text) { this.el.text(text); }; var word = new Word(); word.setText('Hello World'); assert(???, 'Hello World');
  13. 13. function Word(el) { this.el = el; // dependency injection } Word.prototype.setText = function(text) { this.el.text(text); }; var mockEl = $('<div></div>'); // use a test double var word = new Word(mockEl); // inject the dependency word.setText('Hello World'); assert(mockEl.text(), 'Hello World');
  14. 14. function Word(el) { this.el = el || $('.word'); // optional injection } Word.prototype.setText = function(text) { this.el.text(text); }; var mockEl = $('<div></div>'); var word = new Word(mockEl); word.setText('Hello World'); assert(mockEl.text(), 'Hello World');
  15. 15. Dealing with window properties ● You code will likely touch Web API ○ document, Math, ● Can use mocks and stubs for methods ● Many Web API properties are read-only
  16. 16. // If your production code calls alert(), you can mock it var spy = sinon.spy(window, "alert"); assert(spy.calledWith("My alert message")); // However, you can't modify read only properties Math.PI = 3.15; // won’t work window.History.length = 12;
  17. 17. function Win(windowObj) { this.window = windowObj || window; } Win.prototype.scrollX = function() { return this.window.scrollX }; Win.prototype.scrollY = function() { return this.window.scrollY }; var win = new Win(); // in production // win.scrollX() => actual value var win = new Win({ // in testing scrollX: 50, scrollY: 40, History: { … } }); // win.scrollX() => 50
  18. 18. Dealing with network calls ● Use sinon fake server
  19. 19. { "test should fetch comments from server" : function () { this.server.respondWith("GET", "/some/article/comments.json", [200, { "Content-Type": "application/json" }, '[{ "id": 12, "comment": "Hey there" }]']); var callback = sinon.spy(); myLib.getCommentsFor("/some/article", callback); this.server.respond(); sinon.assert.calledWith(callback, [{ id: 12, comment: "Hey there" }])); } }
  20. 20. Code coverage ● Reports what lines of production JS are executed during testing ● necessary, but not sufficient ● Istanbul is a good tool ○ integrates with Karma
  21. 21. JS integration tests ● main.js file initializes your components and injects DOM dependencies ● Stub your REST calls ● Just like unit testing, user events are your input, DOM changes are your output
  22. 22. // Bootstrap your application here // Inject your DOM dependencies at top of call stack // Allows you to mock/stub the DOM for each component $(function() { var $el = $('.some-element'); var component = new Component($el); var $el2 = $('.some-element2'); var component2 = new Component($el2); // initialize more components, global state here });
  23. 23. Demo time
  24. 24. More information ● Testable Javascript ● Karma test runner ● Sinon.js ● Istanbul
  25. 25. About us Author: Ted Piotrowski Find me at: tpiotrowski@atlassian.com Sample code: https://bitbucket.org/tpiotrowski/js-hcm Presentation made for Javascript Ho Chi Minh City Meetup Group You can find us at: ● http://www.meetup.com/JavaScript-Ho-Chi-Minh-City/ ● https://www.facebook.com/JavaScriptHCMC ● https://plus.google.com/u/0/communities/116105314977285194967 ● http://www.slideshare.net/JavascriptMeetup
  26. 26. Lean Coffee Discussion topics

×