Very easy.
1 2 3 4 5 6 7 | test ( "some tests" , function ( ) { expect ( 3 ) ; ok ( true , "passes because true is true" ) ; equal ( "1" , 1 , "passes because '1' == 1" ) ; strictEqual ( "1" , 1 , "fails because '1' !== 1" ) ; } ) ; |
Thousands of tests.
Just qunit.js, qunit.css, and a little bit of HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <!DOCTYPE html> < html > < head > < meta charset = "UTF-8" /> < title >MyApp Test Suite</ title > < link rel = "stylesheet" href = "qunit.css" type = "text/css" > < script src = "qunit.js" ></ script > < script src = "myapp.js" ></ script > < script src = "myapp-test.js" ></ script > </ head > < body > < h1 id = "qunit-header" >MyApp Test Suite</ h1 > < h2 id = "qunit-banner" ></ h2 > < div id = "qunit-testrunner-toolbar" ></ div > < h2 id = "qunit-userAgent" ></ h2 > < ol id = "qunit-tests" ></ ol > < div id = "qunit-fixture" ></ div > </ body > </ html > |
It's really this simple.
1 2 3 | test ( "The name of the test" , function ( ) { // Assertions. } ) ; |
You say “this is how it should work,”
and QUnit tells you when it doesn't.
Get in the habit of doing this!
1 2 3 4 5 6 7 8 9 10 11 12 13 | // You can either set an expectation (number) like this. test ( "test name" , function ( ) { expect ( 3 ) ; // QUnit expects 3 assertions in this test. } ) ; // Or like this. test ( "test name" , 3 , function ( ) { // QUnit expects 3 assertions in this test. } ) ; |
ok
,
equal
,
notEqual
,
strictEqual
,
notStrictEqual
,
deepEqual
,
notDeepEqual
,
raises
ok
A boolean assertion that passes if the first argument is truthy.
1 2 3 4 5 | test ( "ok" , 3 , function ( ) { ok ( true , "passes because true is true" ) ; ok ( 1 , "passes because 1 is truthy" ) ; ok ( "" , "fails because empty string is not truthy" ) ; } ) ; |
equal
A comparison assertion that passes if actual == expected.
1 2 3 4 5 6 7 | test ( "equal" , 3 , function ( ) { var actual = 5 - 4 ; equal ( actual , 1 , "passes because 1 == 1" ) ; equal ( actual , true , "passes because 1 == true" ) ; equal ( actual , false , "fails because 1 != false" ) ; } ) ; |
notEqual
A comparison assertion that passes if actual != expected.
1 2 3 4 5 6 7 | test ( "notEqual" , 3 , function ( ) { var actual = 5 - 4 ; notEqual ( actual , 0 , "passes because 1 != 0" ) ; notEqual ( actual , false , "passes because 1 != false" ) ; notEqual ( actual , true , "fails because 1 == true" ) ; } ) ; |
strictEqual
A comparison assertion that passes if actual === expected.
1 2 3 4 5 6 7 | test ( "strictEqual" , 3 , function ( ) { var actual = 5 - 4 ; strictEqual ( actual , 1 , "passes because 1 === 1" ) ; strictEqual ( actual , true , "fails because 1 !== true" ) ; strictEqual ( actual , false , "fails because 1 !== false" ) ; } ) ; |
notStrictEqual
A comparison assertion that passes if actual !== expected.
1 2 3 4 5 6 7 | test ( "notStrictEqual" , 3 , function ( ) { var actual = 5 - 4 ; notStrictEqual ( actual , 1 , "fails because 1 === 1" ) ; notStrictEqual ( actual , true , "passes because 1 !== true" ) ; notStrictEqual ( actual , false , "passes because 1 !== false" ) ; } ) ; |
deepEqual
Recursive comparison assertion, working on primitives, arrays and objects, using ===.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | test ( "deepEqual" , 7 , function ( ) { var actual = { a : 1 } ; equal ( actual , { a : 1 } , "fails because objects are different" ) ; deepEqual ( actual , { a : 1 } , "passes because objects are equivalent" ) ; deepEqual ( actual , { a : "1" } , "fails because '1' !== 1" ) ; var a = $ ( "body > *" ) ; var b = $ ( "body" ) . children ( ) ; equal ( a , b , "fails because jQuery objects are different" ) ; deepEqual ( a , b , "fails because jQuery objects are not equivalent" ) ; equal ( a . get ( ) , b . get ( ) , "fails because element arrays are different" ) ; deepEqual ( a . get ( ) , b . get ( ) , "passes because element arrays are equivalent" ) ; } ) ; |
notDeepEqual
Recursive comparison assertion. The result of deepEqual, inverted.
1 2 3 4 5 6 7 | test ( "notDeepEqual" , 3 , function ( ) { var actual = { a : 1 } ; notEqual ( actual , { a : 1 } , "passes because objects are different" ) ; notDeepEqual ( actual , { a : 1 } , "fails because objects are equivalent" ) ; notDeepEqual ( actual , { a : "1" } , "passes because '1' !== 1" ) ; } ) ; |
raises
Assertion to test if a callback throws an exception when run.
1 2 3 4 5 6 7 8 9 10 11 12 13 | test ( "raises" , 3 , function ( ) { raises ( function ( ) { throw new Error ( "Look ma, I'm an error!" ) ; } , "passes because an error is thrown inside the callback" ) ; raises ( function ( ) { x // ReferenceError: x is not defined } , "passes because an error is thrown inside the callback" ) ; raises ( function ( ) { var a = 1 ; } , "fails because no error is thrown inside the callback" ) ; } ) ; |
Execution order cannot be guaranteed!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // Don't do this. var counter = 0 ; test ( "first test" , 1 , function ( ) { counter ++ ; equal ( counter , 1 , "counter should be 1" ) ; } ) ; test ( "second test" , 1 , function ( ) { counter ++ ; equal ( counter , 2 , "counter should be 2" ) ; } ) ; test ( "third test" , 2 , function ( ) { counter ++ ; equal ( counter , 2 , "counter should be 2" ) ; ok ( false , "oops, an error" ) ; } ) ; |
Any markup in here will be reset after every test (uses jQuery if available).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <!DOCTYPE html> < html > < head > < meta charset = "UTF-8" /> < title >MyApp Test Suite</ title > < link rel = "stylesheet" href = "qunit.css" type = "text/css" > < script src = "qunit.js" ></ script > < script src = "myapp.js" ></ script > < script src = "myapp-test.js" ></ script > </ head > < body > < h1 id = "qunit-header" >MyApp Test Suite</ h1 > < h2 id = "qunit-banner" ></ h2 > < div id = "qunit-testrunner-toolbar" ></ div > < h2 id = "qunit-userAgent" ></ h2 > < ol id = "qunit-tests" ></ ol > < div id = "qunit-fixture" > < ul > < li >foo</ li > < li >bar</ li > < li >baz</ li > </ ul > </ div > </ body > </ html > |
A little forethought can save a lot of frustration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | module ( "jQuery#enumerate" ) ; test ( "chainable" , 1 , function ( ) { var items = $ ( "#qunit-fixture li" ) ; strictEqual ( items . enumerate ( ) , items , "should be chaninable" ) ; } ) ; test ( "no args passed" , 3 , function ( ) { var items = $ ( "#qunit-fixture li" ) . enumerate ( ) ; equal ( items . eq ( 0 ) . text ( ) , "1. foo" , "first item should have index 1" ) ; equal ( items . eq ( 1 ) . text ( ) , "2. bar" , "second item should have index 2" ) ; equal ( items . eq ( 2 ) . text ( ) , "3. baz" , "third item should have index 3" ) ; } ) ; test ( "0 passed" , 3 , function ( ) { var items = $ ( "#qunit-fixture li" ) . enumerate ( 0 ) ; equal ( items . eq ( 0 ) . text ( ) , "0. foo" , "first item should have index 0" ) ; equal ( items . eq ( 1 ) . text ( ) , "1. bar" , "second item should have index 1" ) ; equal ( items . eq ( 2 ) . text ( ) , "2. baz" , "third item should have index 2" ) ; } ) ; test ( "1 passed" , 3 , function ( ) { var items = $ ( "#qunit-fixture li" ) . enumerate ( 1 ) ; equal ( items . eq ( 0 ) . text ( ) , "1. foo" , "first item should have index 1" ) ; equal ( items . eq ( 1 ) . text ( ) , "2. bar" , "second item should have index 2" ) ; equal ( items . eq ( 2 ) . text ( ) , "3. baz" , "third item should have index 3" ) ; } ) ; |
(Time for an example)
If it makes sense, test your plugin
in older versions of jQuery too.
Because your unit tests should be organized too.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | module ( "core" ) ; test ( "a test in the core module" , function ( ) { ok ( true , "this test had better pass" ) ; } ) ; test ( "another test in the core module" , function ( ) { ok ( true , "this test had also better pass" ) ; } ) ; module ( "options" ) ; test ( "a test in the options module" , function ( ) { ok ( true , "this test really, really better pass" ) ; } ) ; test ( "another test in the options module" , function ( ) { ok ( false , "sadly, this test is going to fail" ) ; } ) ; |
Configure setup and teardown callbacks to streamline your tests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // Defining a "setup" callback. module ( "module1" , { setup : function ( ) { ok ( true , "once extra assert per test" ) ; } } ) ; test ( "test with setup" , function ( ) { expect ( 1 ) ; } ) ; // Defining both "setup" and "teardown" callbacks. module ( "module2" , { setup : function ( ) { ok ( true , "once extra assert per test" ) ; this . prop = "foo" ; } , teardown : function ( ) { ok ( true , "and one extra assert after each test" ) ; } } ) ; test ( "test with setup and teardown" , function ( ) { expect ( 3 ) ; same ( this . prop , "foo" , "this.prop === 'foo' in all tests" ) ; } ) ; |
But as you can see, sometimes no errors is a bad thing.
1 2 3 4 5 6 7 | test ( "no errors" , function ( ) { var actual = false ; setTimeout ( function ( ) { ok ( actual , "this test would fail.. if it ever ran" ) ; } , 1000 ) ; } ) ; |
Now you get an error.. but it's not the error you want.
1 2 3 4 5 6 7 8 9 | test ( "expectations" , function ( ) { expect ( 1 ) ; var actual = false ; setTimeout ( function ( ) { ok ( actual , "this test would fail.. if it ever ran" ) ; } , 1000 ) ; } ) ; |
stop
& start
You must tell QUnit to wait for an asynchronous action to complete.
1 2 3 4 5 6 7 8 9 10 11 | test ( "stop & start" , function ( ) { expect ( 1 ) ; var actual = false ; stop ( ) ; setTimeout ( function ( ) { ok ( actual , "this test actually runs, and fails" ) ; start ( ) ; } , 1000 ) ; } ) ; |
asyncTest
Another way to tell QUnit to wait for an asynchronous action to complete.
1 2 3 4 5 6 7 8 9 10 | asyncTest ( "asyncTest & start" , function ( ) { expect ( 1 ) ; var actual = false ; setTimeout ( function ( ) { ok ( actual , "this test actually runs, and fails" ) ; start ( ) ; } , 1000 ) ; } ) ; |
stops
& starts
Jörn added this in... because I asked nicely.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | test ( "stops & starts" , function ( ) { expect ( 4 ) ; var url = "http://jsfiddle.net/echo/jsonp/?callback=?" ; stop ( ) ; $ . getJSON ( url , { a : 1 } , function ( data ) { ok ( data , "data is returned from the server" ) ; equal ( data . a , "1" , "the value of data.a should be 1" ) ; start ( ) ; } ) ; stop ( ) ; $ . getJSON ( url , { b : 2 } , function ( data ) { ok ( data , "data is returned from the server" ) ; equal ( data . b , "2" , "the value of data.b should be 2" ) ; start ( ) ; } ) ; } ) ; |
What are you testing anyways, your
client code or your server code?
If you mock your AJAX Requests, you test your JavaScript, not your server.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // Simulate your API. $ . mockAjax ( "json" , { "/user" : { status : - 1 } , "/user/(\\d+)" : function ( matches ) { return { status : 1 , user : "sample user " + matches [ 1 ] } ; } } ) ; // Unit tests. test ( "user tests" , function ( ) { expect ( 5 ) ; stop ( ) ; $ . getJSON ( "/user" , function ( data ) { ok ( data , "data is returned from the server" ) ; equal ( data . status , "-1" , "no user specified, status should be -1" ) ; start ( ) ; } ) ; stop ( ) ; $ . getJSON ( "/user/123" , function ( data ) { ok ( data , "data is returned from the server" ) ; equal ( data . status , "1" , "user found, status should be 1" ) ; equal ( data . user , "sample user 123" , "user found, id should be 123" ) ; start ( ) ; } ) ; } ) ; |
Add ?filter=foo
to the URL to run only tests whose names contain "foo".
“Modules” Test Suite | |
---|---|
All tests | modules.html |
“core” module | modules.html?filter=core |
“options” module | modules.html?filter=options |
“Assertions” Test Suite | |
All tests | assertions.html |
“ok” test | assertions.html?filter=ok |
“equal” tests | assertions.html?filter=equal |
Just add ?noglobals
to the URL, and QUnit will fail any “leaky” tests.
1 2 3 4 5 6 7 8 9 | test ( "not leaky" , 1 , function ( ) { var x = true ; ok ( x , "passes because x is true" ) ; } ) ; test ( "leaky" , 1 , function ( ) { x = true ; ok ( x , "also passes because x is true" ) ; } ) ; |
Automatically capture QUnit test results, à la TestSwarm.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | // Runs once at the very beginning. QUnit . begin = function ( ) { console . log ( "Running Test Suite" ) ; } ; // Runs once at the very end. QUnit . done = function ( failures , total ) { console . info ( "Suite: %d failures / %d tests" , failures , total ) ; } ; // Runs once after each assertion. QUnit . log = function ( result , message ) { console [ result ? "log" : "error" ] ( message ) ; } ; // Runs before each test. QUnit . testStart = function ( name ) { console . group ( "Test: " + name ) ; } ; // Runs after each test. QUnit . testDone = function ( name , failures , total ) { console . info ( "Test: %d failures / %d tests" , failures , total ) ; console . groupEnd ( ) ; } ; // Runs before each module. QUnit . moduleStart = function ( name ) { console . group ( "Module: " + name ) ; } ; // Runs after each module. QUnit . moduleDone = function ( name , failures , total ) { console . info ( "Module: %d failures / %d tests" , failures , total ) ; console . groupEnd ( ) ; } ; // Runs after each test group. Redefining this function will // override the built-in #qunit-fixture reset logic. QUnit . reset = function ( ) { console . log ( "Test done!" ) ; } ; |
Distributed Continuous Integration for JavaScript.