Test-Driven JavaScript Development (JavaZone 2010)

Preview:

DESCRIPTION

My slides from JavaZone 2010. Watch video:

Citation preview

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/

•christian@cjohansen.no