@small-web/test-monkey
v1.1.1
Published
A node:test custom reporter that’s also a monkey.
Downloads
358
Maintainers
Readme
Test Monkey
🍌️🐒️

A node:test custom reporter that’s also a monkey.
Displays test runner status using a static single-line spinner (hint: it’s a monkey) and only fills your screen with text on failures and with your coverage report.
No dependencies.
Test Monkey is the successor to Tap Monkey, which is a TAP formatter. If you need to consume TAP, use Tap Monkey. If you want the best possible experience with node:test, use Test Monkey.
Did your tests pass? Excellent! Here, have a banana :) 🍌
System requirements
Node.js version 24.19.0+
Install
npm i --save-dev @small-web/test-monkeyUse
Point --test-reporter at Test Monkey:
node --test --test-reporter=@small-web/test-monkey 'tests/*.js'Or, as a test task in your package.json file:
"scripts": {
"test": "node --test --test-reporter=@small-web/test-monkey 'tests/*.js'"
}💡 Test Monkey is designed for interactive use and does not support
--test-reporter-destination.
Options
Test Monkey is a reporter module, not a command, but it still has options you can set using environment variables.
| Variable | Description |
| --------------------------- | --------------------------------------------------------------------------------------|
| TEST_MONKEY_QUIET | Don’t animate the progress line. Failures, coverage, and the summary are still shown. |
| TEST_MONKEY_DEBUG | Show everything the tests print, including console output from your app. |
| TEST_MONKEY_BAIL_ON_FAIL | Stop reporting at the first failure. |
| TEST_MONKEY_STALL_SECONDS | How long a test idle before Test Monkey warns of a possible hang. Default: 3. |
| TEST_MONKEY_NO_CAPTURE | Everything your tests print go directly to the terminal. |
Accessibility
For a quieter monkey, turn TEST_MONKEY_QUIET on:
"scripts": {
"test": "TEST_MONKEY_QUIET=1 node --test --test-reporter=@small-web/test-monkey 'tests/*.js'"
}In quiet mode, nothing is written to the progress line (no animation, no running test names, etc.) Failures, the coverage report and the final summary are still shown in full.
This is a general accessibility and usability feature. It might help folks using assistive devices like screen readers who might otherwise get overwhelmed by notifications of running and passing tests, as well as anyone else who wants a generally calmer monkey.
💡 The monkey also stays still when it isn’t being watched. If stderr is not a terminal (TTY) – e.g., when it’s a pipe, a redirect, and/or in most continuous integration (CI) systems – the progress line is switched off. (This is to stop animation frames ending up in your log files, etc.)
The progress line
🙉 3 files · 47 tests · 12s · Now running: a monkey testThe progress line has an animated monkey (because, of course) as well as useful statistics about the current run: the number of files in flight, the number of tests completed so far, how long the run has been going, and the name of the currently running test.
Test failures

Failures are always written to the terminal in full. Here’s a list of the failure types Test Monkey handles and the information it renders for each:
Error thrown inside a test – flagged as an error in the test itself rather than an assertion failure, followed by its stack trace.
Assertion failure – the operator, and the expected and actual values, followed by the stack trace with the frame that points at your code highlighted.
Error thrown in a hook – flagged as an error in the setup or teardown around the test, stating which of the four possible hooks it was in. An assertion that fails inside a hook keeps its expected/actual block.
Plan failure – how many assertions (
t.plan()) were expected and how many ran.Timed-out or cancelled/aborted test – shown as
✖ CANCELLED.Test that never starts – this occurs when a subtest registers after its parent has already finished and usually points to a missing
await. The displayed stack is for the call that arrived late.A parent with failing subtest – one line comment pointing out the child’s failure (harsh).
File that couldn’t be run – the child’s exit code and signal, plus the file’s output prior to failing (this is where you’ll find the
SyntaxErrorthat caused the failure).
All failures print a tail of any output received prior to the failure to provide context that could help in debugging the issue.
Stall detection

Test Monkey makes a basic attempt to warn you about a possible hanging test run.
This is a best guess based on a wall clock heuristic (default: 3 seconds of event silence from node:test).
If you have very slow unit – or, more likely, integration – tests, you can increase this duration by setting the TEST_MONKEY_STALL_SECONDS environment variable.
Code coverage

Use Test Monkey for coverage in exactly the same way as you do for your tests:
"scripts": {
"coverage": "node --test --experimental-test-coverage --test-reporter=@small-web/test-monkey 'tests/*.js'"
}Paths are relative to the working directory, and files that miss a --test-coverage-lines (or branches, or functions) threshold are shown in red.
If a threshold is missed, the summary also contains a “Coverage is below the threshold.” message (and you don’t get a banana, sorry).
Note that when thresholds are missed, node:test fails the run.
🍌 Test Monkey itself has 100% test coverage. (Anything less would be embarrassing.)
🪤 Using npm link? Test Monkey’s own code is normally kept out of your coverage statistics as it lives in your project’s node_modules folder which Node excludes from coverage by default. However, when you use
npm link @small-web/test-monkeyto use a local development version of Test Monkey, it lives outside your project’s node_modules folder and so it is included in coverage. To exclude it, use--test-coverage-exclude='**/test-monkey/**'. Furthermore, any--test-coverage-excludeswitches off node’s own default of leaving your test files out, so you will want--test-coverage-exclude='test/**'alongside it. (This note is really only relevant if you’re developing on Test Monkey and testing it.)
Debug output
The opposite of TEST_MONKEY_QUIET is TEST_MONKEY_DEBUG.
Everything your program prints – console.log(), console.error(), anything else on stdout or stderr – is shown as it happens, along with any diagnostics your tests emit using t.diagnostic(). These are normally hidden, under all isolation modes.
Node’s own end-of-run statistics (tests 12, pass 12, duration_ms …) are filtered out even in debug mode as Test Monkey already shows them in the summary.
Exit codes
Exit codes come from node:test, with one exception:
When the test runner cannot find any tests, Test Monkey forces an exit with error code 1, where node would have exited with 0 (success). Having no tests run usually means something went wrong.
Watch mode
Test Monkey works well with node:test’s watch mode (node --test --watch). Totals, timings, and buffered output are reset at the start of each run rather than accumulating over the session.
💡 Coverage is only reported on the first run of a watch session. A missing coverage table on a re-run is standard node:test behaviour; not a bug in Test Monkey.
Support for --test-isolation=none
Your test files run in the runner’s own process in this mode, which means their output normally goes directly to the terminal rather than being relayed to the reporter. To make this mode behave the same way as --test-isolation=process (the default), Test Monkey captures the streams (stdout and stderr) itself and carries out its own processing.
Two kinds of output still go directly to the terminal and we have no control over that:
- A child process your tests spawn with inherited stdio writes to the terminal directly, without passing through JavaScript, so we can’t capture it.
- Output from
--test-global-setup. Node runs this before it loads Test Monkey.
Testing Test Monkey
Test Monkey’s own tests are, of course, displayed by none other than *drumroll* Test Monkey.
Run tests
npm run testRun coverage
npm run coverageWhen Test Monkey, a test reporter, is reporting on itself, its own bugs can appear like a broken test run.
If the output from Test Monkey’s own tests is suspect, use the npm run -s test:spec script which uses Node’s built-in spec reporter to diagnose it.
Recording the screencasts
To record/update all the screencasts in this README, run:
screencasts/recordTo record/update a single screencast, pass the name of its tape file (sans file extension). e.g.,
screencasts/record passingFor more details, see the screencasts README.
Like this? Fund us!
Small Technology Foundation is a tiny, independent not-for-profit.
We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.
Copyright
© 2026-present Aral Balkan, Small Technology Foundation.
License
AGPL version 3.0.
