{"id":13825705,"url":"https://github.com/testdouble/teenytest","last_synced_at":"2026-02-03T13:10:31.775Z","repository":{"id":3851357,"uuid":"51117131","full_name":"testdouble/teenytest","owner":"testdouble","description":"A very simple, zero-config test runner for Node.js","archived":false,"fork":false,"pushed_at":"2023-07-18T22:04:51.000Z","size":549,"stargazers_count":96,"open_issues_count":15,"forks_count":14,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-08-09T17:21:48.584Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/testdouble.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-02-05T00:57:49.000Z","updated_at":"2025-03-30T00:42:31.000Z","dependencies_parsed_at":"2024-01-18T04:06:58.123Z","dependency_job_id":"b1867fec-7e32-4805-b89e-9dafee4861bd","html_url":"https://github.com/testdouble/teenytest","commit_stats":{"total_commits":272,"total_committers":14,"mean_commits":"19.428571428571427","dds":0.2904411764705882,"last_synced_commit":"aad5950939ec208957dcdde4f1c7f39807891e18"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/testdouble/teenytest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/testdouble%2Fteenytest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/testdouble%2Fteenytest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/testdouble%2Fteenytest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/testdouble%2Fteenytest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/testdouble","download_url":"https://codeload.github.com/testdouble/teenytest/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/testdouble%2Fteenytest/sbom","scorecard":{"id":875082,"data":{"date":"2025-08-11","repo":{"name":"github.com/testdouble/teenytest","commit":"aad5950939ec208957dcdde4f1c7f39807891e18"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.5,"checks":[{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/node.js.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":3,"reason":"Found 5/13 approved changesets -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":3,"reason":"dependency not pinned by hash detected -- score normalized to 3","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node.js.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/testdouble/teenytest/node.js.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/node.js.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/testdouble/teenytest/node.js.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 22 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":6,"reason":"4 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-24T05:40:14.459Z","repository_id":3851357,"created_at":"2025-08-24T05:40:14.459Z","updated_at":"2025-08-24T05:40:14.459Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29046503,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T10:09:22.136Z","status":"ssl_error","status_checked_at":"2026-02-03T10:09:16.814Z","response_time":96,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T09:01:25.614Z","updated_at":"2026-02-03T13:10:31.748Z","avatar_url":"https://github.com/testdouble.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# teenytest\n\n[![Build Status](https://travis-ci.org/testdouble/teenytest.svg?branch=main)](https://travis-ci.org/testdouble/teenytest)\n\nA test runner so tiny, you have to squint to see it!\n\nIf you put test scripts in `test/lib`, then teenytest's CLI will run them with\nzero public-API and zero configuration. That's pretty teeny, by the sound of it!\n\n## Usage\n\n```\nnpm i --save-dev teenytest\n```\n\n## Using the CLI\n\nteenytest includes a CLI, which can be run ad hoc with:\n\n```\n$(npm bin)/teenytest\n```\n\nBy default, the CLI will assume your tests are in `\"test/lib/**/*.js\"` and it\nwill search for a test helper in `\"test/helper.js\"`. You can specify either or\nboth of these by providing arguments, as well:\n\n```\n$(npm bin)/teenytest \"test/lib/**/*.js\" --helper \"test/helper.js\"\n```\n\n### As an npm script\n\nWe prefer including our script in the `scripts` section of our package.json:\n\n``` json\n\"scripts\": {\n  \"test\": \"teenytest test/lib/**/*.test.js --helper test/helper.js\"\n}\n```\n\nWith that configuration above, you could run all your tests with:\n\n```\nnpm test\n```\n\nIf you want to run a single test, you can just tack an additional path or glob\nat the end without looking at how teenytest is configured in the package.json:\n\n```\nnpm test path/to/my.test.js\n```\n\nThe above will ignore the glob embedded in the npm script and only run\n`path/to/my.test.js`.\n\n## Writing tests\n\n### Test styles\n\nOur tests are just Node.js modules. Rather than specify your tests via a fancy\ntesting API, whatever your test modules sets onto `module.exports` will determine\nhow teenytest will run the test. Modules can export either a single test\nfunction or an object of (potentially nested) test functions.\n\nRead on for examples.\n\n#### Single function tests\n\nIf you export a function, that function will be run as a single test. Note\nthat you'll get better test output if you name the function.\n\n``` javascript\nvar assert = require('assert')\n\nmodule.exports = function blueIsRed(){\n  assert.equal('blue', 'red')\n}\n```\n\nThe above test will fail (since `'blue'` doesn't equal `'red'`) with output like:\n\n```\nTAP version 13\n1..1\nnot ok 1 - \"blueIsRed\" - test #1 in `test/lib/single-function.js`\n  ---\n  AssertionError: 'blue' == 'red'\n    at blueIsRed (teenytest/example/simple-node/test/lib/single-function.js:4:10)\n    at teenytest/index.js:47:9\n    ...\n    at Module._compile (module.js:434:26)\n  ...\n```\n\n#### Exporting an object of test functions\n\nIf you export an object, you can include as many tests as you like. You can also\nimplement any or all of the four supported test hooks: `beforeEach`, `afterEach`,\n`beforeAll`, and `afterAll`.\n\nA file with two tests and all the hooks implemented could look like:\n\n``` javascript\nvar assert = require('assert')\n\nmodule.exports = {\n  beforeAll: function() { console.log(\"I'll run once before both tests\") },\n  beforeEach: function() { console.log(\"I'll run twice - once before each test\") },\n\n  adds: function() { assert.equal(1 + 1, 2) },\n  subtracts: function() { assert.equal(4 - 2, 2) },\n\n  afterEach: function() { console.log(\"I'll run twice - once after each test\") },\n  afterAll: function() { console.log(\"I'll run once after both tests\") }\n}\n```\n\nThis will output what you might expect (be warned: using `console.log` in your\nactual tests will make teenytest's output unparseable by TAP reporters):\n\n```\nTAP version 13\n1..2\nI'll run once before both tests\nI'll run twice - once before each test\nok 1 - \"adds\" - test #1 in `test/lib/exporting-an-object.js`\nI'll run twice - once after each test\nI'll run twice - once before each test\nok 2 - \"subtracts\" - test #2 in `test/lib/exporting-an-object.js`\nI'll run twice - once after each test\nI'll run once after both tests\n```\n\n#### Nested tests\n\nNested tests are also supported, in which any object can contain any combination\nof hooks, test functions, and additional sub-test objects. This makes nested\nteenytest modules very similar to what's possible with \"BDD\"-like test libraries\n(in what are traditionally referred to as \"example groups\" by RSpec, Jasmine,\nand Mocha parlance).\n\nA common rationale for writing nested tests is to define one nested set of tests\nfor each public method on a subject, for better symmetry between the test and the\nsubject.\n\nLet's see an [example](example/simple-node/test/lib/dog-test.js). Given this test in\n`test/lib/dog-test.js`:\n\n``` js\nvar assert = require('assert')\nvar Dog = require('../../lib/dog')\n\nmodule.exports = {\n  beforeEach: function () {\n    this.subject = new Dog('Sam')\n  },\n  bark: {\n    once: function () {\n      assert.deepEqual(this.subject.bark(1), ['Woof #0'])\n    },\n    twice: function () {\n      assert.deepEqual(this.subject.bark(2), ['Woof #0', 'Woof #1'])\n    }\n  },\n  tag: {\n    frontSaysName: function () {\n      assert.equal(this.subject.tag('front'), 'Hi, I am Sam')\n    },\n    backSaysAddress: function () {\n      assert.equal(this.subject.tag('back'), 'And here is my address')\n    }\n  }\n}\n```\n\nYou'll get this output upon running `$ teenytest test/lib/dog-test.js`:\n\n```\nTAP version 13\n1..4\nok 1 - \"bark once\" - test #1 in `example/simple-node/test/lib/dog-test.js`\nok 2 - \"bark twice\" - test #2 in `example/simple-node/test/lib/dog-test.js`\nok 3 - \"tag frontSaysName\" - test #3 in `example/simple-node/test/lib/dog-test.js`\nok 4 - \"tag backSaysAddress\" - test #4 in `example/simple-node/test/lib/dog-test.js`\n```\n\n### Assertions\n\nOne thing you'll notice right away is that teenytest does not ship with its own\nassertion library. In teenytest, any test that throws an error will trigger a\ntest failure. To keep things simple, the examples in teenytest use Node's\nbuilt-in [assert module](https://nodejs.org/api/assert.html), but keep in mind\nthat it isn't intended for public consumption.\n\nIf you like the simplicity of the built-in assert, you might want to use its port\n[core-assert](https://github.com/sindresorhus/core-assert).\n[chai](https://github.com/chaijs/chai) is also a very popular choice.\n\n### Writing asynchronous tests\n\n#### With callbacks\n\nAny test hook or test function can also support asynchronous behavior via a\ncallback function. To indicate that a function is asynchronous, add a callback\nargument to the test method.\n\nFor instance, a synchronous test could:\n\n``` js\nmodule.exports = function() {\n  require('assert').equal(1+1, 2)\n}\n```\n\nBut an asynchronous test could specify a `done` argument and tell teenytest that\nthe test (or hook) is complete by invoking `done()`.\n\n``` js\nmodule.exports = function(done) {\n  process.nextTick(function(){\n    require('assert').equal(1+1, 2)\n    done()\n  })\n}\n```\n\nA test failure can be triggered by either throwing an uncaught exception (which\nteenytest will be listening for during each asynchronous step) or by passing an\n`Error` as the first argument to `done`.\n\n#### With promises\n\nIf you would prefer to return a promise to manage asynchronous tests, take a look\nat the [teenytest-promise](https://github.com/testdouble/teenytest-promise)\nplugin.\n\n## Test Helper \u0026 Global Hooks\n\nIn addition to defining before \u0026 after hooks on a per test file basis, teenytest\nalso supports a global test helper, which it will search for by default in\n`test/helper.js`, but can be configured with the `helperPath` configuration\noption in the API.\n\nAn example helper might look like this:\n\n``` javascript\n// make global things common across each test to save on per-test setup\nglobal.assert = require('assert')\n\nmodule.exports = {\n  beforeAll: function(){},\n  beforeEach: function(){},\n  afterEach: function(){},\n  afterAll: function(){}\n}\n```\n\nIn this case, the `beforeAll`/`afterAll` hooks will run only at the beginning\nand the end of the entire suite (whereas the same hooks exported from a single\ntest file will run before or after all the tests in that same file).  The\n`beforeEach`/`afterEach` hooks, meanwhile will run before and after each test\nin the entire suite.\n\n## Advanced CLI Usage\n\n### Configuration\n\nYou can configure teenytest via CLI arguments or as properties of a `teenytest`\nobject in your `package.json`. A full example follows:\n\n```\n$(npm bin)/teenytest \\\n  --helper test/support/helper.js \\\n  --timeout 3000 \\\n  --configurator config/teenytest.js \\\n  --plugin test/support/benchmark-plugin.js \\\n  --plugin teenytest-promise \\\\\n  \"lib/**/*.test.js\"\n```\n\nThe above is equivalent to the following `package.json` entry:\n\n``` json\n\"teenytest\": {\n  \"testLocator\": \"lib/**/*.test.js\",\n  \"helper\": \"test/support/helper.js\",\n  \"asyncTimeout\": 3000,\n  \"configurator\": \"config/teenytest.js\",\n  \"plugins\": [\n    \"test/support/benchmark-plugin.js\",\n    \"teenytest-promise\"\n  ]\n}\n```\n\nThese options are available:\n\n* **testLocator** - [Default: `\"test/lib/**/*.js\"`] - one or more globs which\nteenytest should use to search for tests. May be a string or an array of strings\n* **name** - [Default: `[]`] - one or more global name filters to be applied\nto all files matched by `testLocator`\n* **helper** - [Default: `\"test/helper.js\"`] - the location of your global test\nhelper file\n* **asyncTimeout** - [Default: `5000`] - the maximum timeout (in milliseconds) for any\ngiven test in your suite\n* **configurator** - [Default: `undefined`] - a `require`-able path which exports\na function that with parameters `(teenytest, cb)`. Configurator files may be used\nto run custom code just before the test runner executes the test suite, register\nor unregister plugins with functions provided by `teenytest.plugins`, and must\ninvoke the provided callback\n* **plugins** - [Default: `[]`] - an array of `require`-able paths which export\neither teenytest plugin objects or no-arg functions that return plugin objects\n\n### Specifying which test files to run\n\nIf you'd like to run tests from specific files, you can do that by passing\n`testLocator` as an unnamed option on the command line.\n\n```\nteenytest test/foo-test.js\n```\n\nMultiple path/glob options can be passed for `testLocator`. The following will\nrun all tests in `test/specific-foo-test.js` as well as any test file matching\nthe glob pattern `test/*-bar-test.js`.\n\n```\nteenytest test/single-foo-test.js test/*-bar-test.js\n```\n\n### Filtering which tests are run\n\nIf you'd like to just run one test from a file, you can do that, too!\n\n#### Locating by name\n\nIf you have a test in `test/foo-test.js` and it exports an object with functions\n`bar` and `baz`, you could tell teenytest to just run `baz` with:\n\n```\nteenytest test/foo-test.js#baz\n```\n\nThe `#` character will split the glob on the left from the name on the right.\n\nThis can even be used across multiple tests in a wildcard glob, allowing you to\nslice a CI build based on a particular concern, for instance, you could run all\naudit log tests across your project's modules so long as they name the test\nthe same thing (e.g. `teenytest test/**/*.js#audit`) to run all of them at once,\nwithout necessarily having to split that concern into its own set of\nfiles or directories.\n\n#### Locating by line number\n\nSuppose you have a test in `test/bar-test.js` and you want to run the test on\nline 14 (whether that's the line number where the function is declared, or just\nsome line inside the exported test function). You can run just that test with:\n\n```\nteenytest test/bar-test.js:14\n```\n\n#### Locating with multiple names or line numbers\n\nEach `testLocator` option can include one name or line number filter suffix.\nThe same glob may be passed multiple times with different suffixes to locate\ntests matching more than one filter:\n\n```\nteenytest \\\n  test/foo-test.js#red \\\n  test/foo-test.js#blue \\\n  test/bar-test.js:14 \\\n  test/bar-test.js:28\n```\n\nThe above will run tests named `red` and `blue` in the file `test/foo-test.js`\nand tests on lines 14 and 28 in the file `test/bar-test.js`.\n\n#### Locating with the `--name` option\n\nThe `--name` option may be used to specify a global name filter that will be\napplied to every `testLocator` in addition to any filter suffixes provided. The\nfollowing two commands would result in identical test runs:\n\n```\nteenytest \\\n  --name=red\n  test/foo.test.js\n  test/bar.test.js#blue\n  test/baz.test.js:14\n```\n\n```\nteenytest \\\n  test/foo.test.js\n  test/foo.test.js#red\n  test/bar.test.js#blue\n  test/bar.test.js#red\n  test/baz.test.js:14\n  test/baz.test.js#red\n```\n\n`--name` may be used multiple times to specify more than one global name\nfilter:\n\n```\nteenytest --name=red --name=blue test/foo.test.js\n```\n\n### Setting a timeout\n\nBy default, teenytest will allow 5 seconds for tests with asynchronous hooks or\ntest functions to run before failing the test with a timeout error. To change\nthis setting, set the `--timeout` flag in milliseconds:\n\n```\nteenytest --timeout 10000\n```\n\nThe above will set the timeout to 10 seconds.\n\n## Reporting\n\nteenytest's output is\n[TAP13](https://testanything.org/tap-version-13-specification.html)-compliant,\nso its output can be reported on and aggregated with numerous supported\ncontinuous integration \u0026 reporting tools.\n\n### Coverage with istanbul\n\nIf you're looking for code coverage, we recommend using\n[istanbul](https://github.com/gotwarlost/istanbul)'s CLI. To get started,\ninstall istanbul locally:\n\n```\nnpm i --save-dev istanbul\n```\n\nSuppose you're currently running your teeny tests with:\n\n```\n$(npm bin)/teenytest \"lib/**/*.test.js\" --helper \"test/unit-helper.js\"\n```\n\nYou can now generate a coverage report for the same test run with:\n\n```\n$(npm bin)/istanbul cover node_modules/teenytest/bin/teenytest -- \"lib/**/*.test.js\" --helper \"test/unit-helper.js\"\n```\n\nNote the use of `--` before the arguments intended for teenytest itself, which\nistanbul will forward along.\n\nYou could also set up both as [npm scripts](https://docs.npmjs.com/misc/scripts)\nso you could run either `npm test` and `npm run test:cover` by specifying them\nin your package.json:\n\n``` json\n\"scripts\": {\n  \"test\": \"teenytest \\\"lib/**/*.test.js\\\" --helper test/unit-helper.js\",\n  \"test:cover\": \"istanbul cover teenytest -- \\\"lib/**/*.test.js\\\" --helper test/unit-helper.js\"\n}\n```\n\n## Other good stuff\n\n### Building teenytest plugins\n\nMost of the runtime behavior in teenytest is implemented as plugins that\nwrap the functions, tests, and suites defined by the user. You can register\nyour own plugin like this:\n\n``` js\nteenytest.plugins.register({\n  name: 'pending',\n  interceptors: {\n    test: function (runTest, metadata, cb) {\n      runTest(function pendingTest(er, results) {\n        if (_.startsWith(metadata.name, 'pending') \u0026\u0026 results.passing) {\n          metadata.triggerFailure(new Error('Pending should not pass!'))\n        }\n        cb(er)\n      })\n    }\n  }\n})\n```\n\nThe above plugin will fail any tests whose name starts with \"pending\" but that\nactually passed. There are several types of plugins, but all of them follow the\nsame theme of wrapping the users' own defined functions and (often nested)\nsuites.\n\nThere are two things to keep in mind when designing a plugin: wrapper scopes and\nlifecycle events.\n\n#### Plugin wrapper scopes\n\nThere are three scopes of specificity each plugin can attach to: `userFunction`,\n`test`, and `suite`.\n\n##### userFunction wrappers\n\nA `userFunction` could be a hook like `beforeAll` or `afterEach` or an actual\ntest function. If your plugin should augment or observe the actual behavior of\nthe functions a user defines in their test listings, then you want to define a\nuserFunction plugin.\n\nFor example, a plugin below might be a starting point for adding promise support\nto teenytest:\n\n``` js\nmodule.exports = {\n  name: 'teenytest-promise',\n  translators: {\n    userFunction: function (runUserFunction, metadata, cb) {\n      runUserFunction(function (er, result) {\n        if (typeof result.value === 'object' \u0026\u0026\n            typeof result.value['then'] === 'function') {\n          result.value.then(\n            function promiseFulfilled (value) {\n              cb(er, value)\n            },\n            function promiseRejected (reason) {\n              cb(reason, null)\n            }\n          )\n        } else {\n          cb(er)\n        }\n      })\n    }\n  }\n}\n```\n\n(The above is also the actual source listing of v1.0.0 of the\n[teenytest-promise](https://github.com/testdouble/teenytest-promise) module.)\n\n###### test wrappers\n\nNot to be confused with a test _function_, a `test` wrapper scope encompasses a\ntest function _plus_ all its hooks. If your plugin is concerned with each test's\nresults, you probably want a `test`-scoped wrapper.\n\nAn example is teenytest's built-in timeout plugin, which guards against tests\nthat take too long:\n\n``` js\nvar timeoutInMs = 1000\nteenytest.plugins.register({\n  name: 'teenytest-timeout',\n  supervisors: {\n    test: function (runTest, metadata, cb) {\n      var timedOut = false\n      var timer = setTimeout(function outtaTime () {\n        timedOut = true\n        cb(new Error('Test timed out! (timeout: ' + timeoutInMs + 'ms)'))\n      }, timeoutInMs)\n\n      runTest(function timerWrappedCallback (er) {\n        if (!timedOut) {\n          clearTimeout(timer)\n          cb(er)\n        }\n      })\n    }\n  }\n})\n```\n\n##### suite wrappers\n\nFinally, plugins can also wrap the execution of entire suites of tests using the\n`suite` scope. This scope is most often necessary when your plugin wants to\ncomprehend the overall test suite as a tree, and wants to visit each of the\nsuites as nodes on the tree.\n\nThis is certainly the least-used scoping, and is most likely to be needed by\nplugins that gather test results or report on them.\n\n#### Plugin lifecycle events\n\nThe example above defines its wrapper under `interceptors`, because it needs to\nrun after results have been initially determined but before the results have been\nlogged to the console. Below are the available events to hook into:\n\n##### translators\n\nWrapper functions defined under a plugin's `translators` property will run first,\nwhich should enable the author to augment the behavior of the test itself. For\ninstance, one of the first plugins teenytest runs converts all of the user's\nfunctions to a consistent async callback API, regardless of whether the user\nfunction was asynchronous or not.\n\n##### supervisors\n\nWrapper functions that desire to short-circuit or affect the failure/passing\nstatus of a test are implemented under a plugin's `supervisors` key. Two examples\nbuilt into teenytest of this are a plugin that enforces a timeout for each test\nand another that catches uncaught exceptions (i.e. if the user throws error\ninstead of passing it to the callback function).\n\n##### analyzers\n\nWrapper functions that compute results are defined under the `analyzers` key of\na plugin. Teenytest ships with a built-in results plugin \u0026 store that is probably\nfine for most purposes, but if you want to determine the results of your tests\nsome other way, you would define your own `analyzers` wrappers.\n\nIt's important to note that prior to the `analyzers` lifecycle event, all\ncallbacks pass any test failure as an initial error argument, but—because\nthe built-in results plugin can ensure recorded results are passed to subsequent\nplugin wrappers' callbacks—any errors up to this point will be swallowed and\nreplaced with `null`. If a subsequent plugin wrapper passes an error to its own\ncallback function, it will be interpreted by teenytest as a fatal error, aborting\nthe test run.\n\n##### interceptors\n\nSometimes a plugin that plays a supervisory role actually requires knowledge of\na test's results in order to determine if a failure occurred. A classic example\nof this (and perhaps the only use case) are things like \"pending test\" features,\nwhere tests flagged as works-in-progress or \"pending\" should fail (because\nthey've been marked by the user as unfinished). As a result, a pending test\ninterceptor might trigger a failure for any pending test that passes (perhaps\nindicating to the user they need to write a failing test or unflag the test as\nno longer pending).\n\n##### reporters\n\nReporter wrappers come after all the other plugins, using the provided\nresults callback to write results. By default, teenytest writes out TAP13 to\nstandard out, but a custom reporter could format results any way it likes.\n\n### Invoking teenytest via the API\n\nWhile it'd be unusual to need it, if you `require('teenytest')`, its exported\nfunction looks like:\n\n\u003e teenytest(globOfTestPaths, [options], callback)\n\nThe function takes a glob pattern describing where your tests are located and\nan options object with a few simple settings. If your tests pass, the callback's\nsecond argument will be `true`. If your tests fail, it will be `false`.\n\nHere's an example test script with every option set and a comment on the\ndefaults:\n\n``` javascript\n#!/usr/bin/env node\n\nvar teenytest = require('teenytest')\n\nteenytest('test/lib/**/*.js', {\n  helperPath: 'test/helper.js', // module that exports test hook functions (default: null)\n  output: console.log, // output for writing results\n  cwd: process.cwd(), // base path for test globs \u0026 helper path,\n  asyncTimeout: 5000 // milliseconds to wait before triggering failure of async tests \u0026 hooks\n}, function(er, passing) {\n  process.exit(!er \u0026\u0026 passing ? 0 : 1)\n})\n```\n\nAs you can see, the above script will bail with a non-zero exit code if the tests\ndon't pass or if a fatal error occurs.\n\nWhile the API is asynchronous, but both synchronous and asynchronous tests are\nsupported.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftestdouble%2Fteenytest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftestdouble%2Fteenytest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftestdouble%2Fteenytest/lists"}