Test environment for isolated-vm with Jest/Vitest-compatible primitives
npm install @ricsam/isolate-test-environmentTest primitives for running tests in sandboxed V8. Provides a Jest/Vitest-compatible API.
``bash`
npm add @ricsam/isolate-test-environment
The easiest way to use this package is through @ricsam/isolate-runtime:
`typescript
import { createRuntime } from "@ricsam/isolate-runtime";
const runtime = await createRuntime({
testEnvironment: {
onEvent: (event) => {
// Receive lifecycle events during test execution
if (event.type === "testEnd") {
const icon = event.test.status === "pass" ? "✓" : "✗";
console.log(${icon} ${event.test.fullName});
}
},
},
});
await runtime.eval(
describe("math", () => {
it("adds numbers", () => {
expect(1 + 1).toBe(2);
});
it.todo("subtract numbers");
}););
const results = await runtime.testEnvironment.runTests();
console.log(${results.passed}/${results.total} passed, ${results.todo} todo);
// Check if tests exist before running
if (runtime.testEnvironment.hasTests()) {
console.log(Found ${runtime.testEnvironment.getTestCount()} tests);`
}
For advanced use cases with direct isolated-vm access:
`typescript
import { setupTestEnvironment, runTests } from "@ricsam/isolate-test-environment";
const handle = await setupTestEnvironment(context);
`
- describe, it, test (with .skip, .only, .todo modifiers)beforeAll
- , afterAll, beforeEach, afterEachexpect
- with matchers and .not modifier
- toBe(expected) - Strict equality (===)toEqual(expected)
- - Deep equalitytoStrictEqual(expected)
- - Strict deep equality (includes prototype checks)toBeTruthy()
- , toBeFalsy()toBeNull()
- , toBeUndefined(), toBeDefined()toContain(item)
- - Array/string includestoThrow(expected?)
- - Function throwstoBeInstanceOf(cls)
- - Instance checktoHaveLength(length)
- - Array/string lengthtoMatch(pattern)
- - String/regex matchtoHaveProperty(path, value?)
- - Object property check
`javascript
describe("Math operations", () => {
beforeEach(() => {
// setup before each test
});
it("should add numbers", () => {
expect(1 + 1).toBe(2);
});
it("should multiply numbers", async () => {
await Promise.resolve();
expect(2 * 3).toEqual(6);
});
describe("edge cases", () => {
it.skip("should handle infinity", () => {
expect(1 / 0).toBe(Infinity);
});
it.todo("should handle NaN");
});
});
// Negation with .not
expect(1).not.toBe(2);
expect([1, 2]).not.toContain(3);
`
`typescript
import { setupTestEnvironment, runTests, hasTests, getTestCount } from "@ricsam/isolate-test-environment";
// Setup test environment with optional event callback
const handle = await setupTestEnvironment(context, {
onEvent: (event) => {
switch (event.type) {
case "runStart":
console.log(Running ${event.testCount} tests in ${event.suiteCount} suites);${event.test.status}: ${event.test.fullName}
break;
case "testEnd":
console.log();
break;
}
},
});
// Load test code
await context.eval(userProvidedTestCode, { promise: true });
// Check if any tests were registered
if (hasTests(context)) {
console.log(Found ${getTestCount(context)} tests);
}
// Run all registered tests
const results = await runTests(context);
console.log(${results.passed}/${results.total} passed);
handle.dispose();
`
`typescript`
interface RunResults {
passed: number;
failed: number;
skipped: number;
todo: number;
total: number;
duration: number;
success: boolean; // true if no failures
suites: SuiteResult[]; // suite-level results
tests: TestResult[]; // individual test results
}
`typescript
interface TestResult {
name: string;
suitePath: string[]; // suite ancestry
fullName: string; // "suite > nested > test name"
status: "pass" | "fail" | "skip" | "todo";
duration: number;
error?: TestError;
}
interface TestError {
message: string;
stack?: string;
expected?: unknown; // for assertion failures
actual?: unknown;
matcherName?: string; // e.g., "toBe", "toEqual"
}
`
`typescript`
interface SuiteResult {
name: string;
path: string[]; // ancestry path
fullName: string; // "outer > inner"
depth: number; // nesting level (0 for root)
passed: number;
failed: number;
skipped: number;
todo: number;
duration: number;
}
`typescript``
type TestEvent =
| { type: "runStart"; testCount: number; suiteCount: number }
| { type: "suiteStart"; suite: SuiteInfo }
| { type: "suiteEnd"; suite: SuiteResult }
| { type: "testStart"; test: TestInfo }
| { type: "testEnd"; test: TestResult }
| { type: "runEnd"; results: RunResults };
MIT