A universal JavaScript testing framework that works seamlessly across Bun, Deno, and Node.js. Write your tests once and run them anywhere.
- Universal API - Single test API that works across all three runtimes
- Zero Dependencies - Built on top of native testing frameworks
- Runtime Detection - Automatically uses the appropriate native test implementation
- Simple Assertions - Built-in assertion library for common testing needs
- Type Safe - Written with modern JavaScript/ESM modules
npm install test-anywherebun add test-anywhereNo installation needed! Import directly from npm:
import { test, assert } from 'npm:test-anywhere';Or from your local installation:
import { test, assert } from './node_modules/test-anywhere/index.js';You can use either test() or it() (Mocha/Jest style):
import { test, it, assert } from 'test-anywhere';
// Using test()
test('basic math works', () => {
assert.equal(1 + 1, 2);
});
// Using it() - same as test()
it('should compare objects', () => {
assert.deepEqual({ a: 1 }, { a: 1 });
});Group related tests using describe() blocks:
import { describe, it, assert } from 'test-anywhere';
describe('user module', () => {
describe('creation', () => {
it('should create a new user', () => {
assert.ok(true);
});
it('should validate email format', () => {
assert.match('user@example.com', /@/);
});
});
describe('deletion', () => {
it('should delete existing user', () => {
assert.ok(true);
});
});
});Run tests:
node --test
# or
npm testCreate a test file (e.g., example.test.js):
import { test, assert } from 'npm:test-anywhere';
test('basic math works', () => {
assert.equal(1 + 1, 2);
});
test('strings work', () => {
assert.ok('hello'.includes('ell'));
});Run tests:
deno test --allow-readThe --allow-read permission is needed for Deno to import the module.
Create a test file (e.g., example.test.js):
import { test, assert } from 'test-anywhere';
test('basic math works', () => {
assert.equal(1 + 1, 2);
});
test('async operations work', async () => {
const result = await Promise.resolve(42);
assert.equal(result, 42);
});Run tests:
bun testCreates a test with the given name and test function. it() is an alias for test().
Parameters:
name(string): The name/description of the testfn(function): The test function to execute
Example:
test('my test name', () => {
// test code here
});
// or using it() - Mocha/Jest style
it('should do something', () => {
// test code here
});Groups related tests together (BDD style).
Parameters:
name(string): The suite namefn(function): Function containing tests and setup/teardown hooks
Example:
describe('user authentication', () => {
it('should login with valid credentials', () => {
// test code
});
it('should reject invalid credentials', () => {
// test code
});
});Skip a test (won't be executed).
test.skip('broken test', () => {
// This test will be skipped
});Run only this test (useful for debugging).
test.only('debug this test', () => {
// Only this test will run
});Mark a test as pending/TODO.
test.todo('implement this feature');Skip or isolate an entire test suite.
describe.skip('integration tests', () => {
// All tests in this suite will be skipped
});
describe.only('unit tests', () => {
// Only tests in this suite will run
});Set the default timeout for all tests in milliseconds. This is useful when tests need more time to complete (e.g., integration tests, API calls).
Parameters:
timeout(number): Timeout in milliseconds
Example:
import { test, setDefaultTimeout } from 'test-anywhere';
// Set default timeout to 60 seconds
setDefaultTimeout(60000);
test('long running operation', async () => {
// This test can take up to 60 seconds
await someSlowOperation();
});Note:
- For Bun: Uses the native
setDefaultTimeoutfrombun:test - For Node.js and Deno: Not supported natively. A warning will be logged, and you should use timeout options in individual test calls instead.
Asserts that a value is truthy.
Example:
assert.ok(true);
assert.ok(1 === 1, 'one should equal one');Asserts that two values are strictly equal (===).
Example:
assert.equal(2 + 2, 4);
assert.equal('hello', 'hello');Asserts that two values are deeply equal (compares object/array contents).
Example:
assert.deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 });
assert.deepEqual([1, 2, 3], [1, 2, 3]);Asserts that a function throws an error when called.
Example:
assert.throws(() => {
throw new Error('oops');
});test-anywhere supports three assertion styles to provide maximum compatibility and ease of migration:
Traditional Node.js-style assertions (see above sections for details):
import { assert } from 'test-anywhere';
assert.ok(true);
assert.equal(1, 1);
assert.deepEqual([1, 2], [1, 2]);
assert.notEqual(1, 2);
assert.throws(() => {
throw new Error();
});
assert.match('hello', /ell/);
assert.includes([1, 2, 3], 2);Modern chainable API inspired by Jest and Bun:
import { expect } from 'test-anywhere';
expect(value).toBe(expected); // Strict equality (===)
expect(value).toEqual(expected); // Deep equality
expect(value).not.toBe(expected); // Negation
expect(value).toBeNull(); // null check
expect(value).toBeUndefined(); // undefined check
expect(value).toBeTruthy(); // Truthy check
expect(value).toBeFalsy(); // Falsy check
expect(array).toContain(item); // Array/string contains
expect(string).toMatch(/pattern/); // Regex match
expect(fn).toThrow(); // Function throwsComplete Example:
import { describe, it, expect } from 'test-anywhere';
describe('Calculator', () => {
it('should add numbers', () => {
expect(2 + 2).toBe(4);
expect(2 + 2).toEqual(4);
});
it('should handle arrays', () => {
expect([1, 2, 3]).toContain(2);
expect([1, 2, 3]).toEqual([1, 2, 3]);
});
it('should validate strings', () => {
expect('hello world').toMatch(/world/);
expect('hello').not.toBe('goodbye');
});
});Deno-inspired assertion functions from @std/assert:
import {
assertEquals,
assertNotEquals,
assertStrictEquals,
assertExists,
assertMatch,
assertArrayIncludes,
assertThrows,
assertRejects,
} from 'test-anywhere';
assertEquals(actual, expected); // Deep equality
assertNotEquals(actual, expected); // Not equal
assertStrictEquals(actual, expected); // Strict equality (===)
assertNotStrictEquals(actual, expected); // Strict inequality (!==)
assertExists(value); // Not null/undefined
assertMatch(string, /pattern/); // Regex match
assertArrayIncludes(arr, [items]); // Array includes items
assertThrows(() => {
throw new Error();
}); // Sync throws
await assertRejects(async () => {
throw new Error();
}); // Async rejectsComplete Example:
import {
test,
assertEquals,
assertMatch,
assertArrayIncludes,
} from 'test-anywhere';
test('user validation', () => {
const user = { name: 'Alice', email: 'alice@example.com' };
assertEquals(user.name, 'Alice');
assertMatch(user.email, /@/);
});
test('array operations', () => {
const numbers = [1, 2, 3, 4, 5];
assertArrayIncludes(numbers, [2, 4]);
assertEquals(numbers.length, 5);
});All three styles can be used together in the same project:
import { describe, it, assert, expect, assertEquals } from 'test-anywhere';
describe('Multi-style assertions', () => {
it('supports all styles', () => {
const value = 42;
// Node style
assert.equal(value, 42);
// Bun/Jest style
expect(value).toBe(42);
// Deno style
assertEquals(value, 42);
});
});Setup and teardown hooks for test initialization and cleanup.
Runs once before all tests. before() is a Mocha-style alias.
import { beforeAll, describe, it } from 'test-anywhere';
describe('database tests', () => {
beforeAll(() => {
// Connect to database once
});
it('should query data', () => {
// test code
});
});Runs before each test.
beforeEach(() => {
// Reset state before each test
});Runs after each test.
afterEach(() => {
// Clean up after each test
});Runs once after all tests. after() is a Mocha-style alias.
afterAll(() => {
// Disconnect from database
});Returns the current runtime name.
Returns: 'node', 'bun', or 'deno'
Example:
import { getRuntime } from 'test-anywhere';
console.log(`Running on ${getRuntime()}`); // e.g., "Running on node"Check out the examples directory for complete working examples:
- Node.js:
examples/node-example.test.js - Deno:
examples/deno-example.test.js - Bun:
examples/bun-example.test.js
test-anywhere automatically detects the JavaScript runtime (Bun, Deno, or Node.js) and delegates to the appropriate native testing framework:
- Bun → Uses
bun:test - Deno → Uses
Deno.test - Node.js → Uses
node:test
This means you get the full performance and features of each runtime's native testing implementation while maintaining a consistent API across all platforms.
All three runtimes support code coverage reporting through their native test runners. This library provides convenient npm scripts to run coverage for each runtime.
# Run coverage with default runtime (Node.js)
npm run coverage
# Run coverage for specific runtimes
npm run coverage:node
npm run coverage:bun
npm run coverage:deno
# Run coverage with 99% threshold enforcement
npm run coverage:check # Node.js with thresholds
npm run coverage:check:node # Node.js with thresholds
npm run coverage:check:bun # Bun with thresholds (via bunfig.toml)
npm run coverage:check:deno # Deno with thresholds (via script)For CI/CD pipelines, you can enforce minimum coverage thresholds. Default thresholds vary by runtime due to differences in how coverage is calculated:
- Node.js & Deno: 80% (includes test file coverage)
- Bun: 75% (reports src/ files only, excludes test files)
These baselines account for runtime-specific code paths that cannot all be tested in a single runtime environment.
Use the provided script for threshold enforcement:
# Default 80% threshold
node scripts/check-node-coverage.mjs
# Custom threshold
node scripts/check-node-coverage.mjs --threshold=90
# Or via environment variable
COVERAGE_THRESHOLD=90 node scripts/check-node-coverage.mjsNode.js 22.8.0+ also supports native threshold flags:
node --test --experimental-test-coverage \
--test-coverage-lines=80 \
--test-coverage-branches=80 \
--test-coverage-functions=80 \
tests/Use the provided script for reliable threshold enforcement:
# Default 75% threshold
node scripts/check-bun-coverage.mjs
# Custom threshold
node scripts/check-bun-coverage.mjs --threshold=90
# Or via environment variable
COVERAGE_THRESHOLD=90 node scripts/check-bun-coverage.mjsYou can also configure thresholds in bunfig.toml:
[test]
coverageThreshold = { line = 0.75, function = 0.75, statement = 0.75 }
coverageReporter = ["text", "lcov"]
coverageDir = "coverage"Run with: bun test --coverage
Use the provided script for threshold enforcement:
# Default 80% threshold
node scripts/check-deno-coverage.mjs
# Custom threshold
node scripts/check-deno-coverage.mjs --threshold=90
# Or via environment variable
COVERAGE_THRESHOLD=90 node scripts/check-deno-coverage.mjsFor users who need to ensure specific coverage thresholds programmatically (e.g., in a pre-commit hook or custom CI script):
import { execSync } from 'node:child_process';
const THRESHOLD = 80;
// Node.js
try {
execSync(
`node --test --experimental-test-coverage --test-coverage-lines=${THRESHOLD} --test-coverage-branches=${THRESHOLD} --test-coverage-functions=${THRESHOLD} tests/`,
{ stdio: 'inherit' }
);
console.log('Coverage check passed!');
} catch {
console.error('Coverage below threshold');
process.exit(1);
}For integration with coverage reporting tools (Codecov, Coveralls, etc.):
node --test --experimental-test-coverage \
--test-reporter=lcov --test-reporter-destination=coverage.lcov \
tests/bun test --coverage --coverage-reporter=lcov
# Output: coverage/lcov.infodeno test --coverage=coverage --allow-read
deno coverage coverage --lcov --output=coverage.lcovbun test --coverageBun displays a coverage report directly in the terminal. Full configuration in bunfig.toml:
[test]
coverage = true
coverageReporter = ["text", "lcov"]
coverageThreshold = { line = 0.75, function = 0.75, statement = 0.75 }
coverageDir = "coverage"Deno uses a two-step process: collect coverage data, then generate a report:
# Collect coverage data
deno test --coverage=cov_profile --allow-read
# View coverage summary
deno coverage cov_profile
# Generate LCOV report for CI tools
deno coverage cov_profile --lcov --output=coverage.lcov
# Generate HTML report
deno coverage cov_profile --htmlNode.js provides experimental coverage support:
node --test --experimental-test-coverage tests/For more details on coverage options, see the Native Test Frameworks Comparison.
- Node.js: 20.0.0 or higher (for native test runner support)
- Deno: 1.x or higher (native Deno.test support)
- Bun: Any recent version (native bun:test support)
Unlicense - Public Domain
This project maintains strict code quality standards to ensure clean and maintainable code:
The no-unused-vars ESLint rule is enforced without any exceptions. This means:
- All declared variables must be used
- All function parameters must be used
- All caught error variables must be used
- No ignore patterns (like
^_prefixes) are allowed
This strict policy helps maintain code clarity and prevents dead code from accumulating. If you need to acknowledge a parameter but don't use it, consider refactoring the code instead of working around the lint rule.
Contributions are welcome! Please feel free to submit a Pull Request.
Before submitting, ensure your code passes all linting checks:
npm run lint