├── .travis.yml ├── fixtures ├── failing-test.js ├── unexpected-exception-test.js ├── unexpected-rejection-test.js ├── unhandled-rejection-test.js ├── unhandled-exception-test.js ├── failure-in-cb.js ├── async-await-test.js └── end-in-cb.js ├── .editorconfig ├── package.json ├── license ├── .gitignore ├── examples └── example.js ├── readme.md ├── test.js └── index.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | sudo: false 5 | -------------------------------------------------------------------------------- /fixtures/failing-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const test = require(".."); 3 | 4 | test("this test will fail", function* (t) { 5 | t.equal("not ok", "ok"); 6 | }); 7 | -------------------------------------------------------------------------------- /fixtures/unexpected-exception-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const test = require(".."); 3 | 4 | test("this test will fail", () => { 5 | throw new Error("unexpected"); 6 | }); 7 | -------------------------------------------------------------------------------- /fixtures/unexpected-rejection-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const test = require(".."); 3 | 4 | test("this test will fail", function* () { 5 | throw new Error("unexpected"); 6 | }); 7 | -------------------------------------------------------------------------------- /fixtures/unhandled-rejection-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const test = require(".."); 3 | 4 | test("this test will fail", () => { 5 | Promise.reject(new Error("unhandled")); 6 | }); 7 | -------------------------------------------------------------------------------- /fixtures/unhandled-exception-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const test = require(".."); 3 | 4 | test("this test will fail", () => { 5 | setTimeout(() => { 6 | throw new Error("unhandled"); 7 | }, 1); 8 | }); 9 | -------------------------------------------------------------------------------- /fixtures/failure-in-cb.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const test = require(".."); 3 | 4 | test("this test will fail", (t) => { 5 | t.deferred(); 6 | setTimeout(() => { 7 | t.equal(true, false); 8 | t.end(); 9 | }, 2); 10 | }); 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [*.js] 9 | indent_style = tab 10 | 11 | [*.json] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /fixtures/async-await-test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const test = require(".."); 4 | const sleep = require("sleep-promise"); 5 | 6 | test("this test will successfully pass", async (t) => { 7 | await sleep(10); 8 | const a = await Promise.resolve(42); 9 | t.equal(a, 42); 10 | }); 11 | 12 | test("also this test will successfully pass", async (t) => { 13 | await sleep(10); 14 | const a = await Promise.resolve(43); 15 | t.equal(a, 43); 16 | }); 17 | -------------------------------------------------------------------------------- /fixtures/end-in-cb.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const test = require(".."); 3 | 4 | test("first test", (t) => { 5 | t.deferred(); 6 | process.stdout.write("first test start\n"); 7 | setTimeout(() => { 8 | t.equal(true, true); 9 | t.end(); 10 | process.stdout.write("first test end\n"); 11 | }, 2); 12 | }); 13 | 14 | test("second test", (t) => { 15 | t.deferred(); 16 | process.stdout.write("second test start\n"); 17 | setTimeout(() => { 18 | t.equal(true, true); 19 | t.end(); 20 | process.stdout.write("second test end\n"); 21 | }, 2); 22 | }); 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tape-async", 3 | "description": "A lite wrapper around tape to simplify async testing.", 4 | "repository": "parro-it/tape-async", 5 | "version": "2.3.0", 6 | "engines": { 7 | "node": ">=4.1" 8 | }, 9 | "license": "MIT", 10 | "dependencies": { 11 | "co": "^4.6.0", 12 | "is-generator": "^1.0.2", 13 | "is-promise": "^2.1.0", 14 | "tape": "^5.0.1" 15 | }, 16 | "author": "Andrea Parodi", 17 | "scripts": { 18 | "test": "node --trace-warnings test && prettier --check *.js", 19 | "format": "prettier --write *.js" 20 | }, 21 | "devDependencies": { 22 | "execa": "^0.8.0", 23 | "prettier": "2.1.2", 24 | "sleep-promise": "^2.0.0" 25 | }, 26 | "files": [ 27 | "index.js" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Andrea Parodi 3 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 21 | OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: http://goel.io/joe 2 | 3 | #####=== SublimeText ===##### 4 | # cache files for sublime text 5 | *.tmlanguage.cache 6 | *.tmPreferences.cache 7 | *.stTheme.cache 8 | 9 | # workspace files are user-specific 10 | *.sublime-workspace 11 | 12 | # project files should be checked into the repository, unless a significant 13 | # proportion of contributors will probably not be using SublimeText 14 | # *.sublime-project 15 | 16 | # sftp configuration file 17 | sftp-config.json 18 | 19 | #####=== Node ===##### 20 | 21 | # Logs 22 | logs 23 | *.log 24 | 25 | # Runtime data 26 | pids 27 | *.pid 28 | *.seed 29 | 30 | # Directory for instrumented libs generated by jscoverage/JSCover 31 | lib-cov 32 | 33 | # Coverage directory used by tools like istanbul 34 | coverage 35 | 36 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 37 | .grunt 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (http://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directory 46 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 47 | node_modules 48 | 49 | # Debug log from npm 50 | npm-debug.log 51 | 52 | 53 | private 54 | init 55 | yarn.lock 56 | -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | import test from "tape-async"; 2 | 3 | // use a library that return a promise to simplify test code 4 | import superagentPromise from "superagent-promise"; 5 | 6 | // ...other setup stuff 7 | 8 | test( 9 | "use the first test in the file to setup the test suite", 10 | // here since `tape-async` manage the call to t.end for you, 11 | // you can simplify the test to a single function. 12 | recreateDatabase 13 | ); 14 | 15 | test(`getUsersRoles: /users/id/roles returns success when franchisor owner deletes her own roles`, async (assert) => { 16 | const newToken = await helper.getTokenForUser(requesteeUserId); 17 | const result = await superagentPromise 18 | .post(testData.url + `users/` + requesteeUserId + `/roles`) 19 | .set("token", newToken) 20 | .send({ franchiseeId: null, franchisorId: "f1a", roles: [] }) 21 | .end(); 22 | 23 | const json = JSON.parse(result.text); 24 | assert.equals(json.errorCode, ResponseErrorCode.noError); 25 | assert.false(json.data); 26 | const dbResult = await db.runQuery( 27 | `select * from dbo.users_roles where users_id = '` + requesteeUserId + `'` 28 | ); 29 | assert.equals(dbResult.rows.length, 0); 30 | // assert.end(); no need to end the test. 31 | 32 | // if somethink throws in this function, tape-async automatically catch the error. 33 | }); 34 | 35 | // ... lots more tests ... 36 | 37 | test("use the last test in the file to tearDown the test suite", // you can simplify the test to a single expression. // Here since `tape-async` just expect a promise as return value of your test method 38 | () => startedServer.stop()); 39 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # tape-async 2 | 3 | [![Greenkeeper badge](https://badges.greenkeeper.io/parro-it/tape-async.svg)](https://greenkeeper.io/) 4 | 5 | > A lite wrapper around [tape](https://github.com/substack/tape) to simplify async testing. 6 | 7 | [![Travis Build Status](https://img.shields.io/travis/parro-it/tape-async.svg)](http://travis-ci.org/parro-it/tape-async) 8 | [![NPM module](https://img.shields.io/npm/v/tape-async.svg)](https://npmjs.org/package/tape-async) 9 | [![NPM downloads](https://img.shields.io/npm/dt/tape-async.svg)](https://npmjs.org/package/tape-async) 10 | 11 | # Installation 12 | 13 | ```bash 14 | npm install -D tape-async 15 | ``` 16 | 17 | # Usage 18 | 19 | ## Use with `async-await` 20 | 21 | ```js 22 | const test = require("tape-async"); 23 | const sleep = require("sleep-promise"); 24 | 25 | test("this test will successfully pass", async (t) => { 26 | await sleep(100); 27 | const a = await Promise.resolve(42); 28 | t.equal(a, 42); 29 | }); 30 | ``` 31 | 32 | `tape-async` supports async-await syntax. 33 | You are in charge of transpiling your test code. 34 | 35 | ## Use with `generators` 36 | 37 | ```js 38 | const test = require("tape-async"); 39 | const sleep = require("sleep-promise"); 40 | 41 | test("this test will successfully pass", function* (t) { 42 | const result = yield Promise.resolve(42); 43 | t.equal(result, 42); 44 | }); 45 | ``` 46 | 47 | `tape-async` supports generators test to handle async code. 48 | They run using [co](https://github.com/tj/co). 49 | 50 | ## It catches unhandled errors 51 | 52 | ```js 53 | const test = require("tape-async"); 54 | test("this test will fail", () => { 55 | setTimeout(() => { 56 | throw new Error("unhandled"); 57 | }, 100); 58 | }); 59 | ``` 60 | 61 | Unhandled errors in your tests are automatically covered. 62 | Test suite fails with a generic error message and a stack trace. 63 | 64 | ## It catches unhandled `Promise` rejections 65 | 66 | ```js 67 | const test = require("tape-async"); 68 | test("this test will fail", () => { 69 | Promise.reject(new Error("unhandled")); 70 | }); 71 | ``` 72 | 73 | Uncatched Promise rejections in your tests are automatically covered. 74 | Test suite fails with a generic error message and a stack trace. 75 | 76 | ## It supports every [tape](https://github.com/substack/tape) features. 77 | 78 | ```js 79 | const test = require("tape-async"); 80 | test.skip("this test will be skipped", () => {}); 81 | 82 | test.only("this test will be the only one", (t) => { 83 | t.equal(42, 42); 84 | t.end(); 85 | }); 86 | ``` 87 | 88 | Since this is only a tiny wrapper around `tape`, you can 89 | use every feature you are used to. 90 | 91 | # Related projects 92 | 93 | - [tape](https://github.com/substack/tape) - tap-producing test harness for node and browsers. 94 | - [tape-await](https://ghub.io/tape-await) - another async tape implementation. 95 | 96 | # License 97 | 98 | The MIT License (MIT) 99 | 100 | Copyright (c) 2016 Andrea Parodi 101 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const execa = require("execa"); 3 | const test = require("."); 4 | 5 | test("support async await functions", function* (t) { 6 | const result = yield execa("node", ["fixtures/async-await-test"]); 7 | t.equal(result.stdout.split("\n")[2], "ok 1 should be strictly equal"); 8 | }); 9 | 10 | test("test.only is a function", (t) => { 11 | t.equal(typeof test.only, "function"); 12 | t.end(); 13 | }); 14 | test("support normal cb termination", function* (t) { 15 | try { 16 | yield execa("node", ["fixtures/failing-test"]); 17 | t.fail("Failure expected"); 18 | } catch (err) { 19 | t.equal(err.stdout.split("\n")[2], "not ok 1 should be strictly equal"); 20 | } 21 | }); 22 | 23 | test("log unhandled exceptions", function* (t) { 24 | try { 25 | yield execa("node", ["fixtures/unhandled-exception-test"]); 26 | t.fail("Failure expected"); 27 | } catch (err) { 28 | t.equal(err.stderr.slice(0, 30), "\nUnhandled exception occurred."); 29 | } 30 | }); 31 | 32 | test("log unhandled rejection", function* (t) { 33 | try { 34 | yield execa("node", ["fixtures/unhandled-rejection-test"]); 35 | t.fail("Failure expected"); 36 | } catch (err) { 37 | t.equal(err.stderr.slice(0, 30), "\nUnhandled rejection occurred."); 38 | } 39 | }); 40 | 41 | test("log unexpected rejections", function* (t) { 42 | try { 43 | yield execa("node", ["fixtures/unexpected-rejection-test"]); 44 | t.fail("Failure expected"); 45 | } catch (err) { 46 | t.equal(err.stdout.split("\n")[2], "not ok 1 Error: unexpected"); 47 | } 48 | }); 49 | 50 | test("log unexpected exceptions", function* (t) { 51 | try { 52 | yield execa("node", ["fixtures/unexpected-exception-test"]); 53 | t.fail("Failure expected"); 54 | } catch (err) { 55 | t.equal(err.stdout.split("\n")[2], "not ok 1 Error: unexpected"); 56 | } 57 | }); 58 | 59 | test("log test failures", function* (t) { 60 | try { 61 | yield execa("node", ["fixtures/failing-test"]); 62 | t.fail("Failure expected"); 63 | } catch (err) { 64 | t.equal(err.stdout.split("\n")[2], "not ok 1 should be strictly equal"); 65 | } 66 | }); 67 | 68 | test("support tests end in cb", function* (t) { 69 | const result = yield execa("node", ["fixtures/end-in-cb"]); 70 | t.equal( 71 | result.stdout, 72 | `TAP version 13 73 | # first test 74 | first test start 75 | ok 1 should be strictly equal 76 | first test end 77 | # second test 78 | second test start 79 | ok 2 should be strictly equal 80 | second test end 81 | 82 | 1..2 83 | # tests 2 84 | # pass 2 85 | 86 | # ok 87 | ` 88 | ); 89 | }); 90 | 91 | test("support generators with plan", function* (t) { 92 | t.plan(1); 93 | const result = yield Promise.resolve(42); 94 | t.equal(result, 42); 95 | }); 96 | 97 | test("support sync function with plan", (t) => { 98 | t.plan(1); 99 | const result = 42; 100 | t.equal(result, 42); 101 | }); 102 | 103 | test("support generators without explicit end", function* (t) { 104 | const result = yield Promise.resolve(42); 105 | t.equal(result, 42); 106 | }); 107 | 108 | test("support generators with explicit end", function* (t) { 109 | const result = yield Promise.resolve(42); 110 | t.equal(result, 42); 111 | t.end(); 112 | }); 113 | 114 | test("support sync function", (t) => { 115 | const result = 42; 116 | t.equal(result, 42); 117 | t.end(); 118 | }); 119 | 120 | test("support async function", (t) => { 121 | const result = 42; 122 | process.nextTick(() => { 123 | t.equal(result, 42); 124 | t.end(); 125 | }); 126 | }); 127 | 128 | test("support async function with plan", (t) => { 129 | const result = 42; 130 | t.plan(1); 131 | process.nextTick(() => { 132 | t.equal(result, 42); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const tape = require("tape"); 4 | const isGenerator = require("is-generator"); 5 | const isPromise = require("is-promise"); 6 | const defined = require("defined"); 7 | const bind = require("function-bind"); 8 | const has = require("has"); 9 | const isEnumerable = bind.call( 10 | Function.call, 11 | Object.prototype.propertyIsEnumerable 12 | ); 13 | const co = require("co"); 14 | 15 | module.exports = tape; 16 | 17 | process.on("uncaughtException", (err) => { 18 | process.stderr.write( 19 | `\nUnhandled exception occurred. One of your test may have failed silently.\n${err.stack}\n` 20 | ); 21 | process.exit(-1); 22 | }); 23 | 24 | process.on("unhandledRejection", (err) => { 25 | process.stderr.write( 26 | `\nUnhandled rejection occurred. One of your test may have failed silently.\n${err.stack}\n` 27 | ); 28 | process.exit(-1); 29 | }); 30 | 31 | // Maintain tape@1 compatibility 32 | tape.Test.prototype._end = tape.Test.prototype._end || tape.Test.prototype.end; 33 | 34 | tape.Test.prototype.deferred = function () { 35 | this._deferred = true; 36 | }; 37 | 38 | tape.Test.prototype.run = function () { 39 | if (!this._cb || this._skip) { 40 | return this._end(); 41 | } 42 | 43 | this.emit("prerun"); 44 | this._deferred = false; 45 | 46 | const success = () => 47 | setImmediate(() => { 48 | this._end(); 49 | this.emit("run"); 50 | }); 51 | 52 | const failure = (err) => { 53 | this.error(err); 54 | return this._end(); 55 | }; 56 | 57 | const testFn = this._cb; 58 | 59 | let result; 60 | 61 | try { 62 | result = testFn(this); 63 | } catch (err) { 64 | return failure(err); 65 | } 66 | 67 | if (isGenerator(result)) { 68 | return co(function* () { 69 | yield result; 70 | }) 71 | .then(success) 72 | .catch(failure); 73 | } 74 | 75 | if (isPromise(result)) { 76 | return result.then(success).catch(failure); 77 | } 78 | 79 | if (!this._deferred) { 80 | this.emit("run"); 81 | } 82 | 83 | return true; 84 | }; 85 | 86 | tape.Test.prototype.throws = async function (fn, expected, msg, extra) { 87 | var caught = undefined; 88 | var message = undefined; 89 | var passed = undefined; 90 | 91 | if (typeof expected === "string") { 92 | msg = expected; 93 | expected = undefined; 94 | } 95 | 96 | try { 97 | await fn(); 98 | } catch (err) { 99 | caught = { error: err }; 100 | if ( 101 | err !== null && 102 | (!isEnumerable(err, "message") || !has(err, "message")) 103 | ) { 104 | message = err.message; 105 | delete err.message; 106 | err.message = message; 107 | } 108 | } 109 | 110 | passed = caught; 111 | 112 | if (expected instanceof RegExp) { 113 | passed = expected.test(caught && caught.error); 114 | expected = String(expected); 115 | } 116 | 117 | if (typeof expected === "function" && caught) { 118 | passed = caught.error instanceof expected; 119 | caught.error = caught.error.constructor; 120 | } 121 | 122 | this._assert(typeof fn === "function" && passed, { 123 | message: defined(msg, "should throw"), 124 | operator: "throws", 125 | actual: caught && caught.error, 126 | expected: expected, 127 | error: !passed && caught && caught.error, 128 | extra: extra, 129 | }); 130 | }; 131 | 132 | tape.Test.prototype.doesNotThrow = async function (fn, expected, msg, extra) { 133 | var caught = undefined; 134 | 135 | if (typeof expected === "string") { 136 | msg = expected; 137 | expected = undefined; 138 | } 139 | 140 | try { 141 | await fn(); 142 | } catch (err) { 143 | caught = { error: err }; 144 | } 145 | this._assert(!caught, { 146 | message: defined(msg, "should not throw"), 147 | operator: "throws", 148 | actual: caught && caught.error, 149 | expected: expected, 150 | error: caught && caught.error, 151 | extra: extra, 152 | }); 153 | }; 154 | --------------------------------------------------------------------------------