test-anywhere
v0.9.1
Published
A JavaScript testing framework that is built on top of built-in testing frameworks of Bun, Deno, and Node.js
Downloads
2,910
Maintainers
Readme
test-anywhere
A universal JavaScript testing framework that works seamlessly across Bun, Deno, and Node.js. Write your tests once and run them anywhere.
Features
- 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
Installation
Node.js
npm install test-anywhereBun
bun add test-anywhereDeno
No 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';Usage
Basic Test Syntax
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 });
});BDD-Style with describe()
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('[email protected]', /@/);
});
});
describe('deletion', () => {
it('should delete existing user', () => {
assert.ok(true);
});
});
});Node.js
Run tests:
node --test
# or
npm testDeno
Create 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.
Bun
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 testAPI Reference
Test Definition
test(name, fn) / it(name, fn)
Creates 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
});describe(name, fn)
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
});
});Test Modifiers
test.skip(name, fn) / it.skip(name, fn)
Skip a test (won't be executed).
test.skip('broken test', () => {
// This test will be skipped
});test.only(name, fn) / it.only(name, fn)
Run only this test (useful for debugging).
test.only('debug this test', () => {
// Only this test will run
});test.todo(name) / it.todo(name)
Mark a test as pending/TODO.
test.todo('implement this feature');describe.skip(name, fn) / describe.only(name, fn)
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
});Test Configuration
setDefaultTimeout(timeout)
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.
Assertions
assert.ok(value, message?)
Asserts that a value is truthy.
Example:
assert.ok(true);
assert.ok(1 === 1, 'one should equal one');assert.equal(actual, expected, message?)
Asserts that two values are strictly equal (===).
Example:
assert.equal(2 + 2, 4);
assert.equal('hello', 'hello');assert.deepEqual(actual, expected, message?)
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]);assert.throws(fn, message?)
Asserts that a function throws an error when called.
Example:
assert.throws(() => {
throw new Error('oops');
});Multiple Assertion Styles
test-anywhere supports three assertion styles to provide maximum compatibility and ease of migration:
1. Node.js/Classic Style (assert.*)
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);2. Bun/Jest Style (expect())
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');
});
});3. Deno Style (assertEquals, etc.)
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: '[email protected]' };
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);
});Mix and Match
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);
});
});Lifecycle Hooks
Setup and teardown hooks for test initialization and cleanup.
beforeAll(fn) / before(fn)
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
});
});beforeEach(fn)
Runs before each test.
beforeEach(() => {
// Reset state before each test
});afterEach(fn)
Runs after each test.
afterEach(() => {
// Clean up after each test
});afterAll(fn) / after(fn)
Runs once after all tests. after() is a Mocha-style alias.
afterAll(() => {
// Disconnect from database
});getRuntime()
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"Examples
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
How It Works
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.
Code Coverage
All three runtimes support code coverage reporting through their native test runners. This library provides convenient npm scripts to run coverage for each runtime.
Quick Start
# 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)Enforcing Coverage Thresholds
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.
Node.js
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/Bun
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
Deno
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.mjsProgrammatic Coverage Check
For 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);
}Generating LCOV Reports
For integration with coverage reporting tools (Codecov, Coveralls, etc.):
Node.js
node --test --experimental-test-coverage \
--test-reporter=lcov --test-reporter-destination=coverage.lcov \
tests/Bun
bun test --coverage --coverage-reporter=lcov
# Output: coverage/lcov.infoDeno
deno test --coverage=coverage --allow-read
deno coverage coverage --lcov --output=coverage.lcovDetailed Runtime Coverage Options
Bun
bun 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
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
Node.js provides experimental coverage support:
node --test --experimental-test-coverage tests/For more details on coverage options, see the Native Test Frameworks Comparison.
Requirements
- 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)
License
Unlicense - Public Domain
Code Quality
This project maintains strict code quality standards to ensure clean and maintainable code:
No Unused Variables
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.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Before submitting, ensure your code passes all linting checks:
npm run lint