A simple yet powerful testing framework for Node.js backend applications and libraries
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Advanced test creation, grouping, and macro functionality for organizing complex test suites.
Create organized groups of related tests with shared setup, teardown, and configuration.
/**
* Create a Japa test group
* @param title - Group title
* @param callback - Function that receives the group instance for configuration
* @returns Group instance
*/
test.group(title: string, callback: (group: Group) => void): Group;
interface Group {
add(test: Test): void;
bail(toggle?: boolean): this;
timeout(duration: number): this;
retry(count: number): this;
setup(handler: GroupHooksHandler<TestContext>): this;
teardown(handler: GroupHooksHandler<TestContext>): this;
each: {
setup(handler: GroupHooksHandler<TestContext>): Group;
teardown(handler: GroupHooksHandler<TestContext>): Group;
};
tap(handler: (test: Test) => void): this;
}
type GroupHooksHandler<Context> = (context: Context) => void | Promise<void>;Usage Examples:
import { test } from "@japa/runner";
import { assert } from "chai";
test.group("User Authentication", (group) => {
let authService: AuthService;
// Setup before all tests in group
group.setup(async () => {
authService = new AuthService();
await authService.initialize();
});
// Teardown after all tests in group
group.teardown(async () => {
await authService.cleanup();
});
// Setup before each test in group
group.each.setup(async ({ context }) => {
context.user = await createTestUser();
});
// Teardown after each test in group
group.each.teardown(async ({ context }) => {
await deleteTestUser(context.user.id);
});
test("should login with valid credentials", async (ctx) => {
const result = await authService.login(ctx.context.user.email, "password");
assert.isTrue(result.success);
});
test("should reject invalid credentials", async (ctx) => {
const result = await authService.login("invalid@email.com", "wrong");
assert.isFalse(result.success);
});
});
// Group with timeout and retry configuration
test.group("API Integration Tests", (group) => {
group.timeout(10000);
group.retry(2);
test("should fetch user data", async (ctx) => {
// Test logic here
});
});Create reusable test bound macros that can access the currently executed test.
/**
* Create a test bound macro. Within the macro, you can access the
* currently executed test to read its context values or define cleanup hooks.
* @param callback - Macro function that receives the test instance as first parameter
* @returns Function that can be called within tests
*/
test.macro<T extends (test: Test, ...args: any[]) => any>(
callback: T
): (...args: OmitFirstArg<Parameters<T>>) => ReturnType<T>;
type OmitFirstArg<F> = F extends [_: any, ...args: infer R] ? R : never;Usage Examples:
import { test } from "@japa/runner";
import { assert } from "chai";
// Create a macro for database operations
const withDatabase = test.macro((test: Test, tableName: string) => {
test.setup(async () => {
await createTable(tableName);
});
test.cleanup(async () => {
await dropTable(tableName);
});
return {
async insert(data: any) {
return await db.table(tableName).insert(data);
},
async find(id: number) {
return await db.table(tableName).find(id);
},
};
});
// Create a macro for API testing
const withApiClient = test.macro((test: Test, baseUrl: string) => {
let client: ApiClient;
test.setup(() => {
client = new ApiClient(baseUrl);
});
test.cleanup(async () => {
await client.close();
});
return client;
});
// Use macros in tests
test("should create and retrieve user", async (ctx) => {
const db = withDatabase("users");
const api = withApiClient("http://localhost:3000");
const user = await db.insert({ name: "John", email: "john@example.com" });
const retrieved = await db.find(user.id);
assert.equal(retrieved.name, "John");
});Functions to access and manage the currently executing test.
/**
* Get the currently running test instance
* @returns Current test instance or undefined if not in test context
*/
function getActiveTest(): Test<any> | undefined;
/**
* Get the currently running test instance or throw an error
* @returns Current test instance
* @throws Error if not in test context
*/
function getActiveTestOrFail(): Test<any>;Usage Examples:
import { test, getActiveTest, getActiveTestOrFail } from "@japa/runner";
import { assert } from "chai";
// Helper function that works with current test
function addTestMetadata(key: string, value: any) {
const currentTest = getActiveTest();
if (currentTest) {
currentTest.options.meta[key] = value;
}
}
// Macro that uses active test
const trackTestExecution = test.macro(() => {
const currentTest = getActiveTestOrFail();
const startTime = Date.now();
currentTest.cleanup(() => {
const duration = Date.now() - startTime;
console.log(`Test "${currentTest.title}" took ${duration}ms`);
});
});
test("performance test", async (ctx) => {
trackTestExecution();
addTestMetadata("category", "performance");
// Test logic here
await someAsyncOperation();
assert.isTrue(true);
});interface Group {
add(test: Test): void;
bail(toggle?: boolean): this;
timeout(duration: number): this;
retry(count: number): this;
setup(handler: GroupHooksHandler<TestContext>): this;
teardown(handler: GroupHooksHandler<TestContext>): this;
each: {
setup(handler: GroupHooksHandler<TestContext>): Group;
teardown(handler: GroupHooksHandler<TestContext>): Group;
};
tap(handler: (test: Test) => void): this;
}
type GroupHooksHandler<Context> = (context: Context) => void | Promise<void>;type OmitFirstArg<F> = F extends [_: any, ...args: infer R] ? R : never;