Last week we released Syn and mentioned how all testing solutions suffer from some fatal flaw. Today, we are releasing a beta of FuncUnit, which has "solved" JavaScript web application testing.
Features
- Functional Testing - Test complex behavior like drag-drop.
- High Fidelity - Create identical events that a mouse and keyboard create. Run tests in a real browser.
- Automated - Test multiple browsers from the command line.
- Easy to Write - Write, run, and debug tests in the browser, without configuring a server or other tool. Write tests in a familiar jQuery-style API.
- Free - It's open source! Save yourself $5,000 for similar solutions.
- Multi-system - Runs in IE 6+, FF 2+, Safari 4+, Opera 9+, Chrome, on PC, Mac, and Linux.
Download
What
FuncUnit is a web application testing framework that combines Selenium, jQuery, QUnit, and Envjs. It focuses on making testing as easy and fun as possible.
Writing tests is a chore and won't be done unless it's stupidly easy. Plus, tests won't get run unless they can be run automatically.
The problem with other automated testing solutions is that everyone has to install and configure cumbersome external software to even begin writing tests. After setting up the software, you write the tests with some obtuse API in a language other than JavaScript. But the worst part is debugging these tests across the software-browser bridge. The tests are almost impossible to debug!
We fixed this.
Write, Run, and Debug with the Browser
FuncUnit lets you write tests by just creating an html
file that includes funcunit.js
.
Simply open the html page in your browser and it runs your tests. If you made a mistake, or your app breaks, use your favorite debugger (Firebug) to catch the problem. It's easy.
Automate Your Tests
When you've setup your testing server with the browsers you want to support, running the same tests is as simple as opening the test page with envjs:
envjs http://test.myco.com/tests/filemanager.html
We typically set this up to run as a nightly build.
API
FuncUnit's API is super sweet. If you know QUnit and jQuery, it will be second nature. The following tests the handy autosuggest from phpguru. See it in action here. (Turn off your popup blocker!)
module("autosuggest",{
setup : function(){
S.open('autosuggest.html')
}
});test("JavaScript results",function(){
S('input').click().type("JavaScript")
// wait until we have some results
S('.autocomplete_item').visible(function(){
equal( S('.autocomplete_item').size(),
5,
"there are 5 results")
})
});
Demos
Make sure you have your popup blocker off!
- A basic AutoComplete
- Bitovi's Phui tests (Runs Menu, Grid, Sortable, Filler) [Some might break in IE, this is a problem with the widgets, not FuncUnit].
Documentation
JavaScriptMVC's FuncUnit docs.
Use
Setting up FuncUnit is almost exactly like setting up QUnit. After downloading FuncUnit into a public folder on your server (or a local file on your hard drive):
- Create an empty test script (ex:
mytest.js
). - Create a qunit like page (ex:
mytest.html
) that will load your test and display your results. Use this as a template (It's also in the download). - Have that page include funcunit.js, qunit.css, and your test script (mytest.js)
Open mytest.html, you should see something like:
**** you probably want to change the name of these files ****
Writing Tests
All interaction with the tested page is done through the FuncUnit
object. This is aliased as S
. S
is to FuncUnit as $
is to jQuery
.
Typically a test is broken down in 5 parts:
- Opening a page
- Performing user actions (like clicking and typing).
- Waiting for the page to complete its response (animations and Ajax)
- Getting properties of the page (like the text of an element)
- Asserting the properties are correct (like the text == "Hello World")
Testing a dynamic webpage is largely asynchronous. To avoid LOTS of nested callback functions, most FuncUnit commands are queued. But these commands take a callback function that runs after the command completes, but before the next command runs.
For example, the following performs 3 drags that each take a second. But, between the second and third drag, it checks the #start
element's text.
S("#start")
.drag({ to:"#first", duration: 1000 })
.drag({ to:"#second", duration: 1000 },
function(){
equals(S("#start").text(),"2 drops")
})
.drag({ to:"#third", duration: 1000 })
Opening a page
To open a page is simple. Write:
S.open("path/to/mypage.html")
The path to the page should be relative to the FuncUnit test page (mytest.html
). Typically an open should be done in a module's setup function.
Actions
Actions are used to simulate user behavior such as clicking, typing, moving the mouse. FuncUnit provides the following actions:
click
- clicks an element (mousedown, mouseup, click).dblclick
- two clicks followed by a dblclick.rightClick
- a right mousedown, mouseup, and contextmenu.type
- Types characters into an element.move
- mousemove, mouseover, and mouseouts from one element to another.drag
- a drag motion from one element to another.scroll
- scrolls an element.
The following might simulate typing and resizing a "resizable" textarea plugin:
S('textarea').click().type( "Hello World" );
S('.resizer').drag( "+20 +20" );
Waits
Waits are used to wait for a specific condition to be met before continuing to command. Waits look like most jQuery setter methods. For example, the following waits until the width of an element is 200 pixels and tests its offset.
var sm = S("#sliderMenu");
sm.width( 200, function(){
var offset = sm.offset();
equals( offset.left, 200)
equals( offset.top, 200)
})
You can also provide a test function that when true, continues to the next action or wait command. The following is equivalent to the previous example:
var sm = S("#sliderMenu");
sm.width(
function( width ) {
return width == 200;
},
function(){
var offset = sm.offset();
equals( offset.left, 200)
equals( offset.top, 200)
}
)
Getters
Getters are used to test the conditions of the page. Most getter commands also correspond to a jQuery method of the same name.
The following checks that a textarea, which can only be resized vertically, is 20 pixels taller after a drag, but the same width:
var txtarea = S('textarea'),
// save references to width and height
startingWidth = txtarea.width(),
startingHeight = txtarea.height();S('.resizer').drag("+20 +20", function(){
equals(txtarea.width(),
startingWidth,
"width stays the same");
equals(txtarea.height(),
startingHeight+20,
"height got bigger");
});
Assertions
FuncUnit uses qUnit for assertions so you can use:
ok - ok(state, message)
equals - equals(actual, expected, message)
same - same(actual, expected, message)
Running Tests
Running tests in the browser is easy - just open the FuncUnit page. But running tests via the command line is equally as easy. Simply open your test page with envjs:
envjs http://localhost/tests/grid.html
Or if you are using Mac/Linux:
./envjs http://localhost/tests/grid.html
Setting Up Your Test Server
We'll be putting this up in JavaScriptMVC's FuncUnit docs.
Conclusion
We believe FuncUnit finally makes testing JavaScript approachable. We hope you can use to to write better, more robust applications that move web development along faster than ever. If you have questions or comments please post on FuncUnit's Google Group.
Thanks!
FuncUnit is a mashup of some of the best open source tools available:
- Selenium - Browser automation
- QUnit - Testing API
- jQuery - CSS selector and DOM utilities (your app does not need jQuery)
- EnvJS - Simulated browser environment
- Syn
- A synthetic event library
Important Links
- FuncUnit Homepage
- Google Group
- GitHub
- @funcunit on twitter