├── test ├── fixtures │ ├── raw_jscode3.js │ ├── dir │ │ ├── mock_module3.js │ │ ├── mock_module4.js │ │ ├── example_test_sub.js │ │ └── .should_not_be_run.js.swp │ ├── mock_module1.js │ ├── mock_module2.js │ ├── raw_jscode1.js │ ├── raw_jscode2.js │ ├── coffee │ │ └── mock_coffee_module.coffee │ └── example_test.js ├── test.html ├── test-sandbox.js ├── test-cli.js ├── test-runtest.js ├── test-httputil.js ├── test-bettererrors.js ├── test-failing-callbacks.js ├── test-base.js ├── test-testcase.js ├── test-runfiles.js ├── test-testcase-legacy.js └── test-runmodule.js ├── .npmignore ├── img ├── example_fail.png ├── example_pass.png └── example_machineout.png ├── .gitignore ├── index.js ├── .travis.yml ├── examples ├── browser │ ├── suite3.js │ ├── suite1.js │ ├── suite2.js │ └── test.html └── nested │ └── nested_reporter_test.unit.js ├── nodelint.cfg ├── share ├── license.js ├── junit.xml.ejs └── nodeunit.css ├── bin ├── nodeunit.json └── nodeunit ├── lib ├── reporters │ ├── index.js │ ├── lcov.js │ ├── tap.js │ ├── eclipse.js │ ├── skip_passed.js │ ├── html.js │ ├── machineout.js │ ├── verbose.js │ ├── default.js │ ├── minimal.js │ ├── browser.js │ ├── junit.js │ └── nested.js ├── track.js ├── nodeunit.js ├── types.js ├── utils.js ├── core.js └── assert.js ├── LICENSE ├── CONTRIBUTORS.md ├── doc └── nodeunit.md ├── package.json ├── man1 └── nodeunit.1 ├── Makefile ├── deps └── json2.js └── README.md /test/fixtures/raw_jscode3.js: -------------------------------------------------------------------------------- 1 | var t=t?t+1:1; 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | dist 2 | stamp-build 3 | test/fixtures/dir2 4 | -------------------------------------------------------------------------------- /test/fixtures/dir/mock_module3.js: -------------------------------------------------------------------------------- 1 | exports.name = 'mock_module3'; 2 | -------------------------------------------------------------------------------- /test/fixtures/dir/mock_module4.js: -------------------------------------------------------------------------------- 1 | exports.name = 'mock_module4'; 2 | -------------------------------------------------------------------------------- /test/fixtures/mock_module1.js: -------------------------------------------------------------------------------- 1 | exports.name = 'mock_module1'; 2 | -------------------------------------------------------------------------------- /test/fixtures/mock_module2.js: -------------------------------------------------------------------------------- 1 | exports.name = 'mock_module2'; 2 | -------------------------------------------------------------------------------- /img/example_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caolan/nodeunit/HEAD/img/example_fail.png -------------------------------------------------------------------------------- /img/example_pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caolan/nodeunit/HEAD/img/example_pass.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | stamp-build 4 | *~ 5 | gmon.out 6 | v8.log 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /test/fixtures/raw_jscode1.js: -------------------------------------------------------------------------------- 1 | function hello_world(arg) { 2 | return "_" + arg + "_"; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/raw_jscode2.js: -------------------------------------------------------------------------------- 1 | function get_a_variable() { 2 | return typeof a_variable; 3 | } 4 | -------------------------------------------------------------------------------- /img/example_machineout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caolan/nodeunit/HEAD/img/example_machineout.png -------------------------------------------------------------------------------- /test/fixtures/coffee/mock_coffee_module.coffee: -------------------------------------------------------------------------------- 1 | j = 0 2 | j += i for i in [0..5] 3 | 4 | exports.name = "mock_coffee_#{j}" 5 | -------------------------------------------------------------------------------- /test/fixtures/example_test.js: -------------------------------------------------------------------------------- 1 | exports['example test'] = function (test) { 2 | test.ok(true); 3 | test.done(); 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/dir/example_test_sub.js: -------------------------------------------------------------------------------- 1 | exports['example test sub'] = function (test) { 2 | test.ok(true); 3 | test.done(); 4 | }; 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // This file is just added for convenience so this repository can be 2 | // directly checked out into a project's deps folder 3 | module.exports = require('./lib/nodeunit'); 4 | -------------------------------------------------------------------------------- /test/fixtures/dir/.should_not_be_run.js.swp: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | // throw an assertion error if this gets required 4 | assert.ok(false, 'this module should not be loaded!'); 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "stable" 5 | - "10" 6 | - "8" 7 | - "6.14" 8 | - "6.5" 9 | - "6.4" 10 | - "6.3" 11 | - "5.12" 12 | - "4.9" 13 | - "4.4" 14 | - "0.12" 15 | -------------------------------------------------------------------------------- /examples/browser/suite3.js: -------------------------------------------------------------------------------- 1 | this.suite3 = { 2 | 'test for ie6,7,8': function (test) { 3 | test.deepEqual(["test"], ["test"]); 4 | test.notDeepEqual(["a"], ["b"]); 5 | test.done(); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /nodelint.cfg: -------------------------------------------------------------------------------- 1 | //See: http://www.jslint.com/lint.html#options 2 | var options = { 3 | //white: false, // if false, strict whitespace rules should be enforced. 4 | indent: 4, 5 | onevar: false, 6 | vars: true // allow multiple var statement per function. 7 | }; 8 | -------------------------------------------------------------------------------- /share/license.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * https://github.com/caolan/nodeunit 4 | * Copyright (c) 2010 Caolan McMahon 5 | * MIT Licensed 6 | * 7 | * json2.js 8 | * http://www.JSON.org/json2.js 9 | * Public Domain. 10 | * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 11 | */ 12 | -------------------------------------------------------------------------------- /bin/nodeunit.json: -------------------------------------------------------------------------------- 1 | { 2 | "error_prefix": "\u001B[31m", 3 | "error_suffix": "\u001B[39m", 4 | "ok_prefix": "\u001B[32m", 5 | "ok_suffix": "\u001B[39m", 6 | "bold_prefix": "\u001B[1m", 7 | "bold_suffix": "\u001B[22m", 8 | "assertion_prefix": "\u001B[35m", 9 | "assertion_suffix": "\u001B[39m" 10 | } 11 | -------------------------------------------------------------------------------- /examples/browser/suite1.js: -------------------------------------------------------------------------------- 1 | this.suite1 = { 2 | 'test one': function (test) { 3 | test.ok(true, 'everythings ok'); 4 | setTimeout(function () { 5 | test.done(); 6 | }, 10); 7 | }, 8 | 'apples and oranges': function (test) { 9 | test.equal('apples', 'oranges', 'comparing apples and oranges'); 10 | test.done(); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /examples/browser/suite2.js: -------------------------------------------------------------------------------- 1 | this.suite2 = { 2 | 'another test': function (test) { 3 | setTimeout(function () { 4 | // lots of assertions 5 | test.ok(true, 'everythings ok'); 6 | test.ok(true, 'everythings ok'); 7 | test.ok(true, 'everythings ok'); 8 | test.ok(true, 'everythings ok'); 9 | test.ok(true, 'everythings ok'); 10 | test.done(); 11 | }, 10); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /examples/browser/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Example tests 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/reporters/index.js: -------------------------------------------------------------------------------- 1 | // This is a hack to make browserify skip tap 2 | var tap; 3 | try { 4 | tap = require('./' + 'tap'); 5 | } catch (ex) { 6 | tap = { 7 | run: function() { 8 | throw new Error('Sorry, tap reporter not available'); 9 | } 10 | }; 11 | } 12 | 13 | module.exports = { 14 | 'junit': require('./junit'), 15 | 'default': require('./default'), 16 | 'skip_passed': require('./skip_passed'), 17 | 'minimal': require('./minimal'), 18 | 'html': require('./html'), 19 | 'eclipse': require('./eclipse'), 20 | 'machineout': require('./machineout'), 21 | 'tap': tap, 22 | 'nested': require('./nested'), 23 | 'verbose' : require('./verbose'), 24 | 'lcov' : require('./lcov') 25 | // browser test reporter is not listed because it cannot be used 26 | // with the command line tool, only inside a browser. 27 | }; 28 | -------------------------------------------------------------------------------- /share/junit.xml.ejs: -------------------------------------------------------------------------------- 1 | 2 | <% for (var i=0; i < suites.length; i++) { %> 3 | <% var suite=suites[i]; %> 4 | 10 | <% for (var testCaseName in suite.testcases) { %> 11 | <% var testcase=suite.testcases[testCaseName]; %> 12 | 13 | <% if (testcase.failure) { %> 14 | 15 | <% if (testcase.failure.backtrace) { %><%= testcase.failure.backtrace %><% } %> 16 | 17 | <% } %> 18 | 19 | <% } %> 20 | 21 | <% } %> 22 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Nodeunit Test Suite 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Nodeunit Test Suite

18 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Caolan McMahon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /test/test-sandbox.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('../lib/nodeunit'); 2 | var sandbox = require('../lib/utils').sandbox; 3 | var testCase = nodeunit.testCase; 4 | 5 | exports.testSimpleSandbox = function (test) { 6 | var raw_jscode1 = sandbox(__dirname + '/fixtures/raw_jscode1.js'); 7 | test.equal(raw_jscode1.hello_world('foo'), '_foo_', 'evaluation ok'); 8 | test.done(); 9 | }; 10 | 11 | exports.testSandboxContext = function (test) { 12 | var a_variable = 42; // should not be visible in the sandbox 13 | var raw_jscode2 = sandbox(__dirname + '/fixtures/raw_jscode2.js'); 14 | a_variable = 42; // again for the win 15 | test.equal( 16 | raw_jscode2.get_a_variable(), 17 | 'undefined', 18 | 'the variable should not be defined' 19 | ); 20 | test.done(); 21 | }; 22 | 23 | exports.testSandboxMultiple = function (test) { 24 | var raw_jscode3 = sandbox([ 25 | __dirname + '/fixtures/raw_jscode3.js', 26 | __dirname + '/fixtures/raw_jscode3.js', 27 | __dirname + '/fixtures/raw_jscode3.js' 28 | ]); 29 | test.equal(raw_jscode3.t, 3, 'two files loaded'); 30 | test.done(); 31 | }; 32 | -------------------------------------------------------------------------------- /lib/track.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Simple util module to track tests. Adds a process.exit hook to print 3 | * the undone tests. 4 | */ 5 | 6 | 7 | exports.createTracker = function (on_exit) { 8 | var names = {}; 9 | var tracker = { 10 | names: function () { 11 | var arr = []; 12 | for (var k in names) { 13 | if (names.hasOwnProperty(k)) { 14 | arr.push(k); 15 | } 16 | } 17 | return arr; 18 | }, 19 | unfinished: function () { 20 | return tracker.names().length; 21 | }, 22 | put: function (testname) { 23 | names[testname] = testname; 24 | }, 25 | remove: function (testname) { 26 | delete names[testname]; 27 | } 28 | }; 29 | 30 | process.on('exit', function() { 31 | on_exit = on_exit || exports.default_on_exit; 32 | on_exit(tracker); 33 | }); 34 | 35 | return tracker; 36 | }; 37 | 38 | exports.default_on_exit = function (tracker) { 39 | if (tracker.unfinished()) { 40 | console.log(''); 41 | console.log('Undone tests (or their setups/teardowns): '); 42 | var names = tracker.names(); 43 | for (var i = 0; i < names.length; i += 1) { 44 | console.log(names[i]); 45 | } 46 | process.reallyExit(tracker.unfinished()); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /test/test-cli.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec, 2 | path = require('path'); 3 | 4 | var bin = (process.platform === 'win32' ? 'node ' : "") + 5 | path.resolve(__dirname, '../bin/nodeunit'); 6 | var testfile_fullpath = path.resolve(__dirname, './fixtures/example_test.js'); 7 | var fixtures_path = path.resolve(__dirname, './fixtures'); 8 | 9 | exports['run test suite using absolute path'] = function (test) { 10 | exec(bin + ' ' + testfile_fullpath, function (err, stdout, stderr) { 11 | if (err) { 12 | return test.done(err); 13 | } 14 | test.ok(/example test/.test(stdout)); 15 | test.ok(/1 assertion/.test(stdout)); 16 | test.done(); 17 | }); 18 | }; 19 | 20 | exports['runs only top-level suites without recursive flag'] = function (test) { 21 | exec(bin + ' ' + fixtures_path, function (err, stdout, stderr) { 22 | if (err) { 23 | return test.done(err); 24 | } 25 | test.ok(/example test/.test(stdout)); 26 | test.ok(!/example test sub/.test(stdout)); 27 | test.done(); 28 | }); 29 | }; 30 | 31 | exports['runs top + nested suites with recursive flag'] = function (test) { 32 | exec(bin + ' ' + fixtures_path + ' -r', function (err, stdout, stderr) { 33 | if (err) { 34 | return test.done(err); 35 | } 36 | test.ok(/example test/.test(stdout)); 37 | test.ok(/example test sub/.test(stdout)); 38 | test.done(); 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /lib/reporters/lcov.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var nodeunit = require('../nodeunit'), 6 | path = require('path'); 7 | 8 | /** 9 | * Reporter info string 10 | */ 11 | 12 | exports.info = 'The LCOV reporter reads JS files instrumented by JSCoverage (http://siliconforks.com/jscoverage/) and outputs coverage data in the LCOV format (http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php)'; 13 | 14 | /** 15 | * Run all tests within each module, reporting the results to the command-line. 16 | * 17 | * @param {Array} files 18 | * @api public 19 | */ 20 | 21 | exports.run = function (files, options, callback) { 22 | 23 | var paths = files.map(function (p) { 24 | return path.resolve(p); 25 | }); 26 | 27 | nodeunit.runFiles(paths, { 28 | done: function (assertions) { 29 | var cov = (global || window)._$jscoverage || {}; 30 | 31 | Object.keys(cov).forEach(function (filename) { 32 | var data = cov[filename]; 33 | reportFile(filename, data); 34 | }); 35 | 36 | if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined); 37 | } 38 | }); 39 | }; 40 | 41 | function reportFile(filename, data) { 42 | console.log('SF:' + filename); 43 | 44 | data.source.forEach(function(line, num) { 45 | // increase the line number, as JS arrays are zero-based 46 | num++; 47 | 48 | if (data[num] !== undefined) { 49 | console.log('DA:' + num + ',' + data[num]); 50 | } 51 | }); 52 | 53 | console.log('end_of_record'); 54 | } 55 | -------------------------------------------------------------------------------- /share/nodeunit.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Styles taken from qunit.css 3 | */ 4 | 5 | h1#nodeunit-header, h1.nodeunit-header { 6 | padding: 15px; 7 | font-size: large; 8 | background-color: #06b; 9 | color: white; 10 | font-family: 'trebuchet ms', verdana, arial; 11 | margin: 0; 12 | } 13 | 14 | h1#nodeunit-header a { 15 | color: white; 16 | } 17 | 18 | h2#nodeunit-banner { 19 | height: 2em; 20 | border-bottom: 1px solid white; 21 | background-color: #eee; 22 | margin: 0; 23 | font-family: 'trebuchet ms', verdana, arial; 24 | } 25 | h2#nodeunit-banner.pass { 26 | background-color: green; 27 | } 28 | h2#nodeunit-banner.fail { 29 | background-color: red; 30 | } 31 | 32 | h2#nodeunit-userAgent, h2.nodeunit-userAgent { 33 | padding: 10px; 34 | background-color: #eee; 35 | color: black; 36 | margin: 0; 37 | font-size: small; 38 | font-weight: normal; 39 | font-family: 'trebuchet ms', verdana, arial; 40 | font-size: 10pt; 41 | } 42 | 43 | div#nodeunit-testrunner-toolbar { 44 | background: #eee; 45 | border-top: 1px solid black; 46 | padding: 10px; 47 | font-family: 'trebuchet ms', verdana, arial; 48 | margin: 0; 49 | font-size: 10pt; 50 | } 51 | 52 | ol#nodeunit-tests { 53 | font-family: 'trebuchet ms', verdana, arial; 54 | font-size: 10pt; 55 | } 56 | ol#nodeunit-tests li strong { 57 | cursor:pointer; 58 | } 59 | ol#nodeunit-tests .pass { 60 | color: green; 61 | } 62 | ol#nodeunit-tests .fail { 63 | color: red; 64 | } 65 | 66 | p#nodeunit-testresult { 67 | margin-left: 1em; 68 | font-size: 10pt; 69 | font-family: 'trebuchet ms', verdana, arial; 70 | } 71 | -------------------------------------------------------------------------------- /test/test-runtest.js: -------------------------------------------------------------------------------- 1 | /* THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! 2 | * You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build. 3 | * Only code on that line will be removed, its mostly to avoid requiring code 4 | * that is node specific 5 | */ 6 | 7 | var nodeunit = require('../lib/nodeunit'); // @REMOVE_LINE_FOR_BROWSER 8 | 9 | 10 | exports.testArgs = function (test) { 11 | test.ok(test.expect instanceof Function, 'test.expect'); 12 | test.ok(test.done instanceof Function, 'test.done'); 13 | test.ok(test.ok instanceof Function, 'test.ok'); 14 | test.ok(test.same instanceof Function, 'test.same'); 15 | test.ok(test.equals instanceof Function, 'test.equals'); 16 | test.done(); 17 | }; 18 | 19 | exports.testDoneCallback = function (test) { 20 | test.expect(4); 21 | nodeunit.runTest('testname', exports.testArgs, { 22 | testDone: function (name, assertions) { 23 | test.equals(assertions.failures(), 0, 'failures'); 24 | test.equals(assertions.length, 5, 'length'); 25 | test.ok(typeof assertions.duration === "number"); 26 | test.equals(name, 'testname'); 27 | } 28 | }, test.done); 29 | }; 30 | 31 | exports.testThrowError = function (test) { 32 | test.expect(3); 33 | var err = new Error('test'); 34 | var testfn = function (test) { 35 | throw err; 36 | }; 37 | nodeunit.runTest('testname', testfn, { 38 | log: function (assertion) { 39 | test.same(assertion.error, err, 'assertion.error'); 40 | }, 41 | testDone: function (name, assertions) { 42 | test.equals(assertions.failures(), 1); 43 | test.equals(assertions.length, 1); 44 | } 45 | }, test.done); 46 | }; 47 | -------------------------------------------------------------------------------- /test/test-httputil.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('../lib/nodeunit'); 2 | var httputil = require('../lib/utils').httputil; 3 | 4 | exports.testHttpUtilBasics = function (test) { 5 | 6 | test.expect(6); 7 | 8 | httputil(function (req, resp) { 9 | test.equal(req.method, 'PUT'); 10 | test.equal(req.url, '/newpair'); 11 | test.equal(req.headers.foo, 'bar'); 12 | 13 | resp.writeHead(500, {'content-type': 'text/plain'}); 14 | resp.end('failed'); 15 | }, function (server, client) { 16 | client.fetch('PUT', '/newpair', {'foo': 'bar'}, function (resp) { 17 | test.equal(resp.statusCode, 500); 18 | test.equal(resp.headers['content-type'], 'text/plain'); 19 | test.equal(resp.body, 'failed'); 20 | 21 | server.close(); 22 | test.done(); 23 | }); 24 | }); 25 | }; 26 | 27 | exports.testHttpUtilJsonHandling = function (test) { 28 | 29 | test.expect(9); 30 | 31 | httputil(function (req, resp) { 32 | test.equal(req.method, 'GET'); 33 | test.equal(req.url, '/'); 34 | test.equal(req.headers.foo, 'bar'); 35 | 36 | var testdata = {foo1: 'bar', foo2: 'baz'}; 37 | 38 | resp.writeHead(200, {'content-type': 'application/json'}); 39 | resp.end(JSON.stringify(testdata)); 40 | 41 | }, function (server, client) { 42 | client.fetch('GET', '/', {'foo': 'bar'}, function (resp) { 43 | test.equal(resp.statusCode, 200); 44 | test.equal(resp.headers['content-type'], 'application/json'); 45 | 46 | test.ok(resp.bodyAsObject); 47 | test.equal(typeof resp.bodyAsObject, 'object'); 48 | test.equal(resp.bodyAsObject.foo1, 'bar'); 49 | test.equal(resp.bodyAsObject.foo2, 'baz'); 50 | 51 | server.close(); 52 | test.done(); 53 | }); 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Nodeunit contributors (sorted alphabetically) 2 | ============================================ 3 | 4 | * **[Alex Gorbatchev](https://github.com/alexgorbatchev)** 5 | 6 | * Deeper default object inspection 7 | * Timeout to ensure flushing of console output (default reporter) 8 | 9 | * **[Alex Wolfe](https://github.com/alexkwolfe)** 10 | 11 | * HTML test reporter 12 | 13 | * **[Caolan McMahon](https://github.com/caolan)** 14 | 15 | * Author and maintainer 16 | * Most features develpopment 17 | 18 | * **[Carl Fürstenberg](https://github.com/azatoth)** 19 | 20 | * Debian-friendly Makefile, supports both 'node' and 'nodejs' executables 21 | * Sandbox utility 22 | * Minimal test reporter 23 | 24 | * **[Gerad Suyderhoud](https://github.com/gerad)** 25 | 26 | * First comand-line tool 27 | 28 | * **[Kadir Pekel](https://github.com/kadirpekel)** 29 | 30 | * Improvements to default test reporter 31 | * HTTP test utility 32 | 33 | * **[Λlisue](https://github.com/lambdalisue)** 34 | 35 | * Add machineout reporter 36 | 37 | * **[Matthias Lübken](https://github.com/luebken)** 38 | 39 | * Utility functions for tracking incomplete tests on exit 40 | 41 | * **[Oleg Efimov](https://github.com/Sannis)** 42 | 43 | * Adding 'make lint' and fixing nodelint errors 44 | * Option parsing, --help text and config file support 45 | * Reporters option for command-line tool 46 | 47 | * **[Orlando Vazquez](https://github.com/orlandov)** 48 | 49 | * Added jUnit XML reporter 50 | 51 | * **[Ryan Dahl](https://github.com/ry)** 52 | 53 | * Add package.json 54 | 55 | * **[Sam Stephenson](https://github.com/sstephenson)** 56 | 57 | * Coffee-script support 58 | 59 | * **[Thomas Mayfield](https://github.com/thegreatape)** 60 | 61 | * Async setUp and tearDown support for testCase 62 | 63 | * **[Maciej Małecki](https://github.com/mmalecki)** 64 | 65 | * Removal of `testCase` 66 | 67 | **[Full contributors list](https://github.com/caolan/nodeunit/contributors).** 68 | 69 | -------------------------------------------------------------------------------- /doc/nodeunit.md: -------------------------------------------------------------------------------- 1 | nodeunit(1) -- simple node.js unit testing tool 2 | =============================================== 3 | 4 | ## SYNOPSIS 5 | 6 | nodeunit [options] [ ...] 7 | 8 | ## DESCRIPTION 9 | 10 | Nodeunit is a simple unit testing tool based on the node.js assert module. 11 | 12 | * Simple to use 13 | * Just export the tests from a module 14 | * Helps you avoid common pitfalls when testing asynchronous code 15 | * Easy to add test cases with setUp and tearDown functions if you wish 16 | * Allows the use of mocks and stubs 17 | 18 | ## OPTIONS 19 | 20 | __--config FILE__: 21 | Load config options from a JSON file, allows the customisation 22 | of color schemes for the default test reporter etc. 23 | See bin/nodeunit.json for current available options. 24 | 25 | __--reporter FILE__: 26 | You can set the test reporter to a custom module or on of the modules 27 | in nodeunit/lib/reporters, when omitted, the default test runner is used. 28 | 29 | __--list-reporters__: 30 | List available build-in reporters. 31 | 32 | __-t testName__: 33 | Run specifc test only. 34 | 35 | __-f fullTestName__: 36 | Run specific test only. fullTestName is built so: "outerGroup - .. - innerGroup - testName". 37 | 38 | __-h__, __--help__: 39 | Display the help and exit. 40 | 41 | __-v__, __--version__: 42 | Output version information and exit. 43 | 44 | ____: 45 | You can run nodeunit on specific files or on all *\*.js* files inside 46 | a directory. 47 | 48 | ## AUTHORS 49 | 50 | Written by Caolan McMahon and other nodeunit contributors. 51 | Contributors list: . 52 | 53 | ## REPORTING BUGS 54 | 55 | Report nodeunit bugs to . 56 | 57 | ## COPYRIGHT 58 | 59 | Copyright © 2010 Caolan McMahon. 60 | Nodeunit has been released under the MIT license: 61 | . 62 | 63 | ## SEE ALSO 64 | 65 | node(1) 66 | 67 | -------------------------------------------------------------------------------- /lib/reporters/tap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var nodeunit = require('../nodeunit'), 6 | path = require('path'), 7 | assert = require('tap').assert, 8 | tap = require('tap'), 9 | fs = require('fs'); 10 | 11 | /** 12 | * Reporter info string 13 | */ 14 | 15 | exports.info = "TAP output"; 16 | 17 | /** 18 | * Run all tests within each module, reporting the results to the command-line. 19 | * 20 | * @param {Array} files 21 | * @api public 22 | */ 23 | 24 | exports.run = function (files, options, callback) { 25 | 26 | if (!options) { 27 | // load default options 28 | var content = fs.readFileSync( 29 | __dirname + '/../../bin/nodeunit.json', 'utf8' 30 | ); 31 | options = JSON.parse(content); 32 | } 33 | 34 | var paths = files.map(function (p) { 35 | return path.resolve(p); 36 | }); 37 | 38 | tap.pipe(process.stdout); 39 | 40 | nodeunit.runFiles(paths, { 41 | testStart: function (name) { 42 | tap.comment(name.toString()); 43 | }, 44 | testDone: function (name, assertions) { 45 | assertions.forEach(function (e) { 46 | var extra = {}; 47 | if (e.error) { 48 | extra.error = { 49 | name: e.error.name, 50 | message: e.error.message, 51 | stack: e.error.stack.split(/\n/).filter(function (line) { 52 | // exclude line of "types.js" 53 | return ! RegExp(/types.js:83:39/).test(line); 54 | }).join('\n') 55 | }; 56 | extra.wanted = e.error.expected; 57 | extra.found = e.error.actual; 58 | } 59 | tap.assert(e.passed(), e.message, extra); 60 | }); 61 | }, 62 | done: function (assertions) { 63 | tap.end(); 64 | if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined); 65 | } 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodeunit", 3 | "version": "0.11.3", 4 | "description": "Easy unit testing for node.js and the browser.", 5 | "maintainers": [ 6 | { 7 | "name": "Caolan McMahon", 8 | "web": "https://github.com/caolan" 9 | } 10 | ], 11 | "contributors": [ 12 | { 13 | "name": "Romain Beauxis", 14 | "web": "https://github.com/toots" 15 | }, 16 | { 17 | "name": "Alex Gorbatchev", 18 | "web": "https://github.com/alexgorbatchev" 19 | }, 20 | { 21 | "name": "Alex Wolfe", 22 | "web": "https://github.com/alexkwolfe" 23 | }, 24 | { 25 | "name": "Carl Fürstenberg", 26 | "web": "https://github.com/azatoth" 27 | }, 28 | { 29 | "name": "Gerad Suyderhoud", 30 | "web": "https://github.com/gerad" 31 | }, 32 | { 33 | "name": "Kadir Pekel", 34 | "web": "https://github.com/coffeemate" 35 | }, 36 | { 37 | "name": "Oleg Efimov", 38 | "web": "https://github.com/Sannis" 39 | }, 40 | { 41 | "name": "Orlando Vazquez", 42 | "web": "https://github.com/orlandov" 43 | }, 44 | { 45 | "name": "Ryan Dahl", 46 | "web": "https://github.com/ry" 47 | }, 48 | { 49 | "name": "Sam Stephenson", 50 | "web": "https://github.com/sstephenson" 51 | }, 52 | { 53 | "name": "Thomas Mayfield", 54 | "web": "https://github.com/thegreatape" 55 | }, 56 | { 57 | "name": "Elijah Insua ", 58 | "web": "http://tmpvar.com" 59 | } 60 | ], 61 | "repository": { 62 | "type": "git", 63 | "url": "http://github.com/caolan/nodeunit.git" 64 | }, 65 | "devDependencies": { 66 | "should": ">=11.1.0", 67 | "uglify-js": ">=2.7.3" 68 | }, 69 | "bugs": { 70 | "url": "http://github.com/caolan/nodeunit/issues" 71 | }, 72 | "license": "MIT", 73 | "directories": { 74 | "lib": "./lib", 75 | "doc": "./doc", 76 | "man": "./man1" 77 | }, 78 | "bin": { 79 | "nodeunit": "./bin/nodeunit" 80 | }, 81 | "dependencies": { 82 | "ejs": "^2.5.2", 83 | "tap": "^12.0.1" 84 | }, 85 | "scripts": { 86 | "test": "node ./bin/nodeunit" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /man1/nodeunit.1: -------------------------------------------------------------------------------- 1 | .\" Generated with Ronnjs/v0.1 2 | .\" http://github.com/kapouer/ronnjs/ 3 | . 4 | .TH "NODEUNIT" "1" "October 2010" "" "" 5 | . 6 | .SH "NAME" 7 | \fBnodeunit\fR \-\- simple node\.js unit testing tool 8 | . 9 | .SH "SYNOPSIS" 10 | . 11 | .nf 12 | nodeunit [options] [ \.\.\.] 13 | . 14 | .fi 15 | . 16 | .SH "DESCRIPTION" 17 | Nodeunit is a simple unit testing tool based on the node\.js assert module\. 18 | . 19 | .IP "\(bu" 4 20 | Simple to use 21 | . 22 | .IP "\(bu" 4 23 | Just export the tests from a module 24 | . 25 | .IP "\(bu" 4 26 | Helps you avoid common pitfalls when testing asynchronous code 27 | . 28 | .IP "\(bu" 4 29 | Easy to add test cases with setUp and tearDown functions if you wish 30 | . 31 | .IP "\(bu" 4 32 | Allows the use of mocks and stubs 33 | . 34 | .IP "" 0 35 | . 36 | .SH "OPTIONS" 37 | \fB\-\-config FILE\fR: 38 | . 39 | .br 40 | Load config options from a JSON file, allows the customisation 41 | of color schemes for the default test reporter etc\. 42 | See bin/nodeunit\.json for current available options\. 43 | . 44 | .P 45 | \fB\-\-reporter FILE\fR: 46 | . 47 | .br 48 | You can set the test reporter to a custom module or on of the modules 49 | in nodeunit/lib/reporters, when omitted, the default test runner is used\. 50 | . 51 | .P 52 | \fB\-\-list\-reporters\fR: 53 | . 54 | .br 55 | List available build\-in reporters\. 56 | . 57 | .P 58 | \fB\-h\fR, \fB\-\-help\fR: 59 | . 60 | .br 61 | Display the help and exit\. 62 | . 63 | .P 64 | \fB\-v\fR, \fB\-\-version\fR: 65 | . 66 | .br 67 | Output version information and exit\. 68 | . 69 | .P 70 | \fB\fR: 71 | You can run nodeunit on specific files or on all \fI*\.js\fR files inside 72 | . 73 | .br 74 | a directory\. 75 | . 76 | .SH "AUTHORS" 77 | Written by Caolan McMahon and other nodeunit contributors\. 78 | . 79 | .br 80 | Contributors list: \fIhttp://github\.com/caolan/nodeunit/contributors\fR\|\. 81 | . 82 | .SH "REPORTING BUGS" 83 | Report nodeunit bugs to \fIhttp://github\.com/caolan/nodeunit/issues\fR\|\. 84 | . 85 | .SH "COPYRIGHT" 86 | Copyright © 2010 Caolan McMahon\. 87 | . 88 | .br 89 | Nodeunit has been released under the MIT license: 90 | . 91 | .br 92 | \fIhttp://github\.com/caolan/nodeunit/raw/master/LICENSE\fR\|\. 93 | . 94 | .SH "SEE ALSO" 95 | node(1) 96 | -------------------------------------------------------------------------------- /examples/nested/nested_reporter_test.unit.js: -------------------------------------------------------------------------------- 1 | var testCase = require('nodeunit').testCase; 2 | /* 3 | This is an example test suite to demonstrate the nested test reporter. 4 | Run with --reporter nested, e.g., 5 | nodeunit --reporter nested nested_reporter_test.unit.js 6 | 7 | The test output should be something like: 8 | 9 | nested_reporter_test.unit.js 10 | Test 0.1 (pass) 11 | TC 1 12 | TC 1.1 13 | Test 1.1.1 (pass) 14 | TC 2 15 | TC 2.1 16 | TC 2.1.1 17 | Test 2.1.1.1 (pass) 18 | Test 2.1.1.2 (pass) 19 | TC 2.2.1 20 | Test 2.2.1.1 (pass) 21 | TC 2.2.1.1 22 | Test 2.2.1.1.1 (pass) 23 | Test 2.2.1.2 (pass) 24 | TC 3 25 | TC 3.1 26 | TC 3.1.1 27 | Test 3.1.1.1 (should fail) (fail) ✖ 28 | AssertionError: false == true 29 | // stack trace here. 30 | 31 | FAILURES: 1/8 assertions failed (6ms) 32 | */ 33 | 34 | module.exports = testCase({ 35 | "Test 0.1": function(test) { 36 | test.ok(true); 37 | test.done(); 38 | }, 39 | 40 | "TC 1": testCase({ 41 | "TC 1.1": testCase({ 42 | "Test 1.1.1": function(test) { 43 | test.ok(true); 44 | test.done(); 45 | } 46 | }) 47 | }), 48 | 49 | "TC 2": testCase({ 50 | "TC 2.1": testCase({ 51 | "TC 2.1.1": testCase({ 52 | "Test 2.1.1.1": function(test) { 53 | test.ok(true); 54 | test.done(); 55 | }, 56 | 57 | "Test 2.1.1.2": function(test) { 58 | test.ok(true); 59 | test.done(); 60 | } 61 | }), 62 | 63 | "TC 2.2.1": testCase({ 64 | "Test 2.2.1.1": function(test) { 65 | test.ok(true); 66 | test.done(); 67 | }, 68 | 69 | "TC 2.2.1.1": testCase({ 70 | "Test 2.2.1.1.1": function(test) { 71 | test.ok(true); 72 | test.done(); 73 | }, 74 | }), 75 | 76 | "Test 2.2.1.2": function(test) { 77 | test.ok(true); 78 | test.done(); 79 | } 80 | }) 81 | }) 82 | }), 83 | 84 | "TC 3": testCase({ 85 | "TC 3.1": testCase({ 86 | "TC 3.1.1": testCase({ 87 | "Test 3.1.1.1 (should fail)": function(test) { 88 | test.ok(false); 89 | test.done(); 90 | } 91 | }) 92 | }) 93 | }) 94 | }); 95 | -------------------------------------------------------------------------------- /test/test-bettererrors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Test utils.betterErrors. utils.betterErrors should provide sensible error messages even when the error does not 3 | * contain expected, actual or operator. 4 | */ 5 | var assert = require("../lib/assert"); 6 | var should = require("should"); 7 | var types = require("../lib/types"); 8 | var util = require('util'); 9 | var utils = require("../lib/utils"); 10 | 11 | function betterErrorStringFromError(error) { 12 | var assertion = types.assertion({error: error}); 13 | var better = utils.betterErrors(assertion); 14 | return better.error.stack.toString(); 15 | } 16 | 17 | function performBasicChecks(betterErrorString) { 18 | betterErrorString.should.containEql("AssertionError"); 19 | betterErrorString.should.containEql("test-bettererrors"); 20 | //betterErrorString.should.not.include("undefined"); 21 | } 22 | 23 | /** 24 | * Control test. Provide an AssertionError that contains actual, expected operator values. 25 | * @param test the test object from nodeunit 26 | */ 27 | exports.testEqual = function (test) { 28 | try { 29 | assert.equal(true, false); 30 | } catch (error) { 31 | var betterErrorString = betterErrorStringFromError(error); 32 | performBasicChecks(betterErrorString); 33 | 34 | betterErrorString.should.containEql("true"); 35 | betterErrorString.should.containEql("false"); 36 | betterErrorString.should.containEql("=="); 37 | 38 | test.done(); 39 | } 40 | }; 41 | 42 | /** 43 | * Test an AssertionError that does not contain actual, expected or operator values. 44 | * @param test the test object from nodeunit 45 | */ 46 | exports.testAssertThrows = function (test) { 47 | try { 48 | assert.throws(function () { 49 | }); 50 | } catch (error) { 51 | var betterErrorString = betterErrorStringFromError(error); 52 | performBasicChecks(betterErrorString); 53 | test.done(); 54 | } 55 | }; 56 | 57 | /** 58 | * Test with an error that is not an AssertionError. 59 | * 60 | * This function name MUST NOT include "AssertionError" because one of the 61 | * tests it performs asserts that the returned error string does not contain 62 | * the "AssertionError" term. If this function name does include that term, it 63 | * will show up in the stack trace and the test will fail! 64 | * @param test the test object from nodeunit 65 | */ 66 | exports.testErrorIsNotAssertion = function (test) { 67 | try { 68 | throw new Error("test error"); 69 | } catch (error) { 70 | var betterErrorString = betterErrorStringFromError(error); 71 | betterErrorString.should.not.containEql("AssertionError"); 72 | betterErrorString.should.containEql("Error"); 73 | betterErrorString.should.containEql("test error"); 74 | betterErrorString.should.containEql("test-bettererrors"); 75 | betterErrorString.should.not.containEql("undefined"); 76 | test.done(); 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /lib/nodeunit.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies 9 | */ 10 | 11 | var async = require('../deps/async'), 12 | types = require('./types'), 13 | utils = require('./utils'), 14 | core = require('./core'), 15 | reporters = require('./reporters'), 16 | assert = require('./assert'), 17 | path = require('path'), 18 | events = require('events'); 19 | 20 | 21 | /** 22 | * Export sub-modules. 23 | */ 24 | 25 | exports.types = types; 26 | exports.utils = utils; 27 | exports.reporters = reporters; 28 | exports.assert = assert; 29 | 30 | // backwards compatibility 31 | exports.testrunner = { 32 | run: function () { 33 | console.log( 34 | 'WARNING: nodeunit.testrunner is going to be deprecated, please ' + 35 | 'use nodeunit.reporters.default instead!' 36 | ); 37 | return reporters['default'].run.apply(this, arguments); 38 | } 39 | }; 40 | 41 | 42 | /** 43 | * Export all core functions 44 | */ 45 | 46 | for (var k in core) { 47 | exports[k] = core[k]; 48 | }; 49 | 50 | 51 | /** 52 | * Load modules from paths array and run all exported tests in series. If a path 53 | * is a directory, load all supported file types inside it as modules. This only 54 | * reads 1 level deep in the directory and does not recurse through 55 | * sub-directories. 56 | * 57 | * @param {Array} paths 58 | * @param {Object} opt 59 | * @api public 60 | */ 61 | 62 | exports.runFiles = function (paths, opt) { 63 | var all_assertions = []; 64 | var options = types.options(opt); 65 | var start = new Date().getTime(); 66 | 67 | if (!paths.length) { 68 | return options.done(types.assertionList(all_assertions)); 69 | } 70 | 71 | utils.modulePaths(paths, function (err, files) { 72 | if (err) throw err; 73 | async.concatSeries(files, function (file, cb) { 74 | var name = path.basename(file); 75 | exports.runModule(name, require(file), options, cb); 76 | }, 77 | function (err, all_assertions) { 78 | var end = new Date().getTime(); 79 | exports.done() 80 | options.done(types.assertionList(all_assertions, end - start)); 81 | }); 82 | }, options.recursive); 83 | 84 | }; 85 | 86 | /* Export all prototypes from events.EventEmitter */ 87 | var label; 88 | for (label in events.EventEmitter.prototype) { 89 | exports[label] = events.EventEmitter.prototype[label]; 90 | } 91 | 92 | /* Emit event 'complete' on completion of a test suite. */ 93 | exports.complete = function(name, assertions) 94 | { 95 | exports.emit('complete', name, assertions); 96 | }; 97 | 98 | /* Emit event 'complete' on completion of all tests. */ 99 | exports.done = function() 100 | { 101 | exports.emit('done'); 102 | }; 103 | 104 | module.exports = exports; 105 | -------------------------------------------------------------------------------- /lib/reporters/eclipse.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies 9 | */ 10 | 11 | var nodeunit = require('../nodeunit'), 12 | utils = require('../utils'), 13 | fs = require('fs'), 14 | track = require('../track'), 15 | path = require('path'), 16 | AssertionError = require('../assert').AssertionError; 17 | 18 | /** 19 | * Reporter info string 20 | */ 21 | 22 | exports.info = "Reporter for eclipse plugin"; 23 | 24 | 25 | /** 26 | * Run all tests within each module, reporting the results to the command-line. 27 | * 28 | * @param {Array} files 29 | * @api public 30 | */ 31 | 32 | exports.run = function (files, options, callback) { 33 | 34 | var start = new Date().getTime(); 35 | var paths = files.map(function (p) { 36 | if (p.indexOf('/') === 0) { 37 | return p; 38 | } 39 | return path.resolve(p); 40 | }); 41 | var tracker = track.createTracker(function (tracker) { 42 | if (tracker.unfinished()) { 43 | console.log(''); 44 | console.log('FAILURES: Undone tests (or their setups/teardowns): '); 45 | var names = tracker.names(); 46 | for (var i = 0; i < names.length; i += 1) { 47 | console.log('- ' + names[i]); 48 | } 49 | console.log(''); 50 | console.log('To fix this, make sure all tests call test.done()'); 51 | process.reallyExit(tracker.unfinished()); 52 | } 53 | }); 54 | 55 | nodeunit.runFiles(paths, { 56 | testspec: undefined, 57 | moduleStart: function (name) { 58 | console.log('\n' + name); 59 | }, 60 | testDone: function (name, assertions) { 61 | tracker.remove(name); 62 | 63 | if (!assertions.failures()) { 64 | console.log('✔ ' + name); 65 | } 66 | else { 67 | console.log('✖ ' + name + '\n'); 68 | assertions.forEach(function (a) { 69 | if (a.failed()) { 70 | a = utils.betterErrors(a); 71 | if (a.error instanceof AssertionError && a.message) { 72 | console.log( 73 | 'Assertion Message: ' + a.message 74 | ); 75 | } 76 | console.log(a.error.stack + '\n'); 77 | } 78 | }); 79 | } 80 | }, 81 | done: function (assertions, end) { 82 | var end = end || new Date().getTime(); 83 | var duration = end - start; 84 | if (assertions.failures()) { 85 | console.log( 86 | '\n' + 'FAILURES: ' + assertions.failures() + 87 | '/' + assertions.length + ' assertions failed (' + 88 | assertions.duration + 'ms)' 89 | ); 90 | } 91 | else { 92 | console.log( 93 | '\n' + 'OK: ' + assertions.length + 94 | ' assertions (' + assertions.duration + 'ms)' 95 | ); 96 | } 97 | 98 | if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined); 99 | }, 100 | testStart: function (name) { 101 | tracker.put(name); 102 | } 103 | }); 104 | }; 105 | -------------------------------------------------------------------------------- /lib/reporters/skip_passed.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies 9 | */ 10 | 11 | var nodeunit = require('../nodeunit'), 12 | utils = require('../utils'), 13 | fs = require('fs'), 14 | path = require('path'), 15 | AssertionError = require('../assert').AssertionError; 16 | 17 | /** 18 | * Reporter info string 19 | */ 20 | 21 | exports.info = "Skip passed tests output"; 22 | 23 | /** 24 | * Run all tests within each module, reporting the results to the command-line. 25 | * 26 | * @param {Array} files 27 | * @api public 28 | */ 29 | 30 | exports.run = function (files, options, callback) { 31 | 32 | if (!options) { 33 | // load default options 34 | var content = fs.readFileSync( 35 | __dirname + '/../../bin/nodeunit.json', 'utf8' 36 | ); 37 | options = JSON.parse(content); 38 | } 39 | 40 | var error = function (str) { 41 | return options.error_prefix + str + options.error_suffix; 42 | }; 43 | var ok = function (str) { 44 | return options.ok_prefix + str + options.ok_suffix; 45 | }; 46 | var bold = function (str) { 47 | return options.bold_prefix + str + options.bold_suffix; 48 | }; 49 | var assertion_message = function (str) { 50 | return options.assertion_prefix + str + options.assertion_suffix; 51 | }; 52 | var pass_indicator = process.platform === 'win32' ? '\u221A' : '✔'; 53 | var fail_indicator = process.platform === 'win32' ? '\u00D7' : '✖'; 54 | 55 | var start = new Date().getTime(); 56 | var paths = files.map(function (p) { 57 | return path.resolve(p); 58 | }); 59 | 60 | nodeunit.runFiles(paths, { 61 | testspec: options.testspec, 62 | testFullSpec: options.testFullSpec, 63 | moduleStart: function (name) { 64 | console.log('\n' + bold(name)); 65 | }, 66 | testDone: function (name, assertions) { 67 | if (assertions.failures()) { 68 | console.log(error(fail_indicator + ' ' + name) + '\n'); 69 | assertions.forEach(function (a) { 70 | if (a.failed()) { 71 | a = utils.betterErrors(a); 72 | if (a.error instanceof AssertionError && a.message) { 73 | console.log( 74 | 'Assertion Message: ' + assertion_message(a.message) 75 | ); 76 | } 77 | console.log(a.error.stack + '\n'); 78 | } 79 | }); 80 | } 81 | }, 82 | moduleDone: function (name, assertions) { 83 | if (!assertions.failures()) { 84 | console.log(pass_indicator + ' all tests passed'); 85 | } 86 | else { 87 | console.log(error(fail_indicator + ' some tests failed')); 88 | } 89 | }, 90 | done: function (assertions) { 91 | var end = new Date().getTime(); 92 | var duration = end - start; 93 | if (assertions.failures()) { 94 | console.log( 95 | '\n' + bold(error('FAILURES: ')) + assertions.failures() + 96 | '/' + assertions.length + ' assertions failed (' + 97 | assertions.duration + 'ms)' 98 | ); 99 | } 100 | else { 101 | console.log( 102 | '\n' + bold(ok('OK: ')) + assertions.length + 103 | ' assertions (' + assertions.duration + 'ms)' 104 | ); 105 | } 106 | 107 | if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined); 108 | } 109 | }); 110 | }; 111 | -------------------------------------------------------------------------------- /lib/reporters/html.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies 9 | */ 10 | 11 | var nodeunit = require('../nodeunit'), 12 | utils = require('../utils'), 13 | fs = require('fs'), 14 | path = require('path'), 15 | AssertionError = require('../assert').AssertionError; 16 | 17 | /** 18 | * Reporter info string 19 | */ 20 | 21 | exports.info = "Report tests result as HTML"; 22 | 23 | /** 24 | * Run all tests within each module, reporting the results to the command-line. 25 | * 26 | * @param {Array} files 27 | * @api public 28 | */ 29 | 30 | exports.run = function (files, options, callback) { 31 | 32 | var start = new Date().getTime(); 33 | var paths = files.map(function (p) { 34 | return path.resolve(p); 35 | }); 36 | 37 | console.log(''); 38 | console.log(''); 39 | console.log(''); 40 | console.log(''); 54 | console.log(''); 55 | console.log(''); 56 | nodeunit.runFiles(paths, { 57 | testspec: options.testspec, 58 | testFullSpec: options.testFullSpec, 59 | moduleStart: function (name) { 60 | console.log('

' + name + '

'); 61 | console.log('
    '); 62 | }, 63 | testDone: function (name, assertions) { 64 | if (!assertions.failures()) { 65 | console.log('
  1. ' + name + '
  2. '); 66 | } 67 | else { 68 | console.log('
  3. ' + name); 69 | assertions.forEach(function (a) { 70 | if (a.failed()) { 71 | a = utils.betterErrors(a); 72 | if (a.error instanceof AssertionError && a.message) { 73 | console.log('
    ' + 74 | 'Assertion Message: ' + a.message + 75 | '
    '); 76 | } 77 | console.log('
    ');
     78 |                         console.log(a.error.stack);
     79 |                         console.log('
    '); 80 | } 81 | }); 82 | console.log('
  4. '); 83 | } 84 | }, 85 | moduleDone: function () { 86 | console.log('
'); 87 | }, 88 | done: function (assertions) { 89 | var end = new Date().getTime(); 90 | var duration = end - start; 91 | if (assertions.failures()) { 92 | console.log( 93 | '

FAILURES: ' + assertions.failures() + 94 | '/' + assertions.length + ' assertions failed (' + 95 | assertions.duration + 'ms)

' 96 | ); 97 | } 98 | else { 99 | console.log( 100 | '

OK: ' + assertions.length + 101 | ' assertions (' + assertions.duration + 'ms)

' 102 | ); 103 | } 104 | console.log(''); 105 | console.log(''); 106 | 107 | if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined); 108 | } 109 | }); 110 | }; 111 | -------------------------------------------------------------------------------- /lib/reporters/machineout.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * 4 | * @author Alisue (lambdalisue@hashnote.net) 5 | * @url http://hashnote.net/ 6 | * 7 | * Copyright (c) 2011 Alisue 8 | * MIT Licensed 9 | */ 10 | 11 | /** 12 | * Module dependencies 13 | */ 14 | 15 | var nodeunit = require('../nodeunit'), 16 | utils = require('../utils'), 17 | fs = require('fs'), 18 | track = require('../track'), 19 | path = require('path'), 20 | AssertionError = require('../assert').AssertionError; 21 | 22 | /** 23 | * Reporter info string 24 | */ 25 | 26 | exports.info = "Tests reporter for machinally analysis"; 27 | 28 | 29 | /** 30 | * Run all tests within each module, reporting the results to the command-line. 31 | * 32 | * @param {Array} files 33 | * @api public 34 | */ 35 | 36 | exports.run = function (files, options, callback) { 37 | // options doesn't effect 38 | 39 | var parseStack = function (stack, delimiter) { 40 | var parseTrace = function (trace) { 41 | var filename, row, column; 42 | pattern1 = /\s{4}at\s\S+\s\(([^:]+):(\d+):(\d+)\)/; 43 | pattern2 = /\s{4}at\s([^:]+):(\d+):(\d+)/; 44 | 45 | if (trace.match(pattern1) !== null) { 46 | filename = RegExp.$1; 47 | row = RegExp.$2; 48 | column = RegExp.$3; 49 | } else if (trace.match(pattern2) !== null) { 50 | filename = RegExp.$1; 51 | row = RegExp.$2; 52 | column = RegExp.$3; 53 | } else { 54 | throw new Error("Could not parse a line of stack trace: " + trace); 55 | } 56 | return {filename: filename, row: row, column: column}; 57 | }; 58 | if (delimiter === undefined) { 59 | delimiter = ':'; 60 | } 61 | traceback = stack.split('\n'); 62 | firstline = traceback.shift(); 63 | trace = parseTrace(traceback[0]); 64 | return {filename: trace.filename, row: trace.row, column: trace.column, message: firstline}; 65 | }; 66 | var createErrorMessage = function(type, name, filename, row, column, message){ 67 | return [type, name, filename, row, column, message].join(":"); 68 | }; 69 | var paths = files.map(function (p) { 70 | return path.resolve(p); 71 | }); 72 | var tracker = track.createTracker(function (tracker) { 73 | if (tracker.unfinished()) { 74 | var names = tracker.names(); 75 | for (var i = 0; i < names.length; i += 1) { 76 | console.log(createErrorMessage( 77 | 'Error', names[i], 78 | '', '', '', 79 | 'Undone tests - To fix this, make sure all tests call test.done()' 80 | )); 81 | } 82 | process.reallyExit(tracker.unfinished()); 83 | } 84 | }); 85 | 86 | nodeunit.runFiles(paths, { 87 | testspec: options.testspec, 88 | testFullSpec: options.testFullSpec, 89 | moduleStart: function (name) {}, 90 | testDone: function (name, assertions) { 91 | tracker.remove(name); 92 | if (assertions.failures()) { 93 | assertions.forEach(function (a) { 94 | var stacks, message, filename, row, column; 95 | if (a.failed()) { 96 | stackinfo = parseStack(a.error.stack, ':'); 97 | console.log(createErrorMessage( 98 | 'Fail', name, stackinfo.filename, 99 | stackinfo.row, stackinfo.column, stackinfo.message)); 100 | } 101 | }); 102 | } 103 | }, 104 | done: function (assertions, end) { 105 | if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined); 106 | }, 107 | testStart: function(name) { 108 | tracker.put(name); 109 | } 110 | }); 111 | }; 112 | 113 | -------------------------------------------------------------------------------- /test/test-failing-callbacks.js: -------------------------------------------------------------------------------- 1 | var nodeunit = require('../lib/nodeunit'); 2 | 3 | 4 | exports.testFailingLog = function (test) { 5 | test.expect(3); 6 | 7 | // this is meant to bubble to the top, and will be ignored for the purposes 8 | // of testing: 9 | var ignored_error = new Error('ignore this callback error'); 10 | var err_handler = function (err) { 11 | if (err && err.message !== ignored_error.message) { 12 | throw err; 13 | } 14 | }; 15 | process.addListener('uncaughtException', err_handler); 16 | 17 | // A failing callback should not affect the test outcome 18 | var testfn = function (test) { 19 | test.ok(true, 'test.ok'); 20 | test.done(); 21 | }; 22 | nodeunit.runTest('testname', testfn, { 23 | log: function (assertion) { 24 | test.ok(true, 'log called'); 25 | throw ignored_error; 26 | }, 27 | testDone: function (name, assertions) { 28 | test.equals(assertions.failures(), 0, 'failures'); 29 | test.equals(assertions.length, 1, 'total'); 30 | process.removeListener('uncaughtException', err_handler); 31 | } 32 | }, test.done); 33 | }; 34 | 35 | exports.testFailingTestDone = function (test) { 36 | test.expect(2); 37 | 38 | var ignored_error = new Error('ignore this callback error'); 39 | var err_handler = function (err) { 40 | if (err && err.message !== ignored_error.message) { 41 | throw err; 42 | } 43 | }; 44 | process.addListener('uncaughtException', err_handler); 45 | 46 | // A failing callback should not affect the test outcome 47 | var testfn = function (test) { 48 | test.done(); 49 | }; 50 | nodeunit.runTest('testname', testfn, { 51 | log: function (assertion) { 52 | test.ok(false, 'log should not be called'); 53 | }, 54 | testDone: function (name, assertions) { 55 | test.equals(assertions.failures(), 0, 'failures'); 56 | test.equals(assertions.length, 0, 'total'); 57 | process.nextTick(function () { 58 | process.removeListener('uncaughtException', err_handler); 59 | test.done(); 60 | }); 61 | throw ignored_error; 62 | } 63 | }, function () {}); 64 | }; 65 | 66 | exports.testAssertionObj = function (test) { 67 | test.expect(4); 68 | var testfn = function (test) { 69 | test.ok(true, 'ok true'); 70 | test.done(); 71 | }; 72 | nodeunit.runTest('testname', testfn, { 73 | log: function (assertion) { 74 | test.ok(assertion.passed() === true, 'assertion.passed'); 75 | test.ok(assertion.failed() === false, 'assertion.failed'); 76 | }, 77 | testDone: function (name, assertions) { 78 | test.equals(assertions.failures(), 0, 'failures'); 79 | test.equals(assertions.length, 1, 'total'); 80 | } 81 | }, test.done); 82 | }; 83 | 84 | exports.testLogOptional = function (test) { 85 | test.expect(2); 86 | var testfn = function (test) { 87 | test.ok(true, 'ok true'); 88 | test.done(); 89 | }; 90 | nodeunit.runTest('testname', testfn, { 91 | testDone: function (name, assertions) { 92 | test.equals(assertions.failures(), 0, 'failures'); 93 | test.equals(assertions.length, 1, 'total'); 94 | } 95 | }, test.done); 96 | }; 97 | 98 | exports.testExpectWithFailure = function (test) { 99 | test.expect(3); 100 | var testfn = function (test) { 101 | test.expect(1); 102 | test.ok(false, 'test.ok'); 103 | test.done(); 104 | }; 105 | nodeunit.runTest('testname', testfn, { 106 | log: function (assertion) { 107 | test.equals(assertion.method, 'ok', 'assertion.method'); 108 | }, 109 | testDone: function (name, assertions) { 110 | test.equals(assertions.failures(), 1, 'failures'); 111 | test.equals(assertions.length, 1, 'total'); 112 | } 113 | }, test.done); 114 | }; 115 | -------------------------------------------------------------------------------- /lib/reporters/verbose.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies 9 | */ 10 | 11 | var nodeunit = require('../nodeunit'), 12 | utils = require('../utils'), 13 | fs = require('fs'), 14 | track = require('../track'), 15 | path = require('path'), 16 | AssertionError = require('../assert').AssertionError; 17 | 18 | /** 19 | * Reporter info string 20 | */ 21 | 22 | exports.info = "Verbose tests reporter" 23 | 24 | 25 | /** 26 | * Run all tests within each module, reporting the results to the command-line. 27 | * 28 | * @param {Array} files 29 | * @api public 30 | */ 31 | 32 | exports.run = function (files, options, callback) { 33 | 34 | if (!options) { 35 | // load default options 36 | var content = fs.readFileSync( 37 | __dirname + '/../../bin/nodeunit.json', 'utf8' 38 | ); 39 | options = JSON.parse(content); 40 | } 41 | 42 | var error = function (str) { 43 | return options.error_prefix + str + options.error_suffix; 44 | }; 45 | var ok = function (str) { 46 | return options.ok_prefix + str + options.ok_suffix; 47 | }; 48 | var bold = function (str) { 49 | return options.bold_prefix + str + options.bold_suffix; 50 | }; 51 | var assertion_message = function (str) { 52 | return options.assertion_prefix + str + options.assertion_suffix; 53 | }; 54 | var pass_indicator = process.platform === 'win32' ? '\u221A' : '✔'; 55 | var fail_indicator = process.platform === 'win32' ? '\u00D7' : '✖'; 56 | 57 | var start = new Date().getTime(); 58 | var paths = files.map(function (p) { 59 | return path.resolve(p); 60 | }); 61 | var tracker = track.createTracker(function (tracker) { 62 | if (tracker.unfinished()) { 63 | console.log(''); 64 | console.log(error(bold( 65 | 'FAILURES: Undone tests (or their setups/teardowns): ' 66 | ))); 67 | var names = tracker.names(); 68 | for (var i = 0; i < names.length; i += 1) { 69 | console.log('- ' + names[i]); 70 | } 71 | console.log(''); 72 | console.log('To fix this, make sure all tests call test.done()'); 73 | process.reallyExit(tracker.unfinished()); 74 | } 75 | }); 76 | 77 | nodeunit.runFiles(paths, { 78 | testspec: options.testspec, 79 | testFullSpec: options.testFullSpec, 80 | moduleStart: function (name) { 81 | console.log('\n' + bold(name)); 82 | }, 83 | testDone: function (name, assertions) { 84 | tracker.remove(name); 85 | 86 | if (!assertions.failures()) { 87 | console.log(pass_indicator + ' ' + name); 88 | } 89 | else { 90 | console.log(error(fail_indicator + ' ' + name)); 91 | } 92 | // verbose so print everything 93 | assertions.forEach(function (a) { 94 | if (a.failed()) { 95 | console.log(error(' ' + fail_indicator + ' ' + a.message)); 96 | a = utils.betterErrors(a); 97 | console.log(' ' + a.error.stack); 98 | } 99 | else { 100 | console.log(' ' + pass_indicator + ' ' + a.message); 101 | } 102 | }); 103 | }, 104 | done: function (assertions, end) { 105 | var end = end || new Date().getTime(); 106 | var duration = end - start; 107 | if (assertions.failures()) { 108 | console.log( 109 | '\n' + bold(error('FAILURES: ')) + assertions.failures() + 110 | '/' + assertions.length + ' assertions failed (' + 111 | assertions.duration + 'ms)' 112 | ); 113 | } 114 | else { 115 | console.log( 116 | '\n' + bold(ok('OK: ')) + assertions.length + 117 | ' assertions (' + assertions.duration + 'ms)' 118 | ); 119 | } 120 | 121 | if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined); 122 | }, 123 | testStart: function(name) { 124 | tracker.put(name); 125 | } 126 | }); 127 | }; 128 | -------------------------------------------------------------------------------- /lib/reporters/default.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies 9 | */ 10 | 11 | var nodeunit = require('../nodeunit'), 12 | utils = require('../utils'), 13 | fs = require('fs'), 14 | track = require('../track'), 15 | path = require('path'), 16 | AssertionError = require('../assert').AssertionError; 17 | 18 | /** 19 | * Reporter info string 20 | */ 21 | 22 | exports.info = "Default tests reporter"; 23 | 24 | 25 | /** 26 | * Run all tests within each module, reporting the results to the command-line. 27 | * 28 | * @param {Array} files 29 | * @api public 30 | */ 31 | 32 | exports.run = function (files, options, callback) { 33 | 34 | if (!options) { 35 | // load default options 36 | var content = fs.readFileSync( 37 | __dirname + '/../../bin/nodeunit.json', 'utf8' 38 | ); 39 | options = JSON.parse(content); 40 | } 41 | 42 | var error = function (str) { 43 | return options.error_prefix + str + options.error_suffix; 44 | }; 45 | var ok = function (str) { 46 | return options.ok_prefix + str + options.ok_suffix; 47 | }; 48 | var bold = function (str) { 49 | return options.bold_prefix + str + options.bold_suffix; 50 | }; 51 | var assertion_message = function (str) { 52 | return options.assertion_prefix + str + options.assertion_suffix; 53 | }; 54 | var pass_indicator = process.platform === 'win32' ? '\u221A' : '✔'; 55 | var fail_indicator = process.platform === 'win32' ? '\u00D7' : '✖'; 56 | 57 | var start = new Date().getTime(); 58 | var tracker = track.createTracker(function (tracker) { 59 | if (tracker.unfinished()) { 60 | console.log(''); 61 | console.log(error(bold( 62 | 'FAILURES: Undone tests (or their setups/teardowns): ' 63 | ))); 64 | var names = tracker.names(); 65 | for (var i = 0; i < names.length; i += 1) { 66 | console.log('- ' + names[i]); 67 | } 68 | console.log(''); 69 | console.log('To fix this, make sure all tests call test.done()'); 70 | process.reallyExit(tracker.unfinished()); 71 | } 72 | }); 73 | 74 | var opts = { 75 | testspec: options.testspec, 76 | testFullSpec: options.testFullSpec, 77 | recursive: options.recursive, 78 | moduleStart: function (name) { 79 | console.log('\n' + bold(name)); 80 | }, 81 | testDone: function (name, assertions) { 82 | tracker.remove(name); 83 | 84 | if (!assertions.failures()) { 85 | console.log(pass_indicator + ' ' + name); 86 | } 87 | else { 88 | console.log(error(fail_indicator + ' ' + name) + '\n'); 89 | assertions.forEach(function (a) { 90 | if (a.failed()) { 91 | a = utils.betterErrors(a); 92 | if (a.error instanceof AssertionError && a.message) { 93 | console.log( 94 | 'Assertion Message: ' + 95 | assertion_message(a.message) 96 | ); 97 | } 98 | console.log(a.error.stack + '\n'); 99 | } 100 | }); 101 | } 102 | }, 103 | done: function (assertions, end) { 104 | var end = end || new Date().getTime(); 105 | var duration = end - start; 106 | if (assertions.failures()) { 107 | console.log( 108 | '\n' + bold(error('FAILURES: ')) + assertions.failures() + 109 | '/' + assertions.length + ' assertions failed (' + 110 | assertions.duration + 'ms)' 111 | ); 112 | } 113 | else { 114 | console.log( 115 | '\n' + bold(ok('OK: ')) + assertions.length + 116 | ' assertions (' + assertions.duration + 'ms)' 117 | ); 118 | } 119 | 120 | if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined); 121 | }, 122 | testStart: function(name) { 123 | tracker.put(name); 124 | } 125 | }; 126 | if (files && files.length) { 127 | var paths = files.map(function (p) { 128 | return path.resolve(p); 129 | }); 130 | nodeunit.runFiles(paths, opts); 131 | } else { 132 | nodeunit.runModules(files,opts); 133 | } 134 | }; 135 | -------------------------------------------------------------------------------- /lib/reporters/minimal.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies 9 | */ 10 | 11 | var nodeunit = require('../nodeunit'), 12 | utils = require('../utils'), 13 | fs = require('fs'), 14 | path = require('path'), 15 | track = require('../track'), 16 | AssertionError = require('../assert').AssertionError; 17 | 18 | /** 19 | * Reporter info string 20 | */ 21 | 22 | exports.info = "Pretty minimal output"; 23 | 24 | /** 25 | * Run all tests within each module, reporting the results to the command-line. 26 | * 27 | * @param {Array} files 28 | * @api public 29 | */ 30 | 31 | exports.run = function (files, options, callback) { 32 | 33 | if (!options) { 34 | // load default options 35 | var content = fs.readFileSync( 36 | __dirname + '/../../bin/nodeunit.json', 'utf8' 37 | ); 38 | options = JSON.parse(content); 39 | } 40 | 41 | var red = function (str) { 42 | return options.error_prefix + str + options.error_suffix; 43 | }; 44 | var green = function (str) { 45 | return options.ok_prefix + str + options.ok_suffix; 46 | }; 47 | var magenta = function (str) { 48 | return options.assertion_prefix + str + options.assertion_suffix; 49 | }; 50 | var bold = function (str) { 51 | return options.bold_prefix + str + options.bold_suffix; 52 | }; 53 | 54 | var start = new Date().getTime(); 55 | 56 | var tracker = track.createTracker(function (tracker) { 57 | if (tracker.unfinished()) { 58 | console.log(''); 59 | console.log(bold(red( 60 | 'FAILURES: Undone tests (or their setups/teardowns): ' 61 | ))); 62 | var names = tracker.names(); 63 | for (var i = 0; i < names.length; i += 1) { 64 | console.log('- ' + names[i]); 65 | } 66 | console.log(''); 67 | console.log('To fix this, make sure all tests call test.done()'); 68 | process.reallyExit(tracker.unfinished()); 69 | } 70 | }); 71 | 72 | 73 | var opts = { 74 | testspec: options.testspec, 75 | testFullSpec: options.testFullSpec, 76 | moduleStart: function (name) { 77 | process.stdout.write(bold(name) + ': '); 78 | }, 79 | moduleDone: function (name, assertions) { 80 | console.log(''); 81 | if (assertions.failures()) { 82 | assertions.forEach(function (a) { 83 | if (a.failed()) { 84 | a = utils.betterErrors(a); 85 | if (a.error instanceof AssertionError && a.message) { 86 | console.log( 87 | 'Assertion in test ' + bold(a.testname) + ': ' + 88 | magenta(a.message) 89 | ); 90 | } 91 | console.log(a.error.stack + '\n'); 92 | } 93 | }); 94 | } 95 | 96 | }, 97 | testStart: function (name) { 98 | tracker.put(name); 99 | }, 100 | testDone: function (name, assertions) { 101 | tracker.remove(name); 102 | 103 | if (!assertions.failures()) { 104 | process.stdout.write('.'); 105 | } 106 | else { 107 | process.stdout.write(red('F')); 108 | assertions.forEach(function (assertion) { 109 | assertion.testname = name; 110 | }); 111 | } 112 | }, 113 | done: function (assertions) { 114 | var end = new Date().getTime(); 115 | var duration = end - start; 116 | if (assertions.failures()) { 117 | console.log( 118 | '\n' + bold(red('FAILURES: ')) + assertions.failures() + 119 | '/' + assertions.length + ' assertions failed (' + 120 | assertions.duration + 'ms)' 121 | ); 122 | } 123 | else { 124 | console.log( 125 | '\n' + bold(green('OK: ')) + assertions.length + 126 | ' assertions (' + assertions.duration + 'ms)' 127 | ); 128 | } 129 | 130 | if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined); 131 | } 132 | }; 133 | 134 | if (files && files.length) { 135 | var paths = files.map(function (p) { 136 | return path.resolve(p); 137 | }); 138 | nodeunit.runFiles(paths, opts); 139 | } else { 140 | nodeunit.runModules(files,opts); 141 | } 142 | }; 143 | -------------------------------------------------------------------------------- /bin/nodeunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var 4 | fs = require('fs'), 5 | path = require('path'); 6 | 7 | 8 | //require.paths.push(process.cwd()); 9 | var args = (process.ARGV || process.argv).slice(2); 10 | 11 | var files = []; 12 | 13 | var testrunner, 14 | config_file, 15 | config_param_found = false, 16 | output_param_found = false, 17 | reporter_file = 'default', 18 | reporter_param_found = false, 19 | testspec_param_found = false, 20 | testFullSpec_param_found = false; 21 | 22 | var usage = "Usage: nodeunit [options] testmodule1.js testfolder [...] \n" + 23 | "Options:\n\n" + 24 | " --config FILE the path to a JSON file with options\n" + 25 | " --reporter FILE optional path to a reporter file to customize the output\n" + 26 | " --list-reporters list available build-in reporters\n" + 27 | " -r recursively run tests in sub-directories\n" + 28 | " -t testName, specify a test to run\n" + 29 | " -f fullTestName, specify a specific test to run. fullTestName is built so: \"outerGroup - .. - innerGroup - testName\"\n" + 30 | " -h, --help display this help and exit\n" + 31 | " -v, --version output version information and exit"; 32 | 33 | 34 | 35 | // load default options 36 | var content = fs.readFileSync(__dirname + '/nodeunit.json', 'utf8'); 37 | var options = JSON.parse(content); 38 | 39 | // a very basic pseudo --options parser 40 | args.forEach(function (arg) { 41 | if (arg.slice(0, 9) === "--config=") { 42 | config_file = arg.slice(9); 43 | } else if (arg === '--config') { 44 | config_param_found = true; 45 | } else if (config_param_found) { 46 | config_file = arg; 47 | config_param_found = false; 48 | } else if (arg.slice(0, 9) === "--output=") { 49 | options.output = arg.slice(9); 50 | } else if (arg === '--output') { 51 | output_param_found = true; 52 | } else if (output_param_found) { 53 | options.output = arg; 54 | output_param_found = false; 55 | } else if (arg.slice(0, 11) === "--reporter=") { 56 | reporter_file = arg.slice(11); 57 | } else if (arg === '--reporter') { 58 | reporter_param_found = true; 59 | } else if (reporter_param_found) { 60 | reporter_file = arg; 61 | reporter_param_found = false; 62 | } else if (arg === '-r') { 63 | options.recursive = true; 64 | } else if (arg === '-t') { 65 | testspec_param_found = true; 66 | } else if (testspec_param_found) { 67 | options.testspec = arg; 68 | testspec_param_found = false; 69 | } else if (arg === '-f') { 70 | testFullSpec_param_found = true; 71 | } else if (testFullSpec_param_found) { 72 | options.testFullSpec= arg; 73 | testFullSpec_param_found = false; 74 | } else if (arg === '--list-reporters') { 75 | var reporters = fs.readdirSync(__dirname + '/../lib/reporters'); 76 | reporters = reporters.filter(function (reporter_file) { 77 | return (/\.js$/).test(reporter_file); 78 | }).map(function (reporter_file) { 79 | return reporter_file.replace(/\.js$/, ''); 80 | }).filter(function (reporter_file) { 81 | return reporter_file !== 'index'; 82 | }); 83 | console.log('Built-in reporters: '); 84 | reporters.forEach(function (reporter_file) { 85 | var reporter = require('../lib/reporters/' + reporter_file); 86 | console.log(' * ' + reporter_file + (reporter.info ? ': ' + reporter.info : '')); 87 | }); 88 | process.exit(0); 89 | } else if ((arg === '-v') || (arg === '--version')) { 90 | var content = fs.readFileSync(__dirname + '/../package.json', 'utf8'); 91 | var pkg = JSON.parse(content); 92 | console.log(pkg.version); 93 | process.exit(0); 94 | } else if ((arg === '-h') || (arg === '--help')) { 95 | console.log(usage); 96 | process.exit(0); 97 | } else { 98 | files.push(arg); 99 | } 100 | }); 101 | 102 | // defaults to `test` 103 | if (files.length === 0) { 104 | files = ['test']; 105 | } 106 | 107 | if (config_file) { 108 | content = fs.readFileSync(config_file, 'utf8'); 109 | var custom_options = JSON.parse(content); 110 | 111 | for (var option in custom_options) { 112 | if (typeof option === 'string') { 113 | options[option] = custom_options[option]; 114 | } 115 | } 116 | } 117 | 118 | var builtin_reporters = require(__dirname + '/../lib/reporters'); 119 | if (reporter_file in builtin_reporters) { 120 | testrunner = builtin_reporters[reporter_file]; 121 | } 122 | else { 123 | testrunner = require(reporter_file); 124 | } 125 | 126 | testrunner.run(files, options, function(err) { 127 | process.exit(err ? 1 : 0); 128 | }); 129 | -------------------------------------------------------------------------------- /lib/reporters/browser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | * 6 | * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! 7 | * You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build. 8 | * Only code on that line will be removed, its mostly to avoid requiring code 9 | * that is node specific 10 | */ 11 | 12 | 13 | /** 14 | * NOTE: this test runner is not listed in index.js because it cannot be 15 | * used with the command-line tool, only inside the browser. 16 | */ 17 | 18 | 19 | /** 20 | * Reporter info string 21 | */ 22 | 23 | exports.info = "Browser-based test reporter"; 24 | 25 | 26 | /** 27 | * Run all tests within each module, reporting the results 28 | * 29 | * @param {Array} files 30 | * @api public 31 | */ 32 | 33 | exports.run = function (modules, options, callback) { 34 | var start = new Date().getTime(), div, textareas, displayErrorsByDefault; 35 | options = options || {}; 36 | div = options.div || document.body; 37 | textareas = options.textareas; 38 | displayErrorsByDefault = options.displayErrorsByDefault; 39 | 40 | function setText(el, txt) { 41 | if ('innerText' in el) { 42 | el.innerText = txt; 43 | } 44 | else if ('textContent' in el){ 45 | el.textContent = txt; 46 | } 47 | } 48 | 49 | function getOrCreate(tag, id) { 50 | var el = document.getElementById(id); 51 | if (!el) { 52 | el = document.createElement(tag); 53 | el.id = id; 54 | div.appendChild(el); 55 | } 56 | return el; 57 | }; 58 | 59 | var header = getOrCreate('h1', 'nodeunit-header'); 60 | var banner = getOrCreate('h2', 'nodeunit-banner'); 61 | var userAgent = getOrCreate('h2', 'nodeunit-userAgent'); 62 | var tests = getOrCreate('ol', 'nodeunit-tests'); 63 | var result = getOrCreate('p', 'nodeunit-testresult'); 64 | 65 | setText(userAgent, navigator.userAgent); 66 | 67 | nodeunit.runModules(modules, { 68 | moduleStart: function (name) { 69 | /*var mheading = document.createElement('h2'); 70 | mheading.innerText = name; 71 | results.appendChild(mheading); 72 | module = document.createElement('ol'); 73 | results.appendChild(module);*/ 74 | }, 75 | testDone: function (name, assertions) { 76 | var test = document.createElement('li'); 77 | var strong = document.createElement('strong'); 78 | strong.innerHTML = name + ' (' + 79 | '' + assertions.failures() + ', ' + 80 | '' + assertions.passes() + ', ' + 81 | assertions.length + 82 | ')'; 83 | test.className = assertions.failures() ? 'fail': 'pass'; 84 | test.appendChild(strong); 85 | 86 | var aList = document.createElement('ol'); 87 | aList.style.display = displayErrorsByDefault ? 'block' : 'none'; 88 | (displayErrorsByDefault ? strong : test).onclick = function () { 89 | var d = aList.style.display; 90 | aList.style.display = (d == 'none') ? 'block': 'none'; 91 | }; 92 | for (var i=0; i' + (a.error.stack || a.error) + '' : 99 | '
' + (a.error.stack || a.error) + '
'); 100 | li.className = 'fail'; 101 | } 102 | else { 103 | li.innerHTML = a.message || a.method || 'no message'; 104 | li.className = 'pass'; 105 | } 106 | aList.appendChild(li); 107 | } 108 | test.appendChild(aList); 109 | tests.appendChild(test); 110 | }, 111 | done: function (assertions) { 112 | var end = new Date().getTime(); 113 | var duration = end - start; 114 | 115 | var failures = assertions.failures(); 116 | banner.className = failures ? 'fail': 'pass'; 117 | 118 | result.innerHTML = 'Tests completed in ' + duration + 119 | ' milliseconds.
' + 120 | assertions.passes() + ' assertions of ' + 121 | '' + assertions.length + ' passed, ' + 122 | assertions.failures() + ' failed.'; 123 | 124 | if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined); 125 | } 126 | }); 127 | }; 128 | -------------------------------------------------------------------------------- /lib/types.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | * 6 | * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! 7 | * You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build. 8 | * Only code on that line will be removed, it's mostly to avoid requiring code 9 | * that is node specific 10 | */ 11 | 12 | /** 13 | * Module dependencies 14 | */ 15 | 16 | var assert = require('./assert'), //@REMOVE_LINE_FOR_BROWSER 17 | async = require('../deps/async'); //@REMOVE_LINE_FOR_BROWSER 18 | 19 | 20 | /** 21 | * Creates assertion objects representing the result of an assert call. 22 | * Accepts an object or AssertionError as its argument. 23 | * 24 | * @param {object} obj 25 | * @api public 26 | */ 27 | 28 | exports.assertion = function (obj) { 29 | return { 30 | method: obj.method || '', 31 | message: obj.message || (obj.error && obj.error.message) || '', 32 | error: obj.error, 33 | passed: function () { 34 | return !this.error; 35 | }, 36 | failed: function () { 37 | return Boolean(this.error); 38 | } 39 | }; 40 | }; 41 | 42 | /** 43 | * Creates an assertion list object representing a group of assertions. 44 | * Accepts an array of assertion objects. 45 | * 46 | * @param {Array} arr 47 | * @param {Number} duration 48 | * @api public 49 | */ 50 | 51 | exports.assertionList = function (arr, duration) { 52 | var that = arr || []; 53 | that.failures = function () { 54 | var failures = 0; 55 | for (var i = 0; i < this.length; i += 1) { 56 | if (this[i].failed()) { 57 | failures += 1; 58 | } 59 | } 60 | return failures; 61 | }; 62 | that.passes = function () { 63 | return that.length - that.failures(); 64 | }; 65 | that.duration = duration || 0; 66 | return that; 67 | }; 68 | 69 | /** 70 | * Create a wrapper function for assert module methods. Executes a callback 71 | * after it's complete with an assertion object representing the result. 72 | * 73 | * @param {Function} callback 74 | * @api private 75 | */ 76 | 77 | var assertWrapper = function (callback) { 78 | return function (new_method, assert_method, arity) { 79 | return function () { 80 | var message = arguments[arity - 1]; 81 | var a = exports.assertion({method: new_method, message: message}); 82 | try { 83 | assert[assert_method].apply(null, arguments); 84 | } 85 | catch (e) { 86 | a.error = e; 87 | } 88 | callback(a); 89 | }; 90 | }; 91 | }; 92 | 93 | /** 94 | * Creates the 'test' object that gets passed to every test function. 95 | * Accepts the name of the test function as its first argument, followed by 96 | * the start time in ms, the options object and a callback function. 97 | * 98 | * @param {String} name 99 | * @param {Number} start 100 | * @param {Object} options 101 | * @param {Function} callback 102 | * @api public 103 | */ 104 | 105 | exports.test = function (name, start, options, callback) { 106 | var expecting; 107 | var a_list = []; 108 | 109 | var wrapAssert = assertWrapper(function (a) { 110 | a_list.push(a); 111 | if (options.log) { 112 | async.nextTick(function () { 113 | options.log(a); 114 | }); 115 | } 116 | }); 117 | 118 | var test = { 119 | done: function (err) { 120 | if (expecting !== undefined && expecting !== a_list.length) { 121 | var e = new Error( 122 | 'Expected ' + expecting + ' assertions, ' + 123 | a_list.length + ' ran' 124 | ); 125 | var a1 = exports.assertion({method: 'expect', error: e}); 126 | a_list.push(a1); 127 | if (options.log) { 128 | async.nextTick(function () { 129 | options.log(a1); 130 | }); 131 | } 132 | } 133 | if (err) { 134 | var a2 = exports.assertion({error: err}); 135 | a_list.push(a2); 136 | if (options.log) { 137 | async.nextTick(function () { 138 | options.log(a2); 139 | }); 140 | } 141 | } 142 | var end = new Date().getTime(); 143 | async.nextTick(function () { 144 | var assertion_list = exports.assertionList(a_list, end - start); 145 | options.testDone(name, assertion_list); 146 | callback(null, a_list); 147 | }); 148 | }, 149 | ok: wrapAssert('ok', 'ok', 2), 150 | same: wrapAssert('same', 'deepEqual', 3), 151 | equals: wrapAssert('equals', 'equal', 3), 152 | expect: function (num) { 153 | expecting = num; 154 | }, 155 | _assertion_list: a_list 156 | }; 157 | // add all functions from the assert module 158 | for (var k in assert) { 159 | if (assert.hasOwnProperty(k)) { 160 | test[k] = wrapAssert(k, k, assert[k].length); 161 | } 162 | } 163 | return test; 164 | }; 165 | 166 | /** 167 | * Ensures an options object has all callbacks, adding empty callback functions 168 | * if any are missing. 169 | * 170 | * @param {Object} opt 171 | * @return {Object} 172 | * @api public 173 | */ 174 | 175 | exports.options = function (opt) { 176 | var optionalCallback = function (name) { 177 | opt[name] = opt[name] || function () {}; 178 | }; 179 | 180 | optionalCallback('moduleStart'); 181 | optionalCallback('moduleDone'); 182 | optionalCallback('testStart'); 183 | optionalCallback('testReady'); 184 | optionalCallback('testDone'); 185 | //optionalCallback('log'); 186 | 187 | // 'done' callback is not optional. 188 | 189 | return opt; 190 | }; 191 | -------------------------------------------------------------------------------- /lib/reporters/junit.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies 9 | */ 10 | 11 | var nodeunit = require('../nodeunit'), 12 | utils = require('../utils'), 13 | fs = require('fs'), 14 | path = require('path'), 15 | async = require('../../deps/async'), 16 | AssertionError = require('../assert').AssertionError, 17 | child_process = require('child_process'), 18 | ejs = require('ejs'); 19 | 20 | 21 | /** 22 | * Reporter info string 23 | */ 24 | 25 | exports.info = "jUnit XML test reports"; 26 | 27 | 28 | /** 29 | * Ensures a directory exists using mkdir -p. 30 | * 31 | * @param {String} path 32 | * @param {Function} callback 33 | * @api private 34 | */ 35 | 36 | var ensureDir = function (path, callback) { 37 | var mkdir = child_process.spawn('mkdir', ['-p', path]); 38 | mkdir.on('error', function (err) { 39 | callback(err); 40 | callback = function(){}; 41 | }); 42 | mkdir.on('exit', function (code) { 43 | if (code === 0) callback(); 44 | else callback(new Error('mkdir exited with code: ' + code)); 45 | }); 46 | }; 47 | 48 | 49 | /** 50 | * Returns absolute version of a path. Relative paths are interpreted 51 | * relative to process.cwd() or the cwd parameter. Paths that are already 52 | * absolute are returned unaltered. 53 | * 54 | * @param {String} p 55 | * @param {String} cwd 56 | * @return {String} 57 | * @api public 58 | */ 59 | 60 | var abspath = function (p, /*optional*/cwd) { 61 | if (p[0] === '/') return p; 62 | cwd = cwd || process.cwd(); 63 | return path.normalize(path.resolve(p)); 64 | }; 65 | 66 | 67 | /** 68 | * Run all tests within each module, reporting the results to the command-line, 69 | * then writes out junit-compatible xml documents. 70 | * 71 | * @param {Array} files 72 | * @api public 73 | */ 74 | 75 | exports.run = function (files, opts, callback) { 76 | if (!opts.output) { 77 | console.error( 78 | 'Error: No output directory defined.\n' + 79 | '\tEither add an "output" property to your nodeunit.json config ' + 80 | 'file, or\n\tuse the --output command line option.' 81 | ); 82 | return; 83 | } 84 | opts.output = abspath(opts.output); 85 | var error = function (str) { 86 | return opts.error_prefix + str + opts.error_suffix; 87 | }; 88 | var ok = function (str) { 89 | return opts.ok_prefix + str + opts.ok_suffix; 90 | }; 91 | var bold = function (str) { 92 | return opts.bold_prefix + str + opts.bold_suffix; 93 | }; 94 | 95 | var start = new Date().getTime(); 96 | var paths = files.map(function (p) { 97 | return path.resolve(p); 98 | }); 99 | 100 | var modules = {}; 101 | var curModule; 102 | 103 | nodeunit.runFiles(paths, { 104 | testspec: opts.testspec, 105 | testFullSpec: opts.testFullSpec, 106 | moduleStart: function (name) { 107 | curModule = { 108 | errorCount: 0, 109 | failureCount: 0, 110 | tests: 0, 111 | testcases: {}, 112 | name: name, 113 | start: new Date().getTime() 114 | }; 115 | modules[name] = curModule; 116 | }, 117 | testStart: function(name) { 118 | curModule.testcases[name] = {name: name, start : new Date().getTime()}; 119 | }, 120 | moduleDone: function(name) { 121 | curModule.end = new Date().getTime(); 122 | }, 123 | testDone: function (name, assertions) { 124 | var testcase = curModule.testcases[name]; 125 | testcase.end = new Date().getTime(); 126 | for (var i=0; i name_slice(['TC1', 'TC1.1', 'mytest'], 1); 105 | * "TC1,TC1.1" 106 | */ 107 | var name_slice = function (name_arr, end_index) { 108 | return name_arr.slice(0, end_index + 1).join(","); 109 | }; 110 | 111 | var indent = (function () { 112 | var txt = ''; 113 | var i; 114 | for (i = 0; i < spaces_per_indent; i++) { 115 | txt += ' '; 116 | } 117 | return txt; 118 | }()); 119 | 120 | // Indent once for each indent_level 121 | var add_indent = function (txt, indent_level) { 122 | var k; 123 | for (k = 0; k < indent_level; k++) { 124 | txt += indent; 125 | } 126 | return txt; 127 | }; 128 | 129 | // If it's not the last element of the name_arr, it's a testCase. 130 | var is_testCase = function (name_arr, index) { 131 | return index === name_arr.length - 1 ? false : true; 132 | }; 133 | 134 | var testCase_line = function (txt) { 135 | return txt + "\n"; 136 | }; 137 | 138 | /** 139 | * Prints (console.log) the nested test status line(s). 140 | * 141 | * @param {Array} name_arr - Array of name elements. 142 | * @param {String} status - either 'pass' or 'fail'. 143 | * @example 144 | * > print_status(['TC1', 'TC1.1', 'mytest'], 'pass'); 145 | * TC1 146 | * TC1.1 147 | * mytest (pass) 148 | */ 149 | var print_status = function (name_arr, status) { 150 | var txt = ''; 151 | var _name_slice, part, i; 152 | for (i = 0; i < name_arr.length; i++) { 153 | _name_slice = name_slice(name_arr, i); 154 | part = name_arr[i]; 155 | if (!tracker.already_printed[_name_slice]) { 156 | txt = add_indent(txt, i); 157 | if (is_testCase(name_arr, i)) { 158 | txt += testCase_line(part); 159 | } else { 160 | txt += status_text(part, status); 161 | } 162 | tracker.already_printed[_name_slice] = true; 163 | } 164 | } 165 | console.log(txt); 166 | }; 167 | 168 | nodeunit.runFiles(paths, { 169 | testspec: options.testspec, 170 | testFullSpec: options.testFullSpec, 171 | moduleStart: function (name) { 172 | console.log('\n' + bold(name)); 173 | }, 174 | testDone: function (name, assertions) { 175 | tracker.remove(name); 176 | 177 | if (!assertions.failures()) { 178 | print_status(name, 'pass'); 179 | } else { 180 | print_status(name, 'fail'); 181 | assertions.forEach(function (a) { 182 | if (a.failed()) { 183 | a = utils.betterErrors(a); 184 | if (a.error instanceof AssertionError && a.message) { 185 | console.log( 186 | 'Assertion Message: ' + 187 | assertion_message(a.message) 188 | ); 189 | } 190 | console.log(a.error.stack + '\n'); 191 | } 192 | }); 193 | } 194 | }, 195 | done: function (assertions, end) { 196 | end = end || new Date().getTime(); 197 | var duration = end - start; 198 | if (assertions.failures()) { 199 | console.log( 200 | '\n' + bold(error('FAILURES: ')) + assertions.failures() + 201 | '/' + assertions.length + ' assertions failed (' + 202 | assertions.duration + 'ms)' 203 | ); 204 | } else { 205 | console.log( 206 | '\n' + bold(ok('OK: ')) + assertions.length + 207 | ' assertions (' + assertions.duration + 'ms)' 208 | ); 209 | } 210 | 211 | if (callback) callback(assertions.failures() ? new Error('We have got test failures.') : undefined); 212 | }, 213 | testStart: function (name) { 214 | tracker.put(name); 215 | } 216 | }); 217 | }; 218 | -------------------------------------------------------------------------------- /test/test-base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This module is not a plain nodeunit test suite, but instead uses the 3 | * assert module to ensure a basic level of functionality is present, 4 | * allowing the rest of the tests to be written using nodeunit itself. 5 | * 6 | * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! 7 | * You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build. 8 | * Only code on that line will be removed, its mostly to avoid requiring code 9 | * that is node specific 10 | */ 11 | 12 | var assert = require('assert'), // @REMOVE_LINE_FOR_BROWSER 13 | async = require('../deps/async'), // @REMOVE_LINE_FOR_BROWSER 14 | nodeunit = require('../lib/nodeunit'); // @REMOVE_LINE_FOR_BROWSER 15 | 16 | 17 | // NOT A TEST - util function to make testing faster. 18 | // retries the assertion until it passes or the timeout is reached, 19 | // at which point it throws the assertion error 20 | var waitFor = function (fn, timeout, callback, start) { 21 | start = start || new Date().getTime(); 22 | callback = callback || function () {}; 23 | try { 24 | fn(); 25 | callback(); 26 | } 27 | catch (e) { 28 | if (e instanceof assert.AssertionError) { 29 | var now = new Date().getTime(); 30 | if (now - start >= timeout) { 31 | throw e; 32 | } 33 | else { 34 | async.nextTick(function () { 35 | waitFor(fn, timeout, callback, start); 36 | }); 37 | } 38 | } 39 | else { 40 | throw e; 41 | } 42 | } 43 | }; 44 | 45 | 46 | // TESTS: 47 | 48 | // Are exported tests actually run? - store completed tests in this variable 49 | // for checking later 50 | var tests_called = {}; 51 | 52 | // most basic test that should run, the tests_called object is tested 53 | // at the end of this module to ensure the tests were actually run by nodeunit 54 | exports.testCalled = function (test) { 55 | tests_called.testCalled = true; 56 | test.done(); 57 | }; 58 | 59 | // generates test functions for nodeunit assertions 60 | var makeTest = function (method, args_pass, args_fail) { 61 | return function (test) { 62 | var test1_called = false; 63 | var test2_called = false; 64 | 65 | // test pass 66 | nodeunit.runTest( 67 | 'testname', 68 | function (test) { 69 | test[method].apply(test, args_pass); 70 | test.done(); 71 | }, 72 | {testDone: function (name, assertions) { 73 | assert.equal(assertions.length, 1); 74 | assert.equal(assertions.failures(), 0); 75 | }}, 76 | function () { 77 | test1_called = true; 78 | } 79 | ); 80 | 81 | // test failure 82 | nodeunit.runTest( 83 | 'testname', 84 | function (test) { 85 | test[method].apply(test, args_fail); 86 | test.done(); 87 | }, 88 | {testDone: function (name, assertions) { 89 | assert.equal(assertions.length, 1); 90 | assert.equal(assertions.failures(), 1); 91 | }}, 92 | function () { 93 | test2_called = true; 94 | } 95 | ); 96 | 97 | // ensure tests were run 98 | waitFor(function () { 99 | assert.ok(test1_called); 100 | assert.ok(test2_called); 101 | tests_called[method] = true; 102 | }, 500, test.done); 103 | }; 104 | }; 105 | 106 | // ensure basic assertions are working: 107 | exports.testOk = makeTest('ok', [true], [false]); 108 | exports.testEquals = makeTest('equals', [1, 1], [1, 2]); 109 | exports.testSame = makeTest('same', 110 | [{test: 'test'}, {test: 'test'}], 111 | [{test: 'test'}, {monkey: 'penguin'}] 112 | ); 113 | 114 | // from the assert module: 115 | exports.testEqual = makeTest('equal', [1, 1], [1, 2]); 116 | exports.testNotEqual = makeTest('notEqual', [1, 2], [1, 1]); 117 | exports.testDeepEqual = makeTest('deepEqual', 118 | [{one: 1, two: 2}, {one: 1, two: {valueOf:function() {return 2;}}}], 119 | [{one: 1, two: 2}, {two: 2}] 120 | ); 121 | exports.testNotDeepEqual = makeTest('notDeepEqual', 122 | [{one: 1}, {two: 2}], [{one: 1}, {one: 1}] 123 | ); 124 | exports.testStrictEqual = makeTest('strictEqual', [1, 1], [1, true]); 125 | exports.testNotStrictEqual = makeTest('notStrictEqual', [true, 1], [1, 1]); 126 | exports.testThrows = makeTest('throws', 127 | [function () { 128 | throw new Error('test'); 129 | }], 130 | [function () { 131 | return; 132 | }] 133 | ); 134 | exports.testThrowsWithReGex = makeTest('throws', 135 | [function () { 136 | throw new Error('test'); 137 | }, /test/], 138 | [function () { 139 | throw new Error('test'); 140 | }, /fail/] 141 | ); 142 | exports.testThrowsWithErrorValidation = makeTest('throws', 143 | [function () { 144 | throw new Error('test'); 145 | }, function(err) { 146 | return true; 147 | }], 148 | [function () { 149 | throw new Error('test'); 150 | }, function(err) { 151 | return false; 152 | }] 153 | ); 154 | exports.testDoesNotThrows = makeTest('doesNotThrow', 155 | [function () { 156 | return; 157 | }], 158 | [function () { 159 | throw new Error('test'); 160 | }] 161 | ); 162 | exports.testIfError = makeTest('ifError', [false], [new Error('test')]); 163 | 164 | 165 | exports.testExpect = function (test) { 166 | var test1_called = false, 167 | test2_called = false, 168 | test3_called = false; 169 | 170 | // correct number of tests run 171 | nodeunit.runTest( 172 | 'testname', 173 | function (test) { 174 | test.expect(2); 175 | test.ok(true); 176 | test.ok(true); 177 | test.done(); 178 | }, 179 | {testDone: function (name, assertions) { 180 | test.equals(assertions.length, 2); 181 | test.equals(assertions.failures(), 0); 182 | }}, 183 | function () { 184 | test1_called = true; 185 | } 186 | ); 187 | 188 | // no tests run 189 | nodeunit.runTest( 190 | 'testname', 191 | function (test) { 192 | test.expect(2); 193 | test.done(); 194 | }, 195 | {testDone: function (name, assertions) { 196 | test.equals(assertions.length, 1); 197 | test.equals(assertions.failures(), 1); 198 | }}, 199 | function () { 200 | test2_called = true; 201 | } 202 | ); 203 | 204 | // incorrect number of tests run 205 | nodeunit.runTest( 206 | 'testname', 207 | function (test) { 208 | test.expect(2); 209 | test.ok(true); 210 | test.ok(true); 211 | test.ok(true); 212 | test.done(); 213 | }, 214 | {testDone: function (name, assertions) { 215 | test.equals(assertions.length, 4); 216 | test.equals(assertions.failures(), 1); 217 | }}, 218 | function () { 219 | test3_called = true; 220 | } 221 | ); 222 | 223 | // ensure callbacks fired 224 | waitFor(function () { 225 | assert.ok(test1_called); 226 | assert.ok(test2_called); 227 | assert.ok(test3_called); 228 | tests_called.expect = true; 229 | }, 1000, test.done); 230 | }; 231 | 232 | 233 | // tests are async, so wait for them to be called 234 | waitFor(function () { 235 | assert.ok(tests_called.testCalled); 236 | assert.ok(tests_called.ok); 237 | assert.ok(tests_called.equals); 238 | assert.ok(tests_called.same); 239 | assert.ok(tests_called.expect); 240 | }, 10000); 241 | -------------------------------------------------------------------------------- /test/test-testcase.js: -------------------------------------------------------------------------------- 1 | /* THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! 2 | * You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build. 3 | * Only code on that line will be removed, its mostly to avoid requiring code 4 | * that is node specific 5 | */ 6 | 7 | var nodeunit = require('../lib/nodeunit'); // @REMOVE_LINE_FOR_BROWSER 8 | 9 | exports.testTestCase = function (test) { 10 | test.expect(7); 11 | var call_order = []; 12 | var s = { 13 | setUp: function (callback) { 14 | call_order.push('setUp'); 15 | test.equals(this.one, undefined, 'in setUp, this.one not set'); 16 | this.one = 1; 17 | callback(); 18 | }, 19 | tearDown: function (callback) { 20 | call_order.push('tearDown'); 21 | test.ok(true, 'tearDown called'); 22 | callback(); 23 | }, 24 | test1: function (t) { 25 | call_order.push('test1'); 26 | test.equals(this.one, 1, 'in test1, this.one is 1'); 27 | this.one = 2; 28 | t.done(); 29 | }, 30 | test2: function (t) { 31 | call_order.push('test2'); 32 | test.equals(this.one, 1, 'in test2, this.one is still 1'); 33 | t.done(); 34 | } 35 | }; 36 | nodeunit.runSuite(null, s, {}, function () { 37 | test.same(call_order, [ 38 | 'setUp', 'test1', 'tearDown', 39 | 'setUp', 'test2', 'tearDown' 40 | ]); 41 | test.done(); 42 | }); 43 | }; 44 | 45 | exports.tearDownAfterError = function (test) { 46 | test.expect(1); 47 | var s = { 48 | tearDown: function (callback) { 49 | test.ok(true, 'tearDown called'); 50 | callback(); 51 | }, 52 | test: function (t) { 53 | throw new Error('some error'); 54 | } 55 | }; 56 | nodeunit.runSuite(null, s, {}, function () { 57 | test.done(); 58 | }); 59 | }; 60 | 61 | exports.catchSetUpError = function (test) { 62 | test.expect(2); 63 | var test_error = new Error('test error'); 64 | var s = { 65 | setUp: function (callback) { 66 | throw test_error; 67 | }, 68 | test: function (t) { 69 | test.ok(false, 'test function should not be called'); 70 | t.done(); 71 | } 72 | }; 73 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 74 | test.equal(assertions.length, 1); 75 | test.equal(assertions[0].error, test_error); 76 | test.done(); 77 | }); 78 | }; 79 | 80 | exports.setUpErrorCallback = function (test) { 81 | test.expect(2); 82 | var test_error = new Error('test error'); 83 | var s = { 84 | setUp: function (callback) { 85 | callback(test_error); 86 | }, 87 | test: function (t) { 88 | test.ok(false, 'test function should not be called'); 89 | t.done(); 90 | } 91 | }; 92 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 93 | test.equal(assertions.length, 1); 94 | test.equal(assertions[0].error, test_error); 95 | test.done(); 96 | }); 97 | }; 98 | 99 | exports.catchTearDownError = function (test) { 100 | test.expect(2); 101 | var test_error = new Error('test error'); 102 | var s = { 103 | tearDown: function (callback) { 104 | throw test_error; 105 | }, 106 | test: function (t) { 107 | t.done(); 108 | } 109 | }; 110 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 111 | test.equal(assertions.length, 1); 112 | test.equal(assertions[0].error, test_error); 113 | test.done(); 114 | }); 115 | }; 116 | 117 | exports.tearDownErrorCallback = function (test) { 118 | test.expect(2); 119 | var test_error = new Error('test error'); 120 | var s = { 121 | tearDown: function (callback) { 122 | callback(test_error); 123 | }, 124 | test: function (t) { 125 | t.done(); 126 | } 127 | }; 128 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 129 | test.equal(assertions.length, 1); 130 | test.equal(assertions[0].error, test_error); 131 | test.done(); 132 | }); 133 | }; 134 | 135 | exports.testErrorAndtearDownError = function (test) { 136 | test.expect(3); 137 | var error1 = new Error('test error one'); 138 | var error2 = new Error('test error two'); 139 | var s = { 140 | tearDown: function (callback) { 141 | callback(error2); 142 | }, 143 | test: function (t) { 144 | t.done(error1); 145 | } 146 | }; 147 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 148 | test.equal(assertions.length, 2); 149 | test.equal(assertions[0].error, error1); 150 | test.equal(assertions[1].error, error2); 151 | test.done(); 152 | }); 153 | }; 154 | 155 | exports.testCaseGroups = function (test) { 156 | var call_order = []; 157 | var s = { 158 | setUp: function (callback) { 159 | call_order.push('setUp'); 160 | callback(); 161 | }, 162 | tearDown: function (callback) { 163 | call_order.push('tearDown'); 164 | callback(); 165 | }, 166 | test1: function (test) { 167 | call_order.push('test1'); 168 | test.done(); 169 | }, 170 | group1: { 171 | test2: function (test) { 172 | call_order.push('group1.test2'); 173 | test.done(); 174 | } 175 | } 176 | }; 177 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 178 | test.same(call_order, [ 179 | 'setUp', 180 | 'test1', 181 | 'tearDown', 182 | 'setUp', 183 | 'group1.test2', 184 | 'tearDown' 185 | ]); 186 | test.done(); 187 | }); 188 | }; 189 | 190 | exports.nestedTestCases = function (test) { 191 | var call_order = []; 192 | var s = { 193 | setUp: function (callback) { 194 | call_order.push('setUp'); 195 | callback(); 196 | }, 197 | tearDown: function (callback) { 198 | call_order.push('tearDown'); 199 | callback(); 200 | }, 201 | test1: function (test) { 202 | call_order.push('test1'); 203 | test.done(); 204 | }, 205 | group1: { 206 | setUp: function (callback) { 207 | call_order.push('group1.setUp'); 208 | callback(); 209 | }, 210 | tearDown: function (callback) { 211 | call_order.push('group1.tearDown'); 212 | callback(); 213 | }, 214 | test2: function (test) { 215 | call_order.push('group1.test2'); 216 | test.done(); 217 | } 218 | } 219 | }; 220 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 221 | test.same(call_order, [ 222 | 'setUp', 223 | 'test1', 224 | 'tearDown', 225 | 'setUp', 226 | 'group1.setUp', 227 | 'group1.test2', 228 | 'group1.tearDown', 229 | 'tearDown' 230 | ]); 231 | test.done(); 232 | }); 233 | }; 234 | 235 | exports.deepNestedTestCases = function (test) { 236 | var val = 'foo'; 237 | var s = { 238 | setUp: function (callback) { 239 | val = 'bar'; 240 | callback(); 241 | }, 242 | group1: { 243 | test: { 244 | test2: function (test) { 245 | test.equal(val, 'bar'); 246 | test.done(); 247 | } 248 | } 249 | } 250 | }; 251 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 252 | test.ok(!assertions[0].failed()); 253 | test.equal(assertions.length, 1); 254 | test.done(); 255 | }); 256 | }; 257 | -------------------------------------------------------------------------------- /test/test-runfiles.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'), 2 | fs = require('fs'), 3 | path = require('path'), 4 | nodeunit = require('../lib/nodeunit'); 5 | 6 | 7 | var setup = function (fn) { 8 | return function (test) { 9 | process.chdir(__dirname); 10 | var env = { 11 | mock_module1: require(__dirname + '/fixtures/mock_module1'), 12 | mock_module2: require(__dirname + '/fixtures/mock_module2'), 13 | mock_module3: require(__dirname + '/fixtures/dir/mock_module3'), 14 | mock_module4: require(__dirname + '/fixtures/dir/mock_module4') 15 | }; 16 | fn.call(env, test); 17 | }; 18 | }; 19 | 20 | 21 | exports.testRunFiles = setup(function (test) { 22 | test.expect(33); 23 | var runModule_copy = nodeunit.runModule; 24 | 25 | var runModule_calls = []; 26 | var modules = []; 27 | 28 | var opts = { 29 | moduleStart: function () { 30 | return 'moduleStart'; 31 | }, 32 | testDone: function () { 33 | return 'testDone'; 34 | }, 35 | testReady: function () { 36 | return 'testReady'; 37 | }, 38 | testStart: function () { 39 | return 'testStart'; 40 | }, 41 | log: function () { 42 | return 'log'; 43 | }, 44 | done: function (assertions) { 45 | test.equals(assertions.failures(), 0, 'failures'); 46 | test.equals(assertions.length, 5, 'length'); 47 | test.ok(typeof assertions.duration === "number"); 48 | 49 | var called_with = function (name) { 50 | return runModule_calls.some(function (m) { 51 | return m.name === name; 52 | }); 53 | }; 54 | test.ok(called_with('mock_module1'), 'mock_module1 ran'); 55 | test.ok(called_with('mock_module2'), 'mock_module2 ran'); 56 | test.ok(called_with('mock_module3'), 'mock_module3 ran'); 57 | test.ok(called_with('mock_module4'), 'mock_module4 ran'); 58 | test.equals(runModule_calls.length, 5); 59 | 60 | nodeunit.runModule = runModule_copy; 61 | test.done(); 62 | } 63 | }; 64 | 65 | nodeunit.runModule = function (name, mod, options, callback) { 66 | test.equals(options.testDone, opts.testDone); 67 | test.equals(options.testReady, opts.testReady); 68 | test.equals(options.testStart, opts.testStart); 69 | test.equals(options.log, opts.log); 70 | test.ok(typeof name === "string"); 71 | runModule_calls.push(mod); 72 | var m = [{failed: function () { 73 | return false; 74 | }}]; 75 | modules.push(m); 76 | callback(null, m); 77 | }; 78 | 79 | nodeunit.runFiles( 80 | [__dirname + '/fixtures/mock_module1.js', 81 | __dirname + '/fixtures/mock_module2.js', 82 | __dirname + '/fixtures/dir'], 83 | opts 84 | ); 85 | }); 86 | 87 | exports.testRunFilesEmpty = function (test) { 88 | test.expect(3); 89 | nodeunit.runFiles([], { 90 | moduleStart: function () { 91 | test.ok(false, 'should not be called'); 92 | }, 93 | testDone: function () { 94 | test.ok(false, 'should not be called'); 95 | }, 96 | testReady: function () { 97 | test.ok(false, 'should not be called'); 98 | }, 99 | testStart: function () { 100 | test.ok(false, 'should not be called'); 101 | }, 102 | log: function () { 103 | test.ok(false, 'should not be called'); 104 | }, 105 | done: function (assertions) { 106 | test.equals(assertions.failures(), 0, 'failures'); 107 | test.equals(assertions.length, 0, 'length'); 108 | test.ok(typeof assertions.duration === "number"); 109 | test.done(); 110 | } 111 | }); 112 | }; 113 | 114 | 115 | exports.testEmptyDir = function (test) { 116 | var dir2 = __dirname + '/fixtures/dir2'; 117 | 118 | // git doesn't like empty directories, so we have to create one 119 | fs.access(dir2, function (err) { 120 | if (err) { 121 | fs.mkdirSync(dir2, 0777); 122 | } 123 | 124 | // runFiles on empty directory: 125 | nodeunit.runFiles([dir2], { 126 | moduleStart: function () { 127 | test.ok(false, 'should not be called'); 128 | }, 129 | testDone: function () { 130 | test.ok(false, 'should not be called'); 131 | }, 132 | testReady: function () { 133 | test.ok(false, 'should not be called'); 134 | }, 135 | testStart: function () { 136 | test.ok(false, 'should not be called'); 137 | }, 138 | log: function () { 139 | test.ok(false, 'should not be called'); 140 | }, 141 | done: function (assertions) { 142 | test.equals(assertions.failures(), 0, 'failures'); 143 | test.equals(assertions.length, 0, 'length'); 144 | test.ok(typeof assertions.duration === "number"); 145 | test.done(); 146 | } 147 | }); 148 | }); 149 | }; 150 | 151 | 152 | var CoffeeScript; 153 | try { 154 | CoffeeScript = require('coffee-script'); 155 | if (CoffeeScript.register != null) { 156 | CoffeeScript.register(); 157 | } 158 | } catch (e) { 159 | } 160 | 161 | if (CoffeeScript) { 162 | exports.testCoffeeScript = function (test) { 163 | process.chdir(__dirname); 164 | var env = { 165 | mock_coffee_module: require(__dirname + 166 | '/fixtures/coffee/mock_coffee_module') 167 | }; 168 | 169 | test.expect(10); 170 | var runModule_copy = nodeunit.runModule; 171 | 172 | var runModule_calls = []; 173 | var modules = []; 174 | 175 | var opts = { 176 | moduleStart: function () { 177 | return 'moduleStart'; 178 | }, 179 | testDone: function () { 180 | return 'testDone'; 181 | }, 182 | testReady: function () { 183 | return 'testReady'; 184 | }, 185 | testStart: function () { 186 | return 'testStart'; 187 | }, 188 | log: function () { 189 | return 'log'; 190 | }, 191 | done: function (assertions) { 192 | test.equals(assertions.failures(), 0, 'failures'); 193 | test.equals(assertions.length, 1, 'length'); 194 | test.ok(typeof assertions.duration === "number"); 195 | 196 | var called_with = function (name) { 197 | return runModule_calls.some(function (m) { 198 | return m.name === name; 199 | }); 200 | }; 201 | test.ok( 202 | called_with('mock_coffee_15'), 203 | 'mock_coffee_module ran' 204 | ); 205 | test.equals(runModule_calls.length, 1); 206 | 207 | nodeunit.runModule = runModule_copy; 208 | test.done(); 209 | } 210 | }; 211 | 212 | nodeunit.runModule = function (name, mod, options, callback) { 213 | test.equals(options.testDone, opts.testDone); 214 | test.equals(options.testReady, opts.testReady); 215 | test.equals(options.testStart, opts.testStart); 216 | test.equals(options.log, opts.log); 217 | test.ok(typeof name === "string"); 218 | runModule_calls.push(mod); 219 | var m = [{failed: function () { 220 | return false; 221 | }}]; 222 | modules.push(m); 223 | callback(null, m); 224 | }; 225 | 226 | nodeunit.runFiles( 227 | [__dirname + '/fixtures/coffee/mock_coffee_module.coffee'], 228 | opts 229 | ); 230 | }; 231 | } 232 | -------------------------------------------------------------------------------- /test/test-testcase-legacy.js: -------------------------------------------------------------------------------- 1 | /* THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! 2 | * You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build. 3 | * Only code on that line will be removed, its mostly to avoid requiring code 4 | * that is node specific 5 | */ 6 | 7 | var nodeunit = require('../lib/nodeunit'); // @REMOVE_LINE_FOR_BROWSER 8 | var testCase = nodeunit.testCase; 9 | 10 | exports.testTestCase = function (test) { 11 | test.expect(7); 12 | var call_order = []; 13 | var s = testCase({ 14 | setUp: function (callback) { 15 | call_order.push('setUp'); 16 | test.equals(this.one, undefined); 17 | this.one = 1; 18 | callback(); 19 | }, 20 | tearDown: function (callback) { 21 | call_order.push('tearDown'); 22 | test.ok(true, 'tearDown called'); 23 | callback(); 24 | }, 25 | test1: function (t) { 26 | call_order.push('test1'); 27 | test.equals(this.one, 1); 28 | this.one = 2; 29 | t.done(); 30 | }, 31 | test2: function (t) { 32 | call_order.push('test2'); 33 | test.equals(this.one, 1); 34 | t.done(); 35 | } 36 | }); 37 | nodeunit.runSuite(null, s, {}, function () { 38 | test.same(call_order, [ 39 | 'setUp', 'test1', 'tearDown', 40 | 'setUp', 'test2', 'tearDown' 41 | ]); 42 | test.done(); 43 | }); 44 | }; 45 | 46 | exports.tearDownAfterError = function (test) { 47 | test.expect(1); 48 | var s = testCase({ 49 | tearDown: function (callback) { 50 | test.ok(true, 'tearDown called'); 51 | callback(); 52 | }, 53 | test: function (t) { 54 | throw new Error('some error'); 55 | } 56 | }); 57 | nodeunit.runSuite(null, s, {}, function () { 58 | test.done(); 59 | }); 60 | }; 61 | 62 | exports.catchSetUpError = function (test) { 63 | test.expect(2); 64 | var test_error = new Error('test error'); 65 | var s = testCase({ 66 | setUp: function (callback) { 67 | throw test_error; 68 | }, 69 | test: function (t) { 70 | test.ok(false, 'test function should not be called'); 71 | t.done(); 72 | } 73 | }); 74 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 75 | test.equal(assertions.length, 1); 76 | test.equal(assertions[0].error, test_error); 77 | test.done(); 78 | }); 79 | }; 80 | 81 | exports.setUpErrorCallback = function (test) { 82 | test.expect(2); 83 | var test_error = new Error('test error'); 84 | var s = testCase({ 85 | setUp: function (callback) { 86 | callback(test_error); 87 | }, 88 | test: function (t) { 89 | test.ok(false, 'test function should not be called'); 90 | t.done(); 91 | } 92 | }); 93 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 94 | test.equal(assertions.length, 1); 95 | test.equal(assertions[0].error, test_error); 96 | test.done(); 97 | }); 98 | }; 99 | 100 | exports.catchTearDownError = function (test) { 101 | test.expect(2); 102 | var test_error = new Error('test error'); 103 | var s = testCase({ 104 | tearDown: function (callback) { 105 | throw test_error; 106 | }, 107 | test: function (t) { 108 | t.done(); 109 | } 110 | }); 111 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 112 | test.equal(assertions.length, 1); 113 | test.equal(assertions[0].error, test_error); 114 | test.done(); 115 | }); 116 | }; 117 | 118 | exports.tearDownErrorCallback = function (test) { 119 | test.expect(2); 120 | var test_error = new Error('test error'); 121 | var s = testCase({ 122 | tearDown: function (callback) { 123 | callback(test_error); 124 | }, 125 | test: function (t) { 126 | t.done(); 127 | } 128 | }); 129 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 130 | test.equal(assertions.length, 1); 131 | test.equal(assertions[0].error, test_error); 132 | test.done(); 133 | }); 134 | }; 135 | 136 | exports.testErrorAndtearDownError = function (test) { 137 | test.expect(3); 138 | var error1 = new Error('test error one'); 139 | var error2 = new Error('test error two'); 140 | var s = testCase({ 141 | tearDown: function (callback) { 142 | callback(error2); 143 | }, 144 | test: function (t) { 145 | t.done(error1); 146 | } 147 | }); 148 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 149 | test.equal(assertions.length, 2); 150 | test.equal(assertions[0].error, error1); 151 | test.equal(assertions[1].error, error2); 152 | test.done(); 153 | }); 154 | }; 155 | 156 | exports.testCaseGroups = function (test) { 157 | var call_order = []; 158 | var s = testCase({ 159 | setUp: function (callback) { 160 | call_order.push('setUp'); 161 | callback(); 162 | }, 163 | tearDown: function (callback) { 164 | call_order.push('tearDown'); 165 | callback(); 166 | }, 167 | test1: function (test) { 168 | call_order.push('test1'); 169 | test.done(); 170 | }, 171 | group1: { 172 | test2: function (test) { 173 | call_order.push('group1.test2'); 174 | test.done(); 175 | } 176 | } 177 | }); 178 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 179 | test.same(call_order, [ 180 | 'setUp', 181 | 'test1', 182 | 'tearDown', 183 | 'setUp', 184 | 'group1.test2', 185 | 'tearDown' 186 | ]); 187 | test.done(); 188 | }); 189 | }; 190 | 191 | exports.nestedTestCases = function (test) { 192 | var call_order = []; 193 | var s = testCase({ 194 | setUp: function (callback) { 195 | call_order.push('setUp'); 196 | callback(); 197 | }, 198 | tearDown: function (callback) { 199 | call_order.push('tearDown'); 200 | callback(); 201 | }, 202 | test1: function (test) { 203 | call_order.push('test1'); 204 | test.done(); 205 | }, 206 | group1: testCase({ 207 | setUp: function (callback) { 208 | call_order.push('group1.setUp'); 209 | callback(); 210 | }, 211 | tearDown: function (callback) { 212 | call_order.push('group1.tearDown'); 213 | callback(); 214 | }, 215 | test2: function (test) { 216 | call_order.push('group1.test2'); 217 | test.done(); 218 | } 219 | }) 220 | }); 221 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 222 | test.same(call_order, [ 223 | 'setUp', 224 | 'test1', 225 | 'tearDown', 226 | 'setUp', 227 | 'group1.setUp', 228 | 'group1.test2', 229 | 'group1.tearDown', 230 | 'tearDown' 231 | ]); 232 | test.done(); 233 | }); 234 | }; 235 | 236 | exports.deepNestedTestCases = function (test) { 237 | var val = 'foo'; 238 | var s = testCase({ 239 | setUp: function (callback) { 240 | val = 'bar'; 241 | callback(); 242 | }, 243 | group1: testCase({ 244 | test: testCase({ 245 | test2: function (test) { 246 | test.equal(val, 'bar'); 247 | test.done(); 248 | } 249 | }) 250 | }) 251 | }); 252 | nodeunit.runSuite(null, s, {}, function (err, assertions) { 253 | test.ok(!assertions[0].failed()); 254 | test.equal(assertions.length, 1); 255 | test.done(); 256 | }); 257 | }; 258 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies 9 | */ 10 | 11 | var async = require('../deps/async'), 12 | fs = require('fs'), 13 | util = require('util'), 14 | Script = require('vm').Script, 15 | http = require('http'), 16 | path = require('path'); 17 | 18 | 19 | /** 20 | * Detect if coffee-script, iced-coffeescript, or streamline are available and 21 | * the respective file extensions to the search filter in modulePaths if it is. 22 | */ 23 | 24 | var extensions = [ 'js' ]; // js is always supported: add it unconditionally 25 | var extensionPattern; 26 | 27 | try { 28 | require('coffee' + '-script/register'); 29 | extensions.push('coffee'); 30 | } catch (e) { } 31 | 32 | try { 33 | require('iced-coffee' + '-script/register'); 34 | extensions.push('iced'); 35 | } catch (e) { } 36 | 37 | try { 38 | require('stream' + 'line').register(); 39 | extensions.push('_coffee'); 40 | extensions.push('_js'); 41 | } catch (e) { } 42 | 43 | extensionPattern = new RegExp('\\.(?:' + extensions.join('|') + ')$'); 44 | 45 | 46 | /** 47 | * Finds all modules at each path in an array, If a path is a directory, it 48 | * returns all supported file types inside it. This only reads 1 level deep in 49 | * the directory and does not recurse through sub-directories. 50 | * 51 | * The extension (.js, .coffee etc) is stripped from the filenames so they can 52 | * simply be require()'ed. 53 | * 54 | * @param {Array} paths 55 | * @param {Function} callback 56 | * @param {Boolean=} recursive 57 | * @api public 58 | */ 59 | exports.modulePaths = function modulePaths(paths, callback, recursive) { 60 | recursive = (recursive === true); 61 | async.concatSeries(paths, function (p, cb) { 62 | fs.stat(p, function (err, stats) { 63 | if (err) { 64 | return cb(err); 65 | } 66 | if (stats.isFile()) { 67 | return cb(null, [p]); 68 | } 69 | if (stats.isDirectory()) { 70 | fs.readdir(p, function (err, files) { 71 | if (err) { 72 | return cb(err); 73 | } 74 | 75 | // filter out any filenames with unsupported extensions 76 | var modules = files.filter(function (filename) { 77 | return extensionPattern.exec(filename); 78 | }); 79 | 80 | // remove extension from module name and prepend the 81 | // directory path 82 | var fullpaths = modules.map(function (filename) { 83 | var mod_name = filename.replace(extensionPattern, ''); 84 | return [p, mod_name].join('/'); 85 | }); 86 | 87 | if (recursive) { 88 | // get all sub directories 89 | var directories = 90 | files 91 | .map(function(filename) { 92 | // resolve path first 93 | return path.resolve(p, filename); 94 | }) 95 | .filter(function(filename) { 96 | // fetch only directories 97 | return (fs.statSync(filename).isDirectory()); 98 | }); 99 | 100 | // recursively call modulePaths() with sub directories 101 | modulePaths(directories, function(err, files) { 102 | if (!err) { 103 | cb(null, fullpaths.concat(files).sort()) 104 | } else { 105 | cb(err); 106 | } 107 | }, recursive); 108 | } else { 109 | // sort filenames here, because Array.map changes order 110 | fullpaths.sort(); 111 | 112 | // finish 113 | cb(null, fullpaths); 114 | } 115 | 116 | }); 117 | } 118 | }); 119 | }, callback); 120 | }; 121 | 122 | /** 123 | * Evaluates JavaScript files in a sandbox, returning the context. The first 124 | * argument can either be a single filename or an array of filenames. If 125 | * multiple filenames are given their contents are concatenated before 126 | * evalution. The second argument is an optional context to use for the sandbox. 127 | * 128 | * @param files 129 | * @param {Object} sandbox 130 | * @return {Object} 131 | * @api public 132 | */ 133 | 134 | exports.sandbox = function (files, /*optional*/sandbox) { 135 | var source, script, result; 136 | if (!(files instanceof Array)) { 137 | files = [files]; 138 | } 139 | source = files.map(function (file) { 140 | return fs.readFileSync(file, 'utf8'); 141 | }).join(''); 142 | 143 | if (!sandbox) { 144 | sandbox = {}; 145 | } 146 | script = new Script(source); 147 | result = script.runInNewContext(sandbox); 148 | return sandbox; 149 | }; 150 | 151 | /** 152 | * Provides a http request, response testing environment. 153 | * 154 | * Example: 155 | * 156 | * var httputil = require('nodeunit').utils.httputil 157 | * exports.testSomething = function(test) { 158 | * httputil(function (req, resp) { 159 | * resp.writeHead(200, {}); 160 | * resp.end('test data'); 161 | * }, 162 | * function(server, client) { 163 | * client.fetch('GET', '/', {}, function(resp) { 164 | * test.equal('test data', resp.body); 165 | * server.close(); 166 | * test.done(); 167 | * }) 168 | * }); 169 | * }; 170 | * 171 | * @param {Function} cgi 172 | * @param {Function} envReady 173 | * @api public 174 | */ 175 | exports.httputil = function (cgi, envReady) { 176 | var hostname = process.env.HOSTNAME || 'localhost'; 177 | var port = process.env.PORT || 3000; 178 | 179 | var server = http.createServer(cgi); 180 | server.listen(port, hostname); 181 | 182 | var agent = new http.Agent({ host: hostname, port: port, maxSockets: 1 }); 183 | var client = { 184 | fetch: function (method, path, headers, respReady) { 185 | var request = http.request({ 186 | host: hostname, 187 | port: port, 188 | agent: agent, 189 | method: method, 190 | path: path, 191 | headers: headers 192 | }); 193 | request.end(); 194 | request.on('response', function (response) { 195 | response.setEncoding('utf8'); 196 | response.on('data', function (chunk) { 197 | if (response.body) { 198 | response.body += chunk; 199 | } else { 200 | response.body = chunk; 201 | } 202 | }); 203 | response.on('end', function () { 204 | if (response.headers['content-type'] === 'application/json') { 205 | response.bodyAsObject = JSON.parse(response.body); 206 | } 207 | respReady(response); 208 | }); 209 | }); 210 | } 211 | }; 212 | 213 | process.nextTick(function () { 214 | if (envReady && typeof envReady === 'function') { 215 | envReady(server, client); 216 | } 217 | }); 218 | }; 219 | 220 | 221 | /** 222 | * Improves formatting of AssertionError messages to make deepEqual etc more 223 | * readable. 224 | * 225 | * @param {Object} assertion 226 | * @return {Object} 227 | * @api public 228 | */ 229 | 230 | exports.betterErrors = function (assertion) { 231 | if (!assertion.error) { 232 | return assertion; 233 | } 234 | var e = assertion.error; 235 | 236 | if (typeof e.actual !== 'undefined' && typeof e.expected !== 'undefined') { 237 | var actual = util.inspect(e.actual, false, 10).replace(/\n$/, ''); 238 | var expected = util.inspect(e.expected, false, 10).replace(/\n$/, ''); 239 | 240 | var multiline = ( 241 | actual.indexOf('\n') !== -1 || 242 | expected.indexOf('\n') !== -1 243 | ); 244 | var spacing = (multiline ? '\n' : ' '); 245 | e._message = e.message; 246 | e.stack = ( 247 | e.name + ':' + spacing + 248 | actual + spacing + e.operator + spacing + 249 | expected + '\n' + 250 | e.stack.split('\n').slice(1).join('\n') 251 | ); 252 | } 253 | return assertion; 254 | }; 255 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE = nodeunit 2 | NODEJS = $(if $(shell test -f /usr/bin/nodejs && echo "true"),nodejs,node) 3 | 4 | PREFIX ?= /usr/local 5 | BINDIR ?= $(PREFIX)/bin 6 | DATADIR ?= $(PREFIX)/share 7 | MANDIR ?= $(PREFIX)/share/man 8 | LIBDIR ?= $(PREFIX)/lib 9 | NODEJSLIBDIR ?= $(LIBDIR)/$(NODEJS) 10 | 11 | BUILDDIR = dist 12 | 13 | DOCS = $(shell find doc -name '*.md' \ 14 | |sed 's|.md|.1|g' \ 15 | |sed 's|doc/|man1/|g' \ 16 | ) 17 | 18 | 19 | $(shell if [ ! -d $(BUILDDIR) ]; then mkdir $(BUILDDIR); fi) 20 | 21 | all: build doc 22 | 23 | browser: 24 | # super hacky build script for browser version! 25 | mkdir -p $(BUILDDIR)/browser 26 | rm -rf $(BUILDDIR)/browser/* 27 | # build browser version of nodeunit.js 28 | cat share/license.js >> $(BUILDDIR)/browser/nodeunit.js 29 | echo "nodeunit = (function(){" >> $(BUILDDIR)/browser/nodeunit.js 30 | cat deps/json2.js >> $(BUILDDIR)/browser/nodeunit.js 31 | # make assert global 32 | echo "var assert = this.assert = {};" >> $(BUILDDIR)/browser/nodeunit.js 33 | echo "var types = {};" >> $(BUILDDIR)/browser/nodeunit.js 34 | echo "var core = {};" >> $(BUILDDIR)/browser/nodeunit.js 35 | echo "var nodeunit = {};" >> $(BUILDDIR)/browser/nodeunit.js 36 | echo "var reporter = {};" >> $(BUILDDIR)/browser/nodeunit.js 37 | cat deps/async.js >> $(BUILDDIR)/browser/nodeunit.js 38 | echo "(function(exports){" >> $(BUILDDIR)/browser/nodeunit.js 39 | cat lib/assert.js >> $(BUILDDIR)/browser/nodeunit.js 40 | echo "})(assert);" >> $(BUILDDIR)/browser/nodeunit.js 41 | echo "(function(exports){" >> $(BUILDDIR)/browser/nodeunit.js 42 | cat lib/types.js >> $(BUILDDIR)/browser/nodeunit.js 43 | echo "})(types);" >> $(BUILDDIR)/browser/nodeunit.js 44 | echo "(function(exports){" >> $(BUILDDIR)/browser/nodeunit.js 45 | cat lib/core.js >> $(BUILDDIR)/browser/nodeunit.js 46 | echo "})(core);" >> $(BUILDDIR)/browser/nodeunit.js 47 | echo "(function(exports){" >> $(BUILDDIR)/browser/nodeunit.js 48 | cat lib/reporters/browser.js >> $(BUILDDIR)/browser/nodeunit.js 49 | echo "})(reporter);" >> $(BUILDDIR)/browser/nodeunit.js 50 | echo "nodeunit = core;" >> $(BUILDDIR)/browser/nodeunit.js 51 | echo "nodeunit.assert = assert;" >> $(BUILDDIR)/browser/nodeunit.js 52 | echo "nodeunit.reporter = reporter;" >> $(BUILDDIR)/browser/nodeunit.js 53 | echo "nodeunit.run = reporter.run;" >> $(BUILDDIR)/browser/nodeunit.js 54 | echo "return nodeunit; })();" >> $(BUILDDIR)/browser/nodeunit.js 55 | cp $(BUILDDIR)/browser/nodeunit.js $(BUILDDIR)/browser/.nodeunit.js 56 | sed "/\@REMOVE_LINE_FOR_BROWSER/d" <$(BUILDDIR)/browser/.nodeunit.js > $(BUILDDIR)/browser/nodeunit.js 57 | rm $(BUILDDIR)/browser/.nodeunit.js 58 | # copy nodeunit.css 59 | cp share/nodeunit.css $(BUILDDIR)/browser/nodeunit.css 60 | # create nodeunit.min.js 61 | node_modules/uglify-js/bin/uglifyjs $(BUILDDIR)/browser/nodeunit.js > $(BUILDDIR)/browser/nodeunit.min.js 62 | # create test scripts 63 | mkdir -p $(BUILDDIR)/browser/test 64 | cp test/test.html $(BUILDDIR)/browser/test/test.html 65 | # test-base.js 66 | echo "(function (exports) {" > $(BUILDDIR)/browser/test/test-base.js 67 | cat test/test-base.js >> $(BUILDDIR)/browser/test/test-base.js 68 | echo "})(this.test_base = {});" >> $(BUILDDIR)/browser/test/test-base.js 69 | cp $(BUILDDIR)/browser/test/test-base.js $(BUILDDIR)/browser/.test-base.js 70 | sed "/\@REMOVE_LINE_FOR_BROWSER/d" <$(BUILDDIR)/browser/.test-base.js > $(BUILDDIR)/browser/test/test-base.js 71 | rm $(BUILDDIR)/browser/.test-base.js 72 | # test-runmodule.js 73 | echo "(function (exports) {" > $(BUILDDIR)/browser/test/test-runmodule.js 74 | cat test/test-runmodule.js >> $(BUILDDIR)/browser/test/test-runmodule.js 75 | echo "})(this.test_runmodule = {});" >> $(BUILDDIR)/browser/test/test-runmodule.js 76 | cp $(BUILDDIR)/browser/test/test-runmodule.js $(BUILDDIR)/browser/.test-runmodule.js 77 | sed "/\@REMOVE_LINE_FOR_BROWSER/d" <$(BUILDDIR)/browser/.test-runmodule.js > $(BUILDDIR)/browser/test/test-runmodule.js 78 | rm $(BUILDDIR)/browser/.test-runmodule.js 79 | # test-runtest.js 80 | echo "(function (exports) {" > $(BUILDDIR)/browser/test/test-runtest.js 81 | cat test/test-runtest.js >> $(BUILDDIR)/browser/test/test-runtest.js 82 | echo "})(this.test_runtest = {});" >> $(BUILDDIR)/browser/test/test-runtest.js 83 | cp $(BUILDDIR)/browser/test/test-runtest.js $(BUILDDIR)/browser/.test-runtest.js 84 | sed "/\@REMOVE_LINE_FOR_BROWSER/d" <$(BUILDDIR)/browser/.test-runtest.js > $(BUILDDIR)/browser/test/test-runtest.js 85 | rm $(BUILDDIR)/browser/.test-runtest.js 86 | # test-testcase.js 87 | echo "(function (exports) {" > $(BUILDDIR)/browser/test/test-testcase.js 88 | cat test/test-testcase.js >> $(BUILDDIR)/browser/test/test-testcase.js 89 | echo "})(this.test_testcase = {});" >> $(BUILDDIR)/browser/test/test-testcase.js 90 | cp $(BUILDDIR)/browser/test/test-testcase.js $(BUILDDIR)/browser/.test-testcase.js 91 | sed "/\@REMOVE_LINE_FOR_BROWSER/d" <$(BUILDDIR)/browser/.test-testcase.js > $(BUILDDIR)/browser/test/test-testcase.js 92 | rm $(BUILDDIR)/browser/.test-testcase.js 93 | # test-testcase-legacy.js 94 | echo "(function (exports) {" > $(BUILDDIR)/browser/test/test-testcase-legacy.js 95 | cat test/test-testcase-legacy.js >> $(BUILDDIR)/browser/test/test-testcase-legacy.js 96 | echo "})(this.test_testcase_legacy = {});" >> $(BUILDDIR)/browser/test/test-testcase-legacy.js 97 | cp $(BUILDDIR)/browser/test/test-testcase-legacy.js $(BUILDDIR)/browser/.test-testcase-legacy.js 98 | sed "/\@REMOVE_LINE_FOR_BROWSER/d" <$(BUILDDIR)/browser/.test-testcase-legacy.js > $(BUILDDIR)/browser/test/test-testcase-legacy.js 99 | rm $(BUILDDIR)/browser/.test-testcase-legacy.js 100 | # copy nodeunit.js to dist/browser/test to make it easier for me to host and 101 | # run on windows VMs with IE 102 | cp $(BUILDDIR)/browser/nodeunit.js $(BUILDDIR)/browser/test/nodeunit.js 103 | cp $(BUILDDIR)/browser/nodeunit.css $(BUILDDIR)/browser/test/nodeunit.css 104 | 105 | commonjs: 106 | # super hacky build script for browser commonjs version! 107 | ##### make commonjs browser module ###### 108 | mkdir -p $(BUILDDIR)/commonjs 109 | rm -rf $(BUILDDIR)/commonjs/* 110 | mkdir -p $(BUILDDIR)/commonjs/deps 111 | cp deps/json2.js $(BUILDDIR)/commonjs/deps 112 | cp deps/async.js $(BUILDDIR)/commonjs/deps 113 | echo "var async = require('async');" >> $(BUILDDIR)/commonjs/nodeunit.js 114 | echo "var assert = {};" >> $(BUILDDIR)/commonjs/nodeunit.js 115 | echo "var types = {};" >> $(BUILDDIR)/commonjs/nodeunit.js 116 | echo "var core = {};" >> $(BUILDDIR)/commonjs/nodeunit.js 117 | echo "var nodeunit = {};" >> $(BUILDDIR)/commonjs/nodeunit.js 118 | echo "var reporter = {};" >> $(BUILDDIR)/commonjs/nodeunit.js 119 | echo "(function(exports){" >> $(BUILDDIR)/commonjs/nodeunit.js 120 | cat lib/assert.js >> $(BUILDDIR)/commonjs/nodeunit.js 121 | echo "})(assert);" >> $(BUILDDIR)/commonjs/nodeunit.js 122 | echo "(function(exports){" >> $(BUILDDIR)/commonjs/nodeunit.js 123 | cat lib/types.js >> $(BUILDDIR)/commonjs/nodeunit.js 124 | echo "})(types);" >> $(BUILDDIR)/commonjs/nodeunit.js 125 | echo "(function(exports){" >> $(BUILDDIR)/commonjs/nodeunit.js 126 | cat lib/core.js >> $(BUILDDIR)/commonjs/nodeunit.js 127 | echo "})(core);" >> $(BUILDDIR)/commonjs/nodeunit.js 128 | echo "module.exports = core;" >> $(BUILDDIR)/commonjs/nodeunit.js 129 | echo "(function(exports, nodeunit){" >> $(BUILDDIR)/commonjs/nodeunit.js 130 | cat lib/reporters/browser.js >> $(BUILDDIR)/commonjs/nodeunit.js 131 | echo "})(reporter, module.exports);" >> $(BUILDDIR)/commonjs/nodeunit.js 132 | echo "module.exports.assert = assert;" >> $(BUILDDIR)/commonjs/nodeunit.js 133 | echo "module.exports.reporter = reporter;" >> $(BUILDDIR)/commonjs/nodeunit.js 134 | echo "module.exports.run = reporter.run;" >> $(BUILDDIR)/commonjs/nodeunit.js 135 | sed -i "/\@REMOVE_LINE_FOR_BROWSER/d" $(BUILDDIR)/commonjs/nodeunit.js 136 | sed -i "/\@REMOVE_LINE_FOR_COMMONJS/d" $(BUILDDIR)/commonjs/nodeunit.js 137 | ##### end of commonjs browser module ##### 138 | 139 | build: stamp-build 140 | 141 | stamp-build: $(wildcard deps/* lib/*.js) 142 | touch $@; 143 | mkdir -p $(BUILDDIR)/nodeunit 144 | cp -R bin node_modules deps index.js lib package.json share $(BUILDDIR)/nodeunit 145 | printf '#!/bin/sh\n$(NODEJS) $(NODEJSLIBDIR)/$(PACKAGE)/bin/nodeunit $$@' > $(BUILDDIR)/nodeunit.sh 146 | 147 | test: 148 | $(NODEJS) ./bin/nodeunit test 149 | 150 | install: build 151 | install -d $(NODEJSLIBDIR) 152 | cp -a $(BUILDDIR)/nodeunit $(NODEJSLIBDIR) 153 | install -m 0755 $(BUILDDIR)/nodeunit.sh $(BINDIR)/nodeunit 154 | install -d $(MANDIR)/man1/ 155 | cp -a man1/nodeunit.1 $(MANDIR)/man1/ 156 | 157 | uninstall: 158 | rm -rf $(NODEJSLIBDIR)/nodeunit $(NODEJSLIBDIR)/nodeunit.js $(BINDIR)/nodeunit 159 | rm -rf $(MANDIR)/man1/nodeunit.1 160 | 161 | clean: 162 | rm -rf $(BUILDDIR) stamp-build 163 | 164 | lint: 165 | nodelint --config nodelint.cfg ./index.js ./bin/nodeunit ./bin/nodeunit.json ./lib/*.js ./lib/reporters/*.js ./test/*.js 166 | 167 | doc: man1 $(DOCS) 168 | @true 169 | 170 | man1: 171 | @if ! test -d man1 ; then mkdir -p man1 ; fi 172 | 173 | # use `npm install ronn` for this to work. 174 | man1/%.1: doc/%.md 175 | ronn --roff $< > $@ 176 | 177 | .PHONY: browser test install uninstall build all 178 | -------------------------------------------------------------------------------- /test/test-runmodule.js: -------------------------------------------------------------------------------- 1 | /* THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! 2 | * You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build. 3 | * Only code on that line will be removed, its mostly to avoid requiring code 4 | * that is node specific 5 | */ 6 | 7 | var nodeunit = require('../lib/nodeunit'); // @REMOVE_LINE_FOR_BROWSER 8 | 9 | 10 | exports.testRunModule = function (test) { 11 | test.expect(59); 12 | var call_order = []; 13 | var testmodule = { 14 | test1: function (test) { 15 | call_order.push('test1'); 16 | test.ok(true, 'ok true'); 17 | test.done(); 18 | }, 19 | test2: function (test) { 20 | call_order.push('test2'); 21 | test.ok(false, 'ok false'); 22 | test.ok(false, 'ok false'); 23 | test.done(); 24 | }, 25 | test3: function (test) { 26 | call_order.push('test3'); 27 | test.done(); 28 | } 29 | }; 30 | nodeunit.runModule('testmodule', testmodule, { 31 | log: function (assertion) { 32 | call_order.push('log'); 33 | }, 34 | testStart: function (name) { 35 | call_order.push('testStart'); 36 | test.ok( 37 | name.toString() === 'test1' || 38 | name.toString() === 'test2' || 39 | name.toString() === 'test3', 40 | 'testStart called with test name ' 41 | ); 42 | }, 43 | testReady: function (tst) { 44 | call_order.push('testReady'); 45 | test.ok(tst.done, 'testReady called with non-test object'); 46 | test.ok(tst.ok, 'testReady called with non-test object'); 47 | test.ok(tst.same, 'testReady called with non-test object'); 48 | test.ok(tst.expect, 'testReady called with non-test object'); 49 | test.ok(tst._assertion_list, 'testReady called with non-test object'); 50 | test.ok(tst.AssertionError, 'testReady called with non-test object'); 51 | test.ok(tst.fail, 'testReady called with non-test object'); 52 | test.ok(tst.equal, 'testReady called with non-test object'); 53 | test.ok(tst.notEqual, 'testReady called with non-test object'); 54 | test.ok(tst.deepEqual, 'testReady called with non-test object'); 55 | test.ok(tst.notDeepEqual, 'testReady called with non-test object'); 56 | test.ok(tst.strictEqual, 'testReady called with non-test object'); 57 | test.ok(tst.notStrictEqual, 'testReady called with non-test object'); 58 | test.ok(tst.throws, 'testReady called with non-test object'); 59 | test.ok(tst.doesNotThrow, 'testReady called with non-test object'); 60 | test.ok(tst.ifError, 'testReady called with non-test object'); 61 | }, 62 | testDone: function (name, assertions) { 63 | call_order.push('testDone'); 64 | test.ok( 65 | name.toString() === 'test1' || 66 | name.toString() === 'test2' || 67 | name.toString() === 'test3', 68 | 'testDone called with test name' 69 | ); 70 | }, 71 | moduleDone: function (name, assertions) { 72 | call_order.push('moduleDone'); 73 | test.equals(assertions.length, 3); 74 | test.equals(assertions.failures(), 2); 75 | test.equals(name, 'testmodule'); 76 | test.ok(typeof assertions.duration === "number"); 77 | test.same(call_order, [ 78 | 'testStart', 'testReady', 'test1', 'log', 'testDone', 79 | 'testStart', 'testReady', 'test2', 'log', 'log', 'testDone', 80 | 'testStart', 'testReady', 'test3', 'testDone', 81 | 'moduleDone' 82 | ]); 83 | } 84 | }, test.done); 85 | }; 86 | 87 | 88 | exports.testRunModuleTestSpec = function (test) { 89 | test.expect(22); 90 | var call_order = []; 91 | var testmodule = { 92 | test1: function (test) { 93 | test.ok(true, 'ok true'); 94 | test.done(); 95 | }, 96 | test2: function (test) { 97 | call_order.push('test2'); 98 | test.ok(false, 'ok false'); 99 | test.ok(false, 'ok false'); 100 | test.done(); 101 | }, 102 | test3: function (test) { 103 | test.done(); 104 | } 105 | }; 106 | nodeunit.runModule('testmodule', testmodule, { 107 | testspec: "test2", 108 | log: function (assertion) { 109 | call_order.push('log'); 110 | }, 111 | testStart: function (name) { 112 | call_order.push('testStart'); 113 | test.equals( 114 | name,'test2', 115 | 'testStart called with test name ' 116 | ); 117 | }, 118 | testReady: function (tst) { 119 | call_order.push('testReady'); 120 | test.ok(tst.done, 'testReady called with non-test object'); 121 | test.ok(tst.ok, 'testReady called with non-test object'); 122 | test.ok(tst.same, 'testReady called with non-test object'); 123 | test.ok(tst.expect, 'testReady called with non-test object'); 124 | test.ok(tst._assertion_list, 'testReady called with non-test object'); 125 | test.ok(tst.AssertionError, 'testReady called with non-test object'); 126 | test.ok(tst.fail, 'testReady called with non-test object'); 127 | test.ok(tst.equal, 'testReady called with non-test object'); 128 | test.ok(tst.notEqual, 'testReady called with non-test object'); 129 | test.ok(tst.deepEqual, 'testReady called with non-test object'); 130 | test.ok(tst.notDeepEqual, 'testReady called with non-test object'); 131 | test.ok(tst.strictEqual, 'testReady called with non-test object'); 132 | test.ok(tst.notStrictEqual, 'testReady called with non-test object'); 133 | test.ok(tst.throws, 'testReady called with non-test object'); 134 | test.ok(tst.doesNotThrow, 'testReady called with non-test object'); 135 | test.ok(tst.ifError, 'testReady called with non-test object'); 136 | }, 137 | testDone: function (name, assertions) { 138 | call_order.push('testDone'); 139 | test.equal( 140 | name, 'test2', 141 | 'testDone called with test name' 142 | ); 143 | }, 144 | moduleDone: function (name, assertions) { 145 | call_order.push('moduleDone'); 146 | test.equals(assertions.length, 2); 147 | test.equals(name, 'testmodule'); 148 | test.ok(typeof assertions.duration === "number"); 149 | test.same(call_order, [ 150 | 'testStart', 'testReady', 'test2', 'log', 'log', 'testDone', 151 | 'moduleDone' 152 | ]); 153 | } 154 | }, test.done); 155 | }; 156 | 157 | exports.testRunModuleEmpty = function (test) { 158 | nodeunit.runModule('module with no exports', {}, { 159 | log: function (assertion) { 160 | test.ok(false, 'log should not be called'); 161 | }, 162 | testStart: function (name) { 163 | test.ok(false, 'testStart should not be called'); 164 | }, 165 | testReady: function (tst) { 166 | test.ok(false, 'testReady should not be called'); 167 | }, 168 | testDone: function (name, assertions) { 169 | test.ok(false, 'testDone should not be called'); 170 | }, 171 | moduleDone: function (name, assertions) { 172 | test.equals(assertions.length, 0); 173 | test.equals(assertions.failures(), 0); 174 | test.equals(name, 'module with no exports'); 175 | test.ok(typeof assertions.duration === "number"); 176 | } 177 | }, test.done); 178 | }; 179 | 180 | 181 | exports.testNestedTests = function (test) { 182 | var call_order = []; 183 | var m = { 184 | test1: function (test) { 185 | test.done(); 186 | }, 187 | suite: { 188 | t1: function (test) { 189 | test.done(); 190 | }, 191 | t2: function (test) { 192 | test.done(); 193 | }, 194 | another_suite: { 195 | t3: function (test) { 196 | test.done(); 197 | } 198 | } 199 | } 200 | }; 201 | nodeunit.runModule('modulename', m, { 202 | testStart: function (name) { 203 | call_order.push(['testStart'].concat(name)); 204 | }, 205 | testReady: function (tst) { 206 | call_order.push(['testReady']); 207 | }, 208 | testDone: function (name, assertions) { 209 | call_order.push(['testDone'].concat(name)); 210 | } 211 | }, function () { 212 | test.same(call_order, [ 213 | ['testStart', 'test1'], ['testReady'], ['testDone', 'test1'], 214 | ['testStart', 'suite', 't1'], ['testReady'], ['testDone', 'suite', 't1'], 215 | ['testStart', 'suite', 't2'], ['testReady'], ['testDone', 'suite', 't2'], 216 | ['testStart', 'suite', 'another_suite', 't3'], 217 | ['testReady'], 218 | ['testDone', 'suite', 'another_suite', 't3'] 219 | ]); 220 | test.done(); 221 | }); 222 | }; 223 | -------------------------------------------------------------------------------- /lib/core.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Nodeunit 3 | * Copyright (c) 2010 Caolan McMahon 4 | * MIT Licensed 5 | * 6 | * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! 7 | * You can use @REMOVE_LINE_FOR_BROWSER to remove code from the browser build. 8 | * Only code on that line will be removed, it's mostly to avoid requiring code 9 | * that is node specific 10 | */ 11 | 12 | /** 13 | * Module dependencies 14 | */ 15 | 16 | var async = require('../deps/async'), //@REMOVE_LINE_FOR_BROWSER 17 | nodeunit = require('./nodeunit'), //@REMOVE_LINE_FOR_BROWSER 18 | types = require('./types'); //@REMOVE_LINE_FOR_BROWSER 19 | 20 | 21 | /** 22 | * Added for browser compatibility 23 | */ 24 | 25 | var _keys = function (obj) { 26 | if (Object.keys) { 27 | return Object.keys(obj); 28 | } 29 | var keys = []; 30 | for (var k in obj) { 31 | if (obj.hasOwnProperty(k)) { 32 | keys.push(k); 33 | } 34 | } 35 | return keys; 36 | }; 37 | 38 | 39 | var _copy = function (obj) { 40 | var nobj = {}; 41 | var keys = _keys(obj); 42 | for (var i = 0; i < keys.length; i += 1) { 43 | nobj[keys[i]] = obj[keys[i]]; 44 | } 45 | return nobj; 46 | }; 47 | 48 | 49 | /** 50 | * Runs a test function (fn) from a loaded module. After the test function 51 | * calls test.done(), the callback is executed with an assertionList as its 52 | * second argument. 53 | * 54 | * @param {String} name 55 | * @param {Function} fn 56 | * @param {Object} opt 57 | * @param {Function} callback 58 | * @api public 59 | */ 60 | 61 | exports.runTest = function (name, fn, opt, callback) { 62 | var options = types.options(opt); 63 | 64 | options.testStart(name); 65 | var start = new Date().getTime(); 66 | var test = types.test(name, start, options, callback); 67 | 68 | options.testReady(test); 69 | try { 70 | fn(test); 71 | } 72 | catch (e) { 73 | test.done(e); 74 | } 75 | }; 76 | 77 | /** 78 | * Takes an object containing test functions or other test suites as properties 79 | * and runs each in series. After all tests have completed, the callback is 80 | * called with a list of all assertions as the second argument. 81 | * 82 | * If a name is passed to this function it is prepended to all test and suite 83 | * names that run within it. 84 | * 85 | * @param {String} name 86 | * @param {Object} suite 87 | * @param {Object} opt 88 | * @param {Function} callback 89 | * @api public 90 | */ 91 | 92 | exports.runSuite = function (name, suite, opt, callback) { 93 | suite = wrapGroup(suite); 94 | var keys = _keys(suite); 95 | 96 | async.concatSeries(keys, function (k, cb) { 97 | var prop = suite[k], _name; 98 | 99 | _name = name ? [].concat(name, k) : [k]; 100 | _name.toString = function () { 101 | // fallback for old one 102 | return this.join(' - '); 103 | }; 104 | 105 | if (typeof prop === 'function') { 106 | var in_name = false, 107 | in_specific_test = (_name.toString() === opt.testFullSpec) ? true : false; 108 | for (var i = 0; i < _name.length; i += 1) { 109 | if (_name[i] === opt.testspec) { 110 | in_name = true; 111 | } 112 | } 113 | 114 | if ((!opt.testFullSpec || in_specific_test) && (!opt.testspec || in_name)) { 115 | if (opt.moduleStart) { 116 | opt.moduleStart(); 117 | } 118 | exports.runTest(_name, suite[k], opt, cb); 119 | } 120 | else { 121 | return cb(); 122 | } 123 | } 124 | else { 125 | exports.runSuite(_name, suite[k], opt, cb); 126 | } 127 | }, callback); 128 | }; 129 | 130 | /** 131 | * Run each exported test function or test suite from a loaded module. 132 | * 133 | * @param {String} name 134 | * @param {Object} mod 135 | * @param {Object} opt 136 | * @param {Function} callback 137 | * @api public 138 | */ 139 | 140 | exports.runModule = function (name, mod, opt, callback) { 141 | var options = _copy(types.options(opt)); 142 | 143 | var _run = false; 144 | var _moduleStart = options.moduleStart; 145 | 146 | mod = wrapGroup(mod); 147 | 148 | function run_once() { 149 | if (!_run) { 150 | _run = true; 151 | _moduleStart(name); 152 | } 153 | } 154 | options.moduleStart = run_once; 155 | 156 | var start = new Date().getTime(); 157 | 158 | exports.runSuite(null, mod, options, function (err, a_list) { 159 | var end = new Date().getTime(); 160 | var assertion_list = types.assertionList(a_list, end - start); 161 | options.moduleDone(name, assertion_list); 162 | if (nodeunit.complete) { 163 | nodeunit.complete(name, assertion_list); 164 | } 165 | callback(null, a_list); 166 | }); 167 | }; 168 | 169 | /** 170 | * Treats an object literal as a list of modules keyed by name. Runs each 171 | * module and finished with calling 'done'. You can think of this as a browser 172 | * safe alternative to runFiles in the nodeunit module. 173 | * 174 | * @param {Object} modules 175 | * @param {Object} opt 176 | * @api public 177 | */ 178 | 179 | // TODO: add proper unit tests for this function 180 | exports.runModules = function (modules, opt) { 181 | var all_assertions = []; 182 | var options = types.options(opt); 183 | var start = new Date().getTime(); 184 | 185 | async.concatSeries(_keys(modules), function (k, cb) { 186 | exports.runModule(k, modules[k], options, cb); 187 | }, 188 | function (err, all_assertions) { 189 | var end = new Date().getTime(); 190 | options.done(types.assertionList(all_assertions, end - start)); 191 | }); 192 | }; 193 | 194 | 195 | /** 196 | * Wraps a test function with setUp and tearDown functions. 197 | * Used by testCase. 198 | * 199 | * @param {Function} setUp 200 | * @param {Function} tearDown 201 | * @param {Function} fn 202 | * @api private 203 | */ 204 | 205 | var wrapTest = function (setUp, tearDown, fn) { 206 | return function (test) { 207 | var context = {}; 208 | if (tearDown) { 209 | var done = test.done; 210 | test.done = function (err) { 211 | try { 212 | tearDown.call(context, function (err2) { 213 | if (err && err2) { 214 | test._assertion_list.push( 215 | types.assertion({error: err}) 216 | ); 217 | return done(err2); 218 | } 219 | done(err || err2); 220 | }); 221 | } 222 | catch (e) { 223 | done(e); 224 | } 225 | }; 226 | } 227 | if (setUp) { 228 | setUp.call(context, function (err) { 229 | if (err) { 230 | return test.done(err); 231 | } 232 | fn.call(context, test); 233 | }); 234 | } 235 | else { 236 | fn.call(context, test); 237 | } 238 | }; 239 | }; 240 | 241 | 242 | /** 243 | * Returns a serial callback from two functions. 244 | * 245 | * @param {Function} funcFirst 246 | * @param {Function} funcSecond 247 | * @api private 248 | */ 249 | 250 | var getSerialCallback = function (fns) { 251 | if (!fns.length) { 252 | return null; 253 | } 254 | return function (callback) { 255 | var that = this; 256 | var bound_fns = []; 257 | for (var i = 0, len = fns.length; i < len; i++) { 258 | (function (j) { 259 | bound_fns.push(function () { 260 | return fns[j].apply(that, arguments); 261 | }); 262 | })(i); 263 | } 264 | return async.series(bound_fns, callback); 265 | }; 266 | }; 267 | 268 | 269 | /** 270 | * Wraps a group of tests with setUp and tearDown functions. 271 | * Used by testCase. 272 | * 273 | * @param {Object} group 274 | * @param {Array} setUps - parent setUp functions 275 | * @param {Array} tearDowns - parent tearDown functions 276 | * @api private 277 | */ 278 | 279 | var wrapGroup = function (group, setUps, tearDowns) { 280 | var tests = {}; 281 | 282 | var setUps = setUps ? setUps.slice(): []; 283 | var tearDowns = tearDowns ? tearDowns.slice(): []; 284 | 285 | if (group.setUp) { 286 | setUps.push(group.setUp); 287 | delete group.setUp; 288 | } 289 | if (group.tearDown) { 290 | tearDowns.unshift(group.tearDown); 291 | delete group.tearDown; 292 | } 293 | 294 | var keys = _keys(group); 295 | 296 | for (var i = 0; i < keys.length; i += 1) { 297 | var k = keys[i]; 298 | if (typeof group[k] === 'function') { 299 | tests[k] = wrapTest( 300 | getSerialCallback(setUps), 301 | getSerialCallback(tearDowns), 302 | group[k] 303 | ); 304 | } 305 | else if (typeof group[k] === 'object') { 306 | tests[k] = wrapGroup(group[k], setUps, tearDowns); 307 | } 308 | } 309 | return tests; 310 | }; 311 | 312 | 313 | /** 314 | * Backwards compatibility for test suites using old testCase API 315 | */ 316 | 317 | exports.testCase = function (suite) { 318 | return suite; 319 | }; 320 | -------------------------------------------------------------------------------- /lib/assert.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is based on the node.js assert module, but with some small 3 | * changes for browser-compatibility 4 | * THIS FILE SHOULD BE BROWSER-COMPATIBLE JS! 5 | */ 6 | 7 | 8 | /** 9 | * Added for browser compatibility 10 | */ 11 | 12 | var _keys = function(obj){ 13 | if(Object.keys) return Object.keys(obj); 14 | if (typeof obj != 'object' && typeof obj != 'function') { 15 | throw new TypeError('-'); 16 | } 17 | var keys = []; 18 | for(var k in obj){ 19 | if(obj.hasOwnProperty(k)) keys.push(k); 20 | } 21 | return keys; 22 | }; 23 | 24 | 25 | 26 | // http://wiki.commonjs.org/wiki/Unit_Testing/1.0 27 | // 28 | // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! 29 | // 30 | // Originally from narwhal.js (http://narwhaljs.org) 31 | // Copyright (c) 2009 Thomas Robinson <280north.com> 32 | // 33 | // Permission is hereby granted, free of charge, to any person obtaining a copy 34 | // of this software and associated documentation files (the 'Software'), to 35 | // deal in the Software without restriction, including without limitation the 36 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 37 | // sell copies of the Software, and to permit persons to whom the Software is 38 | // furnished to do so, subject to the following conditions: 39 | // 40 | // The above copyright notice and this permission notice shall be included in 41 | // all copies or substantial portions of the Software. 42 | // 43 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 47 | // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 48 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 49 | 50 | 51 | var pSlice = Array.prototype.slice; 52 | 53 | // 1. The assert module provides functions that throw 54 | // AssertionError's when particular conditions are not met. The 55 | // assert module must conform to the following interface. 56 | 57 | var assert = exports; 58 | 59 | // 2. The AssertionError is defined in assert. 60 | // new assert.AssertionError({message: message, actual: actual, expected: expected}) 61 | 62 | assert.AssertionError = function AssertionError (options) { 63 | this.name = "AssertionError"; 64 | this.message = options.message; 65 | this.actual = options.actual; 66 | this.expected = options.expected; 67 | this.operator = options.operator; 68 | var stackStartFunction = options.stackStartFunction || fail; 69 | 70 | if (Error.captureStackTrace) { 71 | Error.captureStackTrace(this, stackStartFunction); 72 | } 73 | }; 74 | // code from util.inherits in node 75 | assert.AssertionError.super_ = Error; 76 | 77 | 78 | // EDITED FOR BROWSER COMPATIBILITY: replaced Object.create call 79 | // TODO: test what effect this may have 80 | var ctor = function () { this.constructor = assert.AssertionError; }; 81 | ctor.prototype = Error.prototype; 82 | assert.AssertionError.prototype = new ctor(); 83 | 84 | 85 | assert.AssertionError.prototype.toString = function() { 86 | if (this.message) { 87 | return [this.name+":", this.message].join(' '); 88 | } else { 89 | return [ this.name+":" 90 | , typeof this.expected !== 'undefined' ? JSON.stringify(this.expected) : 'undefined' 91 | , this.operator 92 | , typeof this.actual !== 'undefined' ? JSON.stringify(this.actual) : 'undefined' 93 | ].join(" "); 94 | } 95 | }; 96 | 97 | // assert.AssertionError instanceof Error 98 | 99 | assert.AssertionError.__proto__ = Error.prototype; 100 | 101 | // At present only the three keys mentioned above are used and 102 | // understood by the spec. Implementations or sub modules can pass 103 | // other keys to the AssertionError's constructor - they will be 104 | // ignored. 105 | 106 | // 3. All of the following functions must throw an AssertionError 107 | // when a corresponding condition is not met, with a message that 108 | // may be undefined if not provided. All assertion methods provide 109 | // both the actual and expected values to the assertion error for 110 | // display purposes. 111 | 112 | function fail(actual, expected, message, operator, stackStartFunction) { 113 | throw new assert.AssertionError({ 114 | message: message, 115 | actual: actual, 116 | expected: expected, 117 | operator: operator, 118 | stackStartFunction: stackStartFunction 119 | }); 120 | } 121 | 122 | // EXTENSION! allows for well behaved errors defined elsewhere. 123 | assert.fail = fail; 124 | 125 | // 4. Pure assertion tests whether a value is truthy, as determined 126 | // by !!guard. 127 | // assert.ok(guard, message_opt); 128 | // This statement is equivalent to assert.equal(true, guard, 129 | // message_opt);. To test strictly for the value true, use 130 | // assert.strictEqual(true, guard, message_opt);. 131 | 132 | assert.ok = function ok(value, message) { 133 | if (!!!value) fail(value, true, message, "==", assert.ok); 134 | }; 135 | 136 | // 5. The equality assertion tests shallow, coercive equality with 137 | // ==. 138 | // assert.equal(actual, expected, message_opt); 139 | 140 | assert.equal = function equal(actual, expected, message) { 141 | if (actual != expected) fail(actual, expected, message, "==", assert.equal); 142 | }; 143 | 144 | // 6. The non-equality assertion tests for whether two objects are not equal 145 | // with != assert.notEqual(actual, expected, message_opt); 146 | 147 | assert.notEqual = function notEqual(actual, expected, message) { 148 | if (actual == expected) { 149 | fail(actual, expected, message, "!=", assert.notEqual); 150 | } 151 | }; 152 | 153 | // 7. The equivalence assertion tests a deep equality relation. 154 | // assert.deepEqual(actual, expected, message_opt); 155 | 156 | assert.deepEqual = function deepEqual(actual, expected, message) { 157 | if (!_deepEqual(actual, expected)) { 158 | fail(actual, expected, message, "deepEqual", assert.deepEqual); 159 | } 160 | }; 161 | 162 | var Buffer = null; 163 | if (typeof require !== 'undefined' && typeof process !== 'undefined') { 164 | try { 165 | Buffer = require('buffer').Buffer; 166 | } 167 | catch (e) { 168 | // May be a CommonJS environment other than Node.js 169 | Buffer = null; 170 | } 171 | } 172 | 173 | function _deepEqual(actual, expected) { 174 | // 7.1. All identical values are equivalent, as determined by ===. 175 | if (actual === expected) 176 | return true; 177 | 178 | // Convert to primitives, if supported 179 | actual = actual.valueOf ? actual.valueOf() : actual; 180 | expected = expected.valueOf ? expected.valueOf() : expected; 181 | 182 | // 7.2. If the expected value is a Date object, the actual value is 183 | // equivalent if it is also a Date object that refers to the same time. 184 | if (actual instanceof Date && expected instanceof Date) { 185 | return actual.getTime() === expected.getTime(); 186 | 187 | // 7.2.1 If the expcted value is a RegExp object, the actual value is 188 | // equivalent if it is also a RegExp object that refers to the same source and options 189 | } else if (actual instanceof RegExp && expected instanceof RegExp) { 190 | return actual.source === expected.source && 191 | actual.global === expected.global && 192 | actual.ignoreCase === expected.ignoreCase && 193 | actual.multiline === expected.multiline; 194 | 195 | } else if (Buffer && actual instanceof Buffer && expected instanceof Buffer) { 196 | return (function() { 197 | var i, len; 198 | 199 | for (i = 0, len = expected.length; i < len; i++) { 200 | if (actual[i] !== expected[i]) { 201 | return false; 202 | } 203 | } 204 | return actual.length === expected.length; 205 | })(); 206 | // 7.3. Other pairs that do not both pass typeof value == "object", 207 | // equivalence is determined by ==. 208 | } else if (typeof actual != 'object' && typeof expected != 'object') { 209 | return actual == expected; 210 | 211 | // 7.4. For all other Object pairs, including Array objects, equivalence is 212 | // determined by having the same number of owned properties (as verified 213 | // with Object.prototype.hasOwnProperty.call), the same set of keys 214 | // (although not necessarily the same order), equivalent values for every 215 | // corresponding key, and an identical "prototype" property. Note: this 216 | // accounts for both named and indexed properties on Arrays. 217 | } else { 218 | return objEquiv(actual, expected); 219 | } 220 | } 221 | 222 | function isUndefinedOrNull (value) { 223 | return value === null || value === undefined; 224 | } 225 | 226 | function isArguments (object) { 227 | return Object.prototype.toString.call(object) == '[object Arguments]'; 228 | } 229 | 230 | function objEquiv (a, b) { 231 | if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) 232 | return false; 233 | 234 | // an identical "prototype" property. 235 | if (a.prototype !== b.prototype) return false; 236 | //~~~I've managed to break Object.keys through screwy arguments passing. 237 | // Converting to array solves the problem. 238 | if (isArguments(a)) { 239 | if (!isArguments(b)) { 240 | return false; 241 | } 242 | a = pSlice.call(a); 243 | b = pSlice.call(b); 244 | return _deepEqual(a, b); 245 | } 246 | try{ 247 | var ka = _keys(a), 248 | kb = _keys(b), 249 | key, i; 250 | } catch (e) {//happens when one is a string literal and the other isn't 251 | return false; 252 | } 253 | // having the same number of owned properties (keys incorporates hasOwnProperty) 254 | if (ka.length != kb.length) 255 | return false; 256 | //the same set of keys (although not necessarily the same order), 257 | ka.sort(); 258 | kb.sort(); 259 | //~~~cheap key test 260 | for (i = ka.length - 1; i >= 0; i--) { 261 | if (ka[i] != kb[i]) 262 | return false; 263 | } 264 | //equivalent values for every corresponding key, and 265 | //~~~possibly expensive deep test 266 | for (i = ka.length - 1; i >= 0; i--) { 267 | key = ka[i]; 268 | if (!_deepEqual(a[key], b[key] )) 269 | return false; 270 | } 271 | return true; 272 | } 273 | 274 | // 8. The non-equivalence assertion tests for any deep inequality. 275 | // assert.notDeepEqual(actual, expected, message_opt); 276 | 277 | assert.notDeepEqual = function notDeepEqual(actual, expected, message) { 278 | if (_deepEqual(actual, expected)) { 279 | fail(actual, expected, message, "notDeepEqual", assert.notDeepEqual); 280 | } 281 | }; 282 | 283 | // 9. The strict equality assertion tests strict equality, as determined by ===. 284 | // assert.strictEqual(actual, expected, message_opt); 285 | 286 | assert.strictEqual = function strictEqual(actual, expected, message) { 287 | if (actual !== expected) { 288 | fail(actual, expected, message, "===", assert.strictEqual); 289 | } 290 | }; 291 | 292 | // 10. The strict non-equality assertion tests for strict inequality, as determined by !==. 293 | // assert.notStrictEqual(actual, expected, message_opt); 294 | 295 | assert.notStrictEqual = function notStrictEqual(actual, expected, message) { 296 | if (actual === expected) { 297 | fail(actual, expected, message, "!==", assert.notStrictEqual); 298 | } 299 | }; 300 | 301 | function expectedException(actual, expected) { 302 | if (!actual || !expected) { 303 | return false; 304 | } 305 | 306 | if (expected instanceof RegExp) { 307 | return expected.test(actual.message || actual); 308 | } else if (actual instanceof expected) { 309 | return true; 310 | } else if (expected.call({}, actual) === true) { 311 | return true; 312 | } 313 | 314 | return false; 315 | } 316 | 317 | function _throws(shouldThrow, block, expected, message) { 318 | var actual; 319 | 320 | if (typeof expected === 'string') { 321 | message = expected; 322 | expected = null; 323 | } 324 | 325 | try { 326 | block(); 327 | } catch (e) { 328 | actual = e; 329 | } 330 | 331 | message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + 332 | (message ? ' ' + message : '.'); 333 | 334 | if (shouldThrow && !actual) { 335 | fail('Missing expected exception' + message); 336 | } 337 | 338 | if (!shouldThrow && expectedException(actual, expected)) { 339 | fail('Got unwanted exception' + message); 340 | } 341 | 342 | if ((shouldThrow && actual && expected && 343 | !expectedException(actual, expected)) || (!shouldThrow && actual)) { 344 | throw actual; 345 | } 346 | } 347 | 348 | // 11. Expected to throw an error: 349 | // assert.throws(block, Error_opt, message_opt); 350 | 351 | assert.throws = function(block, /*optional*/error, /*optional*/message) { 352 | _throws.apply(this, [true].concat(pSlice.call(arguments))); 353 | }; 354 | 355 | // EXTENSION! This is annoying to write outside this module. 356 | assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message) { 357 | _throws.apply(this, [false].concat(pSlice.call(arguments))); 358 | }; 359 | 360 | assert.ifError = function (err) { if (err) {throw err;}}; 361 | -------------------------------------------------------------------------------- /deps/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2010-11-17 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false, regexp: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | var JSON = {}; 163 | 164 | (function () { 165 | "use strict"; 166 | 167 | function f(n) { 168 | // Format integers to have at least two digits. 169 | return n < 10 ? '0' + n : n; 170 | } 171 | 172 | if (typeof Date.prototype.toJSON !== 'function') { 173 | 174 | Date.prototype.toJSON = function (key) { 175 | 176 | return isFinite(this.valueOf()) ? 177 | this.getUTCFullYear() + '-' + 178 | f(this.getUTCMonth() + 1) + '-' + 179 | f(this.getUTCDate()) + 'T' + 180 | f(this.getUTCHours()) + ':' + 181 | f(this.getUTCMinutes()) + ':' + 182 | f(this.getUTCSeconds()) + 'Z' : null; 183 | }; 184 | 185 | String.prototype.toJSON = 186 | Number.prototype.toJSON = 187 | Boolean.prototype.toJSON = function (key) { 188 | return this.valueOf(); 189 | }; 190 | } 191 | 192 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 193 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 194 | gap, 195 | indent, 196 | meta = { // table of character substitutions 197 | '\b': '\\b', 198 | '\t': '\\t', 199 | '\n': '\\n', 200 | '\f': '\\f', 201 | '\r': '\\r', 202 | '"' : '\\"', 203 | '\\': '\\\\' 204 | }, 205 | rep; 206 | 207 | 208 | function quote(string) { 209 | 210 | // If the string contains no control characters, no quote characters, and no 211 | // backslash characters, then we can safely slap some quotes around it. 212 | // Otherwise we must also replace the offending characters with safe escape 213 | // sequences. 214 | 215 | escapable.lastIndex = 0; 216 | return escapable.test(string) ? 217 | '"' + string.replace(escapable, function (a) { 218 | var c = meta[a]; 219 | return typeof c === 'string' ? c : 220 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 221 | }) + '"' : 222 | '"' + string + '"'; 223 | } 224 | 225 | 226 | function str(key, holder) { 227 | 228 | // Produce a string from holder[key]. 229 | 230 | var i, // The loop counter. 231 | k, // The member key. 232 | v, // The member value. 233 | length, 234 | mind = gap, 235 | partial, 236 | value = holder[key]; 237 | 238 | // If the value has a toJSON method, call it to obtain a replacement value. 239 | 240 | if (value && typeof value === 'object' && 241 | typeof value.toJSON === 'function') { 242 | value = value.toJSON(key); 243 | } 244 | 245 | // If we were called with a replacer function, then call the replacer to 246 | // obtain a replacement value. 247 | 248 | if (typeof rep === 'function') { 249 | value = rep.call(holder, key, value); 250 | } 251 | 252 | // What happens next depends on the value's type. 253 | 254 | switch (typeof value) { 255 | case 'string': 256 | return quote(value); 257 | 258 | case 'number': 259 | 260 | // JSON numbers must be finite. Encode non-finite numbers as null. 261 | 262 | return isFinite(value) ? String(value) : 'null'; 263 | 264 | case 'boolean': 265 | case 'null': 266 | 267 | // If the value is a boolean or null, convert it to a string. Note: 268 | // typeof null does not produce 'null'. The case is included here in 269 | // the remote chance that this gets fixed someday. 270 | 271 | return String(value); 272 | 273 | // If the type is 'object', we might be dealing with an object or an array or 274 | // null. 275 | 276 | case 'object': 277 | 278 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 279 | // so watch out for that case. 280 | 281 | if (!value) { 282 | return 'null'; 283 | } 284 | 285 | // Make an array to hold the partial results of stringifying this object value. 286 | 287 | gap += indent; 288 | partial = []; 289 | 290 | // Is the value an array? 291 | 292 | if (Object.prototype.toString.apply(value) === '[object Array]') { 293 | 294 | // The value is an array. Stringify every element. Use null as a placeholder 295 | // for non-JSON values. 296 | 297 | length = value.length; 298 | for (i = 0; i < length; i += 1) { 299 | partial[i] = str(i, value) || 'null'; 300 | } 301 | 302 | // Join all of the elements together, separated with commas, and wrap them in 303 | // brackets. 304 | 305 | v = partial.length === 0 ? '[]' : 306 | gap ? '[\n' + gap + 307 | partial.join(',\n' + gap) + '\n' + 308 | mind + ']' : 309 | '[' + partial.join(',') + ']'; 310 | gap = mind; 311 | return v; 312 | } 313 | 314 | // If the replacer is an array, use it to select the members to be stringified. 315 | 316 | if (rep && typeof rep === 'object') { 317 | length = rep.length; 318 | for (i = 0; i < length; i += 1) { 319 | k = rep[i]; 320 | if (typeof k === 'string') { 321 | v = str(k, value); 322 | if (v) { 323 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 324 | } 325 | } 326 | } 327 | } else { 328 | 329 | // Otherwise, iterate through all of the keys in the object. 330 | 331 | for (k in value) { 332 | if (Object.hasOwnProperty.call(value, k)) { 333 | v = str(k, value); 334 | if (v) { 335 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 336 | } 337 | } 338 | } 339 | } 340 | 341 | // Join all of the member texts together, separated with commas, 342 | // and wrap them in braces. 343 | 344 | v = partial.length === 0 ? '{}' : 345 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 346 | mind + '}' : '{' + partial.join(',') + '}'; 347 | gap = mind; 348 | return v; 349 | } 350 | } 351 | 352 | // If the JSON object does not yet have a stringify method, give it one. 353 | 354 | if (typeof JSON.stringify !== 'function') { 355 | JSON.stringify = function (value, replacer, space) { 356 | 357 | // The stringify method takes a value and an optional replacer, and an optional 358 | // space parameter, and returns a JSON text. The replacer can be a function 359 | // that can replace values, or an array of strings that will select the keys. 360 | // A default replacer method can be provided. Use of the space parameter can 361 | // produce text that is more easily readable. 362 | 363 | var i; 364 | gap = ''; 365 | indent = ''; 366 | 367 | // If the space parameter is a number, make an indent string containing that 368 | // many spaces. 369 | 370 | if (typeof space === 'number') { 371 | for (i = 0; i < space; i += 1) { 372 | indent += ' '; 373 | } 374 | 375 | // If the space parameter is a string, it will be used as the indent string. 376 | 377 | } else if (typeof space === 'string') { 378 | indent = space; 379 | } 380 | 381 | // If there is a replacer, it must be a function or an array. 382 | // Otherwise, throw an error. 383 | 384 | rep = replacer; 385 | if (replacer && typeof replacer !== 'function' && 386 | (typeof replacer !== 'object' || 387 | typeof replacer.length !== 'number')) { 388 | throw new Error('JSON.stringify'); 389 | } 390 | 391 | // Make a fake root object containing our value under the key of ''. 392 | // Return the result of stringifying the value. 393 | 394 | return str('', {'': value}); 395 | }; 396 | } 397 | 398 | 399 | // If the JSON object does not yet have a parse method, give it one. 400 | 401 | if (typeof JSON.parse !== 'function') { 402 | JSON.parse = function (text, reviver) { 403 | 404 | // The parse method takes a text and an optional reviver function, and returns 405 | // a JavaScript value if the text is a valid JSON text. 406 | 407 | var j; 408 | 409 | function walk(holder, key) { 410 | 411 | // The walk method is used to recursively walk the resulting structure so 412 | // that modifications can be made. 413 | 414 | var k, v, value = holder[key]; 415 | if (value && typeof value === 'object') { 416 | for (k in value) { 417 | if (Object.hasOwnProperty.call(value, k)) { 418 | v = walk(value, k); 419 | if (v !== undefined) { 420 | value[k] = v; 421 | } else { 422 | delete value[k]; 423 | } 424 | } 425 | } 426 | } 427 | return reviver.call(holder, key, value); 428 | } 429 | 430 | 431 | // Parsing happens in four stages. In the first stage, we replace certain 432 | // Unicode characters with escape sequences. JavaScript handles many characters 433 | // incorrectly, either silently deleting them, or treating them as line endings. 434 | 435 | text = String(text); 436 | cx.lastIndex = 0; 437 | if (cx.test(text)) { 438 | text = text.replace(cx, function (a) { 439 | return '\\u' + 440 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 441 | }); 442 | } 443 | 444 | // In the second stage, we run the text against regular expressions that look 445 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 446 | // because they can cause invocation, and '=' because it can cause mutation. 447 | // But just to be safe, we want to reject all unexpected forms. 448 | 449 | // We split the second stage into 4 regexp operations in order to work around 450 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 451 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 452 | // replace all simple value tokens with ']' characters. Third, we delete all 453 | // open brackets that follow a colon or comma or that begin the text. Finally, 454 | // we look to see that the remaining characters are only whitespace or ']' or 455 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 456 | 457 | if (/^[\],:{}\s]*$/ 458 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 459 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 460 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 461 | 462 | // In the third stage we use the eval function to compile the text into a 463 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 464 | // in JavaScript: it can begin a block or an object literal. We wrap the text 465 | // in parens to eliminate the ambiguity. 466 | 467 | j = eval('(' + text + ')'); 468 | 469 | // In the optional fourth stage, we recursively walk the new structure, passing 470 | // each name/value pair to a reviver function for possible transformation. 471 | 472 | return typeof reviver === 'function' ? 473 | walk({'': j}, '') : j; 474 | } 475 | 476 | // If the text is not JSON parseable, then a SyntaxError is thrown. 477 | 478 | throw new SyntaxError('JSON.parse'); 479 | }; 480 | } 481 | }()); 482 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nodeunit 2 | ======== 3 | 4 | [![Build Status](https://travis-ci.org/caolan/nodeunit.svg?branch=master)](https://travis-ci.org/caolan/nodeunit) 5 | 6 | Simple syntax, powerful tools. Nodeunit provides easy async unit testing for 7 | node.js and the browser. 8 | 9 | 10 | DEPRECATED PROJECT 11 | ----- 12 | 13 | The project is very stale. We've kept it working on new versions of node, and sometimes merged small PRs that help teams relying on nodeunit. 14 | 15 | Nodeunit was the arguably first testing framework developed for node. It was very useful at the time, but there's an overwhelming number 16 | of other worthwhile testing solutions out there that are actively maintained. tap, ava, tape, mocha, jasmine, jest, ... the list goes on and on. 17 | 18 | If Nodeunit were truly bringing some different philosophy to the testing scene I'd say yes effort should be made to shore up it's development, 19 | but given how many other great options there are out there, a benefit of letting it atrophy is it's one less choice people have to make when 20 | choosing a testing solution. *You are strongly encouraged to check out other more modern testing options.* 21 | 22 | 23 | Features 24 | -------- 25 | 26 | * Simple to use 27 | * Just export the tests from a module 28 | * Works with node.js and in the browser 29 | * Helps you avoid common pitfalls when testing asynchronous code 30 | * Easy to add test cases with setUp and tearDown functions if you wish 31 | * Flexible reporters for custom output, built-in support for HTML and jUnit XML 32 | * Allows the use of mocks and stubs 33 | 34 | Contributors 35 | ------------ 36 | 37 | * [alexgorbatchev](https://github.com/alexgorbatchev) 38 | * [alexkwolfe](https://github.com/alexkwolfe) 39 | * [azatoth](https://github.com/azatoth) 40 | * [kadirpekel](https://github.com/kadirpekel) 41 | * [lambdalisue](https://github.com/lambdalisue) 42 | * [luebken](https://github.com/luebken) 43 | * [orlandov](https://github.com/orlandov) 44 | * [Sannis](https://github.com/Sannis) 45 | * [sstephenson](https://github.com/sstephenson) 46 | * [thegreatape](https://github.com/thegreatape) 47 | * [mmalecki](https://github.com/mmalecki) 48 | * and thanks to [cjohansen](https://github.com/cjohansen) for input and advice 49 | on implementing setUp and tearDown functions. See 50 | [cjohansen's fork](https://github.com/cjohansen/nodeunit). 51 | 52 | Also, check out gerad's [nodeunit-dsl](https://github.com/gerad/nodeunit-dsl) 53 | project, which implements a 'pretty dsl on top of nodeunit'. 54 | 55 | More contributor information can be found in the 56 | [CONTRIBUTORS.md](https://github.com/caolan/nodeunit/blob/master/CONTRIBUTORS.md) 57 | file. 58 | 59 | Usage 60 | ----- 61 | 62 | Here is an example unit test module: 63 | 64 | exports.testSomething = function(test) { 65 | test.expect(1); 66 | test.ok(true, "this assertion should pass"); 67 | test.done(); 68 | }; 69 | 70 | exports.testSomethingElse = function(test) { 71 | test.ok(false, "this assertion should fail"); 72 | test.done(); 73 | }; 74 | 75 | When run using the included test runner, this will output the following: 76 | 77 | 78 | 79 | Installation 80 | ------------ 81 | 82 | There are two options for installing nodeunit: 83 | 84 | 1. Clone / download nodeunit from [github](https://github.com/caolan/nodeunit), 85 | then: 86 | 87 | make && sudo make install 88 | 89 | 2. Install via npm: 90 | 91 | npm install nodeunit -g 92 | 93 | API Documentation 94 | ----------------- 95 | 96 | Nodeunit uses the functions available in the node.js 97 | [assert module](http://nodejs.org/docs/v0.4.2/api/assert.html): 98 | 99 | * __ok(value, [message])__ - Tests if value is a true value. 100 | * __equal(actual, expected, [message])__ - Tests shallow, coercive equality 101 | with the equal comparison operator ( == ). 102 | * __notEqual(actual, expected, [message])__ - Tests shallow, coercive 103 | non-equality with the not equal comparison operator ( != ). 104 | * __deepEqual(actual, expected, [message])__ - Tests for deep equality. 105 | * __notDeepEqual(actual, expected, [message])__ - Tests for any deep 106 | inequality. 107 | * __strictEqual(actual, expected, [message])__ - Tests strict equality, as 108 | determined by the strict equality operator ( === ) 109 | * __notStrictEqual(actual, expected, [message])__ - Tests strict non-equality, 110 | as determined by the strict not equal operator ( !== ) 111 | * __throws(block, [error], [message])__ - Expects block to throw an error. 112 | * __doesNotThrow(block, [error], [message])__ - Expects block not to throw an 113 | error. 114 | * __ifError(value)__ - Tests if value is not a false value, throws if it is a 115 | true value. Useful when testing the first argument, error in callbacks. 116 | 117 | Nodeunit also provides the following functions within tests: 118 | 119 | * __expect(amount)__ - Specify how many assertions are expected to run within a 120 | test. Very useful for ensuring that all your callbacks and assertions are 121 | run. 122 | * __done()__ - Finish the current test function, and move on to the next. ALL 123 | tests should call this! 124 | 125 | Nodeunit aims to be simple and easy to learn. This is achieved through using 126 | existing structures (such as node.js modules) to maximum effect, and reducing 127 | the API where possible, to make it easier to digest. 128 | 129 | Tests are simply exported from a module, but they are still run in the order 130 | they are defined. 131 | 132 | __Note:__ Users of old nodeunit versions may remember using `ok`, `equals` and 133 | `same` in the style of qunit, instead of the assert functions above. These 134 | functions still exist for backwards compatibility, and are simply aliases to 135 | their assert module counterparts. 136 | 137 | 138 | Asynchronous Testing 139 | -------------------- 140 | 141 | When testing asynchronous code, there are a number of sharp edges to watch out 142 | for. Thankfully, nodeunit is designed to help you avoid as many of these 143 | pitfalls as possible. For the most part, testing asynchronous code in nodeunit 144 | _just works_. 145 | 146 | 147 | ### Tests run in series 148 | 149 | While running tests in parallel seems like a good idea for speeding up your 150 | test suite, in practice I've found it means writing much more complicated 151 | tests. Because of node's module cache, running tests in parallel means mocking 152 | and stubbing is pretty much impossible. One of the nicest things about testing 153 | in javascript is the ease of doing stubs: 154 | 155 | var _readFile = fs.readFile; 156 | fs.readFile = function(path, callback) { 157 | // it's a stub! 158 | }; 159 | // test function that uses fs.readFile 160 | 161 | // we're done 162 | fs.readFile = _readFile; 163 | 164 | You cannot do this when running tests in parallel. In order to keep testing as 165 | simple as possible, nodeunit avoids it. Thankfully, most unit-test suites run 166 | fast anyway. 167 | 168 | 169 | ### Explicit ending of tests 170 | 171 | When testing async code it's important that tests end at the correct point, not 172 | just after a given number of assertions. Otherwise your tests can run short, 173 | ending before all assertions have completed. It's important to detect too 174 | many assertions as well as too few. Combining explicit ending of tests with 175 | an expected number of assertions helps to avoid false test passes, so be sure 176 | to use the `test.expect()` method at the start of your test functions, and 177 | `test.done()` when finished. 178 | 179 | 180 | Groups, setUp and tearDown 181 | -------------------------- 182 | 183 | Nodeunit allows the nesting of test functions: 184 | 185 | exports.test1 = function (test) { 186 | ... 187 | } 188 | 189 | exports.group = { 190 | test2: function (test) { 191 | ... 192 | }, 193 | test3: function (test) { 194 | ... 195 | } 196 | } 197 | 198 | This would be run as: 199 | 200 | test1 201 | group - test2 202 | group - test3 203 | 204 | Using these groups, Nodeunit allows you to define a `setUp` function, which is 205 | run before each test, and a `tearDown` function, which is run after each test 206 | calls `test.done()`: 207 | 208 | module.exports = { 209 | setUp: function (callback) { 210 | this.foo = 'bar'; 211 | callback(); 212 | }, 213 | tearDown: function (callback) { 214 | // clean up 215 | callback(); 216 | }, 217 | test1: function (test) { 218 | test.equals(this.foo, 'bar'); 219 | test.done(); 220 | } 221 | }; 222 | 223 | In this way, it's possible to have multiple groups of tests in a module, each 224 | group with its own setUp and tearDown functions. 225 | 226 | 227 | Running Tests 228 | ------------- 229 | 230 | Nodeunit comes with a basic command-line test runner, which can be installed 231 | using `sudo make install`. Example usage: 232 | 233 | nodeunit testmodule1.js testfolder [...] 234 | 235 | If no entry file specified, `test` defaults. 236 | 237 | The default test reporter uses color output, because I think that's more fun :) I 238 | intend to add a no-color option in future. To give you a feeling of the fun you'll 239 | be having writing tests, lets fix the example at the start of the README: 240 | 241 | 242 | 243 | Ahhh, Doesn't that feel better? 244 | 245 | When using the included test runner, it will exit using the failed number of 246 | assertions as the exit code. This means it exits with 0 when all tests pass. 247 | 248 | 249 | ### Command-line Options 250 | 251 | * __--reporter FILE__ - you can set the test reporter to a custom module or 252 | on of the modules in nodeunit/lib/reporters, when omitted, the default test runner 253 | is used. 254 | * __--list-reporters__ - list available built-in reporters. 255 | * __--config FILE__ - load config options from a JSON file, allows 256 | the customisation of color schemes for the default test reporter etc. See 257 | bin/nodeunit.json for current available options. 258 | * __-t testName__ - run specific test only. 259 | * __-f fullTestName__ - run specific test only. fullTestName is built so: "outerGroup - .. - innerGroup - testName". 260 | * __--version__ or __-v__ - report nodeunit version 261 | * __--help__ - show nodeunit help 262 | 263 | 264 | Running tests in the browser 265 | ---------------------------- 266 | 267 | Nodeunit tests can also be run inside the browser. For example usage, see 268 | the examples/browser folder. The basic syntax is as follows: 269 | 270 | __test.html__ 271 | 272 | 273 | 274 | Example Test Suite 275 | 276 | 277 | 278 | 279 | 280 | 281 |

Example Test Suite

282 | 288 | 289 | 290 | 291 | Here, `suite1` and `suite2` are just object literals containing test functions 292 | or groups, as would be returned if you did `require('test-suite')` in node.js: 293 | 294 | __suite1.js__ 295 | 296 | this.suite1 = { 297 | 'example test': function (test) { 298 | test.ok(true, 'everything is ok'); 299 | test.done(); 300 | } 301 | }; 302 | 303 | If you wish to use a commonjs format for your test suites (using exports), it is 304 | up to you to define the commonjs tools for the browser. There are a number of 305 | alternatives and it's important it fits with your existing code, which is 306 | why nodeunit does not currently provide this out of the box. 307 | 308 | In the example above, the tests will run when the page is loaded. 309 | 310 | The browser-version of nodeunit.js is created in dist/browser when you do, `make 311 | browser`. You'll need [UglifyJS](https://github.com/mishoo/UglifyJS) installed in 312 | order for it to automatically create nodeunit.min.js. 313 | 314 | 315 | Adding nodeunit to Your Projects 316 | -------------------------------- 317 | 318 | If you don't want people to have to install the nodeunit command-line tool, 319 | you'll want to create a script that runs the tests for your project with the 320 | correct require paths set up. Here's an example test script, that assumes you 321 | have nodeunit in a suitably located node_modules directory. 322 | 323 | #!/usr/bin/env node 324 | var reporter = require('nodeunit').reporters.default; 325 | reporter.run(['test']); 326 | 327 | If you're using git, you might find it useful to include nodeunit as a 328 | submodule. Using submodules makes it easy for developers to download nodeunit 329 | and run your test suite, without cluttering up your repository with 330 | the source code. To add nodeunit as a git submodule do the following: 331 | 332 | git submodule add git://github.com/caolan/nodeunit.git node_modules/nodeunit 333 | 334 | This will add nodeunit to the node_modules folder of your project. Now, when 335 | cloning the repository, nodeunit can be downloaded by doing the following: 336 | 337 | git submodule init 338 | git submodule update 339 | 340 | Let's update the test script above with a helpful hint on how to get nodeunit, 341 | if it's missing: 342 | 343 | #!/usr/bin/env node 344 | try { 345 | var reporter = require('nodeunit').reporters.default; 346 | } 347 | catch(e) { 348 | console.log("Cannot find nodeunit module."); 349 | console.log("You can download submodules for this project by doing:"); 350 | console.log(""); 351 | console.log(" git submodule init"); 352 | console.log(" git submodule update"); 353 | console.log(""); 354 | process.exit(); 355 | } 356 | 357 | process.chdir(__dirname); 358 | reporter.run(['test']); 359 | 360 | Now if someone attempts to run your test suite without nodeunit installed they 361 | will be prompted to download the submodules for your project. 362 | 363 | 364 | Built-in Test Reporters 365 | ----------------------- 366 | 367 | * __default__ - The standard reporter seen in the nodeunit screenshots 368 | * __minimal__ - Pretty, minimal output, shows errors and progress only 369 | * __html__ - Outputs a HTML report to stdout 370 | * __junit__ - Creates jUnit compatible XML reports, which can be used with 371 | continuous integration tools such as [Hudson](http://hudson-ci.org/). 372 | * __machineout__ - Simple reporter for machine analysis. There is 373 | [nodeunit.vim](https://github.com/lambdalisue/nodeunit.vim) which is useful for TDD on VIM. 374 | 375 | 376 | Writing a Test Reporter 377 | --------------------- 378 | 379 | Nodeunit exports runTest(fn, options), runModule(mod, options) and 380 | runFiles(paths, options). You'll most likely want to run test suites from 381 | files, which can be done using the latter function. The _options_ argument can 382 | contain callbacks which run during testing. Nodeunit provides the following 383 | callbacks: 384 | 385 | * __moduleStart(name)__ - called before a module is tested 386 | * __moduleDone(name, assertions)__ - called once all test functions within the 387 | module have completed (see assertions object reference below) 388 | ALL tests within the module 389 | * __testStart(name)__ - called before a test function is run 390 | * __testReady(test)__ - called before a test function is run with the test object that will be passed to the test function 391 | * __testDone(name, assertions)__ - called once a test function has completed 392 | (by calling test.done()) 393 | * __log(assertion)__ - called whenever an assertion is made (see assertion 394 | object reference below) 395 | * __done(assertions)__ - called after all tests/modules are complete 396 | 397 | The __assertion__ object: 398 | 399 | * __passed()__ - did the assertion pass? 400 | * __failed()__ - did the assertion fail? 401 | * __error__ - the AssertionError if the assertion failed 402 | * __method__ - the nodeunit assertion method used (ok, same, equals...) 403 | * __message__ - the message the assertion method was called with (optional) 404 | 405 | The __assertionList__ object: 406 | 407 | * An array-like object with the following new attributes: 408 | * __failures()__ - the number of assertions which failed 409 | * __duration__ - the time taken for the test to complete in msecs 410 | 411 | For a reference implementation of a test reporter, see lib/reporters/default.js in 412 | the nodeunit project directory. 413 | 414 | 415 | Sandbox utility 416 | --------------- 417 | 418 | This is a function which evaluates JavaScript files in a sandbox and returns the 419 | context. The sandbox function can be used for testing client-side code or private 420 | un-exported functions within a module. 421 | 422 | var sandbox = require('nodeunit').utils.sandbox; 423 | var example = sandbox('example.js'); 424 | 425 | __sandbox(files, sandbox)__ - Evaluates JavaScript files in a sandbox, returning 426 | the context. The first argument can either be a single filename or an array of 427 | filenames. If multiple filenames are given their contents are concatenated before 428 | evaluation. The second argument is an optional context to use for the sandbox. 429 | 430 | Note: When working with the sandbox if your script depends on outside sources 431 | (i.e. using `require`) then you will want to pass that into the optional 432 | context when setting up the sandbox. 433 | 434 | var sandbox = require('nodeunit').utils.sandbox; 435 | // pass in some node globals 436 | var box_globals = { 437 | // Passing module.exports into the sandbox will give your code access to it. 438 | module: {exports: exports}, 439 | // Passing require into the sandbox will give your code access to use it AND 440 | // will share the cache with modules already required from outside the sandbox. 441 | require: require, 442 | // Passing console into the sandbox will give your code access to it 443 | console: console 444 | }; 445 | var example = sandbox('example.js', box_globals); 446 | 447 | 448 | Running the nodeunit Tests 449 | -------------------------- 450 | 451 | The tests for nodeunit are written using nodeunit itself as the test framework. 452 | However, the module test-base.js first does some basic tests using the assert 453 | module to ensure that test functions are actually run, and a basic level of 454 | nodeunit functionality is available. 455 | 456 | To run the nodeunit tests do: 457 | 458 | make test 459 | 460 | __Note:__ There was a bug in node v0.2.0 causing the tests to hang, upgrading 461 | to v0.2.1 fixes this. 462 | 463 | 464 | __machineout__ reporter 465 | ---------------------------------------------- 466 | 467 | The default reporter is readable for human but not for machine analysis. 468 | When you want to analyze the output of nodeunit, use __machineout__ reporter and you will get 469 | 470 | 471 | 472 | 473 | nodeunit with vim 474 | ---------------------------------- 475 | There is [nodeunit.vim](https://github.com/lambdalisue/nodeunit.vim) so you can use 476 | nodeunit with VIM. 477 | 478 | That compiler uses __machineout__ reporter and it is useful to use 479 | with [vim-makegreen](https://github.com/reinh/vim-makegreen). 480 | 481 | 482 | 483 | Contributing 484 | ------------ 485 | 486 | Contributions to the project are most welcome, so feel free to fork and improve. 487 | When submitting a pull request, please run `make lint` first to ensure 488 | we're following a consistent coding style. 489 | --------------------------------------------------------------------------------