Test-Driven JavaScript
Eliminating fear and chance from front-end web
development
Christian Johansen
http://cjohansen.no/http://github.com/cjohansenhttp://gitorious.org/~cjohansenhttp://twitter.com/cjno
My book
http://tddjs.com/
What we're doing today
•How to unit test JavaScript?
•JavaScript testing challenges
•Tool chain integration
How to unit test JavaScript?
In-browser test frameworks
YUI Test•Part of the YUI framework•Can test any code, regardless of framework•In-browser runner•Built-in mocks•Can ship results over the internet•Supports many output formats (JUnit XML, TAP, JSON ++)
http://developer.yahoo.com/yui/3/test/
YUI Test case anatomy
YUI Test scaffolding
YUI Test run
YUI Test: The Good
•Easy to get started
•Run in any browser
•Built-in mocks
•Drop into app for integration testing
YUI Test: The bad
•Boilerplate HTML fixture
•Manually test all browsers
Problem: Impractical workflow
Headless runners
JSpec•BDD framework
•Runs in browser, Rhino and Node.js
•Emulate DOM with env.js
•Browser-based: Similar to YUI Test
JSpec Rhino scaffolding
JSpec Rhino run
JSpec + Rhino: The good
•No browsers
•Fast
Problem: It's all fake
Just another runtimeNot like any browsers actually in use
Rhino
env.js
Just another DOM implementationNot like any DOM implementation in actual use
I hear these are popular
...and these
Manual testing is time consuming
The best from both worlds
JsTestDriver
JsTestDriver.conf
Start JsTestDriver Server
java -jar JsTestDriver-1.2.2.jar --port 4224
Capture Target Browsers
JsTestDriver Run
Bonus features•Alternative assertion frameworks
•Supports QUnit, YUI, Jasmine
•JUnit XML Output
•Coverage plugin
CLI Helper
$ gem install jstdutil$ export JSTESTDRIVER_HOME=~/bin/jstestdriver
Pretty colors
With errors
Also...
$ jsautotest
Runs affected tests on each save
Eclipse
Eclipse
Eclipse run
IntelliJ IDEA plugin also available
Just released
JavaScript testing challenges
XMLHttpRequest
•Needs a server responding
•Makes tests run slow(er)
•Unsuitable for unit tests
Solution: Encapsulate
•Simple and elegant
•Looser coupling
•Easy to test
Example: Chat client
Anatomy
messageFormController
this.view (form)this.model (cometClient)
onSubmit
messageListController
this.view (dl)this.model (cometClient)
cometClient
What's the trick?
•All network access goes through cometClient
•observable supports same API as cometClient
•Use observable in tests
What about cometClient?
We'll get there
Event Handlers
•Touch, keyboard, mouse events
•Cross-browser issues
•Cumbersome to manually fire
messageFormController
this.view (form)this.model (cometClient)
onSubmit
messageListController
this.view (dl)this.model (cometClient)
cometClient
Submitting message
Solution: Decouple code
•Simple
•Testable
•Often makes sense API-wise
Testing event handlers
•Verify that setView adds event handler to form element for submit event
•Verify that the handler is postMessage, bound to the controller
•Test postMessage separately
Stubs and Mocks
Disclaimer: I wrote thathttp://cjohansen.no/sinon/
Sinon.JS Spies
•Wraps functions
•Does not interrupt normal execution
•Logs all calls and related data
Using Sinon.JS spies
sinon.testCase()
•Automatically verifies mocks
•Automatically restores all fakes
•Provides useful utilities (more later)
Verify that an event handler was added
Testing the handler
Testing event handlers
•Verify that setView adds event handler to form element for submit event
•Verify that the handler is postMessage, bound to the controller
•Test postMessage separately
Use an ad hoc stub
Integration: Simulate
Testing actual network access
Using Sinon.JS
Configure a fake server
Fake JSON response{ "message": [{ "id": 1, "user": "Johansen", "message": "oh hai" }],
"token": "1"}
The cometClient format, an array of one new message
Force fake server to respond
What happened?
•GET /chat?1283370174112
•Fake server recognizes /\/chat\?\d+/
•this.server.respond(); fakes a response
•cometClient dispatches canned data
Testing timers
•setTimeout/setInterval
•Causes slow(er) tests
•Causes asynchronous tests
Solution: Fake it
Toolchain
JsTestDriver and Maven
http://code.google.com/p/jsd-maven/
XML Pushups
Can you take one more?
Continuous Integration
Hudson setup
•"Free-style software project"
•Hudson xUnit Plugin
java -jar test/JsTestDriver-1.2.2.jar \ --config jsTestDriver.conf \ --reset \ --server http://localhost:4223 \ --tests all \ --testOutput .
Project overview
Test case
Failed test (IE6)
Risky time: Live demo•Add a feature - test first
•Autotest
•Test with Maven
•CI with Hudson
•Test in multiple browsers
Feature: @-messages
Highlight messages directed at current user
messageFormController
this.view (form)this.model (cometClient)
onSubmit
messageListController
this.view (dl)this.model (cometClient)
cometClient
Questions?
My book
http://tddjs.com/
Thanks for your time!•http://cjohansen.no/
•http://github.com/cjohansen/
•http://gitorious.org/~cjohansen/
•http://twitter.com/cjno/