This document discusses test-driven development for JavaScript using ScrewUnit and BlueRidge. It introduces the Carbon Five consulting firm and covers why JavaScript unit testing is important. It then demonstrates how to write behavioral tests using ScrewUnit's BDD style and shows a live example testing a wizard component. Some challenges of JavaScript testing like DOM cleanup and structure are addressed. The document emphasizes that JavaScript testing is possible and can be integrated into the development process.
3. Carbon Five
Boutique software consulting
20 people, all developers
Agile development, collaboration, transparency
Mix of projects: start-ups / non-profits / enterprise
History of Java, but now RoR + misc.
4. Agenda
JavaScript unit testing: how & why
BDD, jQuery, plugins
Demo TDD with a simple test
Problems & lessons learned
Q&A
5. Why NOT JS Unit Testing?
Can’t get started
We don’t; nobody else does
Patterns unclear-- what does it look like
Too many tools (and arch.) to choose from
... lack of tool & infrastructure support
Flakey/bad experiences
6. Why JS Unit Testing?
Tests => Confidence => Ambition
Take on more challenging requirements
More homogenous architecture (w/o Flash)
Push logic into browser... scalability
Refactor reliably
7. JS Testing Universe
Styles of Tests:
xUnit: JSUnit, QUnit, YUITest, etc.
BDD: JSSpec, ScrewUnit
Automation:
In-browser w/ Selenium, WebDriver, JSTestDriver
Out of browser: Env.js + Rhino
8. Blue Ridge
Ruby on Rails plugin (therefore conventions!)
Integrates
ScrewUnit (BDD) and Smoke (mocking)
Glue code and scripts
Rhino & Env.js to run out of browser
from Larry Karnowski, Runcoderun
9. BDD
Language closer to user stories
Popularized by RSpec in Ruby
describe -- organize
before / after blocks
Nesting describes -- to DRY
One assert should per test example
10. describe Stack do
before(:each) do
@stack = Stack.new
@stack.push :item
end
describe "#peek" do
it "should return the top element" do
@stack.peek.should == :item
end
it "should not remove the top element" do
@stack.peek
@stack.size.should == 1
end
end
describe "#pop" do Stack (generated docs)
it "should return the top element" do #peek
@stack.pop.should == :item - should return the top element
end
it "should remove the top element" do
- should not remove the top
@stack.pop element
@stack.size.should == 0 #pop
end - should return the top element
end
end - should remove the top element
11. BDD in RSpec
describe 'a man' {
before {
@man = Man.new :luck=>5
}
describe '#decrement_luck' {
it "decrements the luck field by the given amount" {
@man.decrement_luck(3)
@man.luck.should == 2
}
}
...
});
12. BDD in ScrewUnit
describe('Man', function() {
var man;
before(function() {
man = new Man({luck: 5});
});
describe('#decrement_luck', function() {
it("decrements the luck field by the given amount", function() {
man.decrement_luck(3);
expect(man.luck()).to(equal, 2)
});
});
...
});
14. Wizard-izer (our example)
Given a form comprised of fieldsets
Creates a “panel” for each fieldset
Hides all but the first panel
Adds buttons to navigate panels
etc.
17. 3 Components
Fixture - markup that JS requires
test/javascript/widget.html
Spec - tests
test/javascript/specs/widget_spec.js)
Code to test - independent JS
<public>/javascript/widget.js
18. jQuery
Functional DOM manipulation
One function $: $(selector) eg. $(‘p.cls’)
Chainable -- $
(‘p.cls’).addClass(‘abc’).removeClass(‘cls’).etc(...)
Plugins
19. jQuery Plugin
Add function to jQuery.fn.reverse() = function() {
jQuery object
// “this” is list of nodes
Receive list of nodes
$(this).each(function() {
var t = $(this).text();
Do your stuff $(this).text(t);
}
Return “this”
return this;
Good tutorials online }
20. DOM cleanup
Tools don’t reset your DOM automatically
They are writing status back to the page (yuck!)
Solution
Wrap fixtures (in #fixture container)
before function that restores DOM state
22. Put CSS in Fixture
Helps with debugging
Use FireBug to inspect
23. Structure as a jQuery plugin
jQuery is not required-- but really helps you organize
code
Separate document.ready() from code--
dependency injection
Use as organizing principal, and dependency injection
Write modular code, instead page-specific code
25. Not Hard to Make JS
Testing a Part of Your
Process
26. Markup Design
Generated in JS? On the server? Hybrid?
Unit testing encourages generating more markup in
JavaScript
Will affect SEO and accessibility (good or bad)
27. When to test
Some JS can be experimental
Test-drive when you have a good sense of what he
component is
Testing is a refactoring tool
As code gets complicated, pull into testable units
28. Don’t Be Afraid of JS
Reminder wizard
Method chooser demo-- data driven JS, with ajax
structure
29. What to Unit Test /
Limitations
AJAX
JavaScript Unit Testing
setTimeout()
setInterval()
DOM Business
Manipulation Logic Animations
Computed sizes
30. It’s Not Browser Testing
...it’s JS unit testing
Not cross-browser testing
Not your production markup
Functional test framework still have a place
31. Our Testing Stack
Selenium Integration & Browser Testing 1/10th
Controller/View Integration
Tests 1/5th
Javascript Unit
Testing
Model Unit Controller Unit 1/10th
Tests Tests
32. JavaScript Testing
Yes We Can!
Andy Peterson Jonah Williams
andy@carbonfive.com jonah@carbonfive.com
skype: ndpsoft
33. Blue Ridge TODOs
Run in more browsers (not just Firefox)
Speed
Events
Driving in multiple browsers
Using code served from app
Shenandoah
Who Writes Java? Write Unit tests?
Who Writes Ruby
Who Writes JavaScript? Do you write unit tests?
BDD?
jQuery?
John Resig Survey: http://www.slideshare.net/jeresig/understanding-javascript-testing
Counter each of these!!!!
we wrote something we otherwise would have outsourced to Flash developers-- demo method explorer
# nested describes---
# need button to go to next fieldset
it("should add 'next' button to first fieldset", function() {
expect($("form fieldset:first a").text()).to(equal, 'Next');
});
it("should add 'next' class to next button", function() {
expect($("form fieldset:first a").hasClass('next')).to(equal, true);
});
it("should add 'next' button to second fieldset", function() {
expect($("form fieldset:nth(1) a:last").text()).to(equal, 'Next');
});
it("should not add 'next' buttons to last fieldset", function() {
expect($("form fieldset:last a").text()).to(equal, '');
});
# code
$('fieldset:not(:last)').each(function() { $(this).append('<a class="next">Next</a>') });
./script/generate javascript_spec wiz
# create fixture
<form action="application.html">
<fieldset title="A"><input type="text" value="a"/></fieldset>
<fieldset title="B"><input type="text" value="b"/></fieldset>
<fieldset title="C"><input type="text" value="c"/></fieldset>
</form>
# create simple test
describe('initialization', function() {
it("shows the first fieldset", function(){
expect($("form fieldset:first").is(':visible')).to(equal, true);
});
});
# run from command line
# tests that fail
it("hides the second and third fieldset", function(){
expect($("form fieldset:not(:first)").is(':visible')).to(equal, false);
});
# run from command line
# write code in before block
var wizardize = function() {
$('form').each(function() {
$('fieldset', this).hide();
$('fieldset:first', this).show();
});
mkdir demo
cd demo
rails demo
cd demo
./script/plugin install git://github.com/relevance/blue-ridge.git
./script/generate blue_ridge
mkdir demo
cd demo
rails demo
cd demo
./script/plugin install git://github.com/relevance/blue-ridge.git
./script/generate blue_ridge