mrkeyoor.com_
Wed 23 Sept 00:34 UTC
npmTestingupdated 22 Sept 2026

uvu review

uvu 0.5.6 is a small JavaScript test runner built around executable test files. A file registers cases on the default test suite or a named suite, attaches hooks if needed, and calls run(). The CLI finds files and loads their suites; running the file with Node isolates it. Assertions live in an optional submodule and ordinary thrown errors also fail tests. The current release fixed false passes around process.exit and unresolved promises, corrected BigInt diff output, and made programmatic runner imports possible.

Verdict

uvu 0.5.6 installed in 1.2 seconds, occupied 1 MB, and produced a 5.5 KB gzipped broad bundle in our sandbox with 0 audit findings. It still suits compact Node test suites, but new projects needing watch, coverage, mocks, or transforms will assemble less plumbing with Vitest.

We installed it

Lab card: what happened when we installed uvuScreenshot of uvu documentation
Install✓ · 1.2s6 packages on disk · 1 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser5.5 KBgzipped (12.9 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does uvu install cleanly?

Yes. In a fresh container with an empty cache, npm install uvu finished in 1 seconds, leaving 6 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does uvu add to a browser bundle?

5.5 KB gzipped (12.9 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does uvu work with both ESM and CommonJS?

Yes. Both import 'uvu' and require('uvu') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does uvu include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

uvu or vitest: which should you use?

vitest: Choose it for active development, watch mode, Vite transforms, mocking, fake timers, coverage, and browser options in one runner. uvu 0.5.6 installed in 1.2 seconds, occupied 1 MB, and produced a 5.5 KB gzipped broad bundle in our sandbox with 0 audit findings.

When should you not use uvu?

You expect built-in watch mode; the project documentation delegates watching to an external file watcher

API stability4/5The 0.5 API has stayed unchanged for years: register cases, attach before and after hooks, mutate suite context when needed, and call run(). Assertions throw ordinary Error subclasses, and the exports map supports require and import. The package remains below 1.0, and the release gap means compatibility comes partly from inactivity rather than a published stability policy.
Docs4/5The README and linked docs cover the CLI, regex discovery, suites, hooks, context, isolation, ESM, assertions, snapshots, fixtures, coverage, watching, and TypeScript examples. They state that assertions are optional and explain thrown-error handling. Several examples and the benchmark still target old Node versions and legacy -r esm usage, which dates otherwise useful guidance.
Maintenance2/5Version 0.5.6 was released on July 3, 2022, and GitHub records the last repository push on August 30, 2024. GitHub now shows 88 open issues and pull requests. The final release fixed exit and unresolved-promise failures, but users cannot assume timely work on current Node loaders, TypeScript execution, browser tooling, or coverage integrations.
Ecosystem3/5uvu recorded 5,455,805 npm downloads for August 18 through 24, 2026 and has 3,032 GitHub stars. CommonJS, ESM, TypeScript declarations, optional assertions, and ordinary thrown errors let it combine with many Node tools. It has recipes rather than a coordinated plugin system, leaving watch, coverage, transpilation, DOM, mocks, and browser execution to other packages.

Use it if

  • A Node library needs fast, explicit tests with suites, hooks, async support, and few moving parts
  • You want test files that also run directly with Node for process-level isolation
  • Built-in deep, snapshot, match, throws, and type assertions cover the project's needs
  • CommonJS and ESM entry points plus bundled declarations must coexist with Node 8 compatibility
Skip it if

Setup reality

Our uvu 0.5.6 install finished in 1.2 seconds and left 6 packages using 1 MB on disk. It has 4 direct dependencies, 0 peer dependencies, and 124 KB unpacked. npm audit found 0 known vulnerabilities. The package declares Node >=8.

A test file must call test.run() or suite.run() after registration. Forgetting that call can leave a file that loads without executing its cases. Add uvu tests to package.json for collection, or run a single file with Node when isolation matters. The CLI's directory pattern and ignore flags are regular expressions, not shell globs.

TypeScript declarations ship with the package, and the exports map has separate require and import targets. Both forms worked in our Node 22 sandbox. Discovery recognizes TypeScript filename extensions but does not transpile them. Compile first or preload a runtime loader. Version 0.5.6 improved programmatic parse and run imports, although its release note still describes that interface as an area intended for simplification.

Our broad browser bundle measured 12.9 KB minified and 5.5 KB gzipped. The assertions are browser-compatible, but uvu does not supply a browser, DOM, or automation layer. Add c8 or nyc for coverage and an external watcher for reruns. Shared suite context is mutable, so reset it in hooks. For rejected promises, await the call inside a test; an assertion intended for synchronous throws does not replace async rejection handling.

Patterns

Run a basic assertion write-basic-test

import {test} from 'uvu';
import * as assert from 'uvu/assert';

test('adds numbers', () => {
  assert.is(2 + 2, 4);
});

test.run();

The final test.run() is required; registering the case alone does not execute it when the file loads.

Group cases in a named suite create-named-suite

import {suite} from 'uvu';
import * as assert from 'uvu/assert';

const math = suite('math');
math('sqrt', () => assert.is(Math.sqrt(9), 3));
math.run();

A named suite reports its label and maintains its own hooks and mutable context.

Await an asynchronous result test-async-code

test('loads user', async () => {
  const user = await loadUser('42');
  assert.is(user.id, '42');
});

Return or await the promise so 0.5.6 can observe completion and rejection before the process exits.

Create and clean suite context share-test-context

const api = suite('api');

api.before.each((context) => { context.server = startServer(); });
api.after.each(async (context) => { await context.server.close(); });

Suite context is mutable and shared through hooks, so each test should receive freshly assigned state when isolation matters.

Compare nested values assert-deep-equality

import * as assert from 'uvu/assert';

assert.equal(
  {user: {id: 7, roles: ['editor']}},
  {user: {id: 7, roles: ['editor']}}
);

assert.equal performs a deep comparison; assert.is checks strict identity and is the better signal for primitives or references.

Check a synchronous exception assert-synchronous-throw

assert.throws(
  () => JSON.parse('{'),
  /Unexpected end/
);

assert.throws expects the callback to throw synchronously; use try and catch around an awaited promise for an async rejection.

Compare an inline snapshot snapshot-inline-value

const output = JSON.stringify({ok: true});
assert.snapshot(output, '{"ok":true}');

uvu compares the supplied expected value and does not maintain or update a separate snapshot file.

Isolate one test file with Node run-single-file

node tests/parser.test.mjs

The file still needs its own run() call; direct Node execution avoids loading every suite found by the CLI collector.

Collect files with a regular expression filter-test-files

uvu tests '\.unit\.(js|mjs)$'

The second CLI argument is a case-insensitive regular expression, not a glob such as **/*.unit.js.

Use Node's assertion module use-node-assert

import {test} from 'uvu';
import assert from 'node:assert/strict';

test('parses', () => {
  assert.deepEqual(parse('a=1'), {a: '1'});
});
test.run();

uvu treats thrown errors as failures, so its optional assert package is not required for a 0.5.6 suite.

Alternatives

PackageRegistryPick it when
vitestnpmChoose it for active development, watch mode, Vite transforms, mocking, fake timers, coverage, and browser options in one runner.
tapenpmChoose it for TAP output and a streaming assertion style with a long Node and browser history.
mochanpmChoose it for a mature runner with many reporters, loaders, interfaces, hooks, and third-party integrations.

More testing guides

pytest · chai · jsdom · vitest · playwright · coverage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.