CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-japa--runner

A simple yet powerful testing framework for Node.js backend applications and libraries

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

core-classes.mddocs/

Core Classes

Low-level classes for advanced usage, testing frameworks, and plugin development.

Capabilities

Runner Class

Test execution engine that manages suites, reporters, and the overall test lifecycle.

/**
 * Runner class is used to execute the tests
 */
class Runner {
  /** Enable or disable bail mode (stop on first failure) */
  bail(toggle?: boolean): this;
  
  /** Register a reporter for test output */
  registerReporter(reporter: ReporterContract): void;
  
  /** Get test execution summary */
  getSummary(): RunnerSummary;
  
  /** Register callback for suite events */
  onSuite(callback: (suite: Suite) => void): void;
  
  /** Add a suite to the runner */
  add(suite: Suite): void;
  
  /** Start test execution */
  async start(): Promise<void>;
  
  /** Execute all tests */
  async exec(): Promise<void>;
  
  /** Finish test execution */
  async end(): Promise<void>;
}

interface RunnerSummary {
  hasError: boolean;
  aggregates: {
    passed: number;
    failed: number;
    skipped: number;
    todo: number;
    total: number;
  };
  failureTree: Array<{
    name: string;
    errors: Array<{ phase: string; error: Error }>;
    children: Array<{
      name: string;
      errors: Array<{ phase: string; error: Error }>;
    }>;
  }>;
}

Usage Examples:

import { Runner, Emitter, Suite } from "@japa/runner/core";

// Create and configure runner
const emitter = new Emitter();
const runner = new Runner(emitter);

// Enable bail mode
runner.bail(true);

// Register custom reporter
runner.registerReporter({
  name: "custom",
  handler: (runner, emitter) => {
    emitter.on("test:end", (payload) => {
      console.log(`Test: ${payload.title} - ${payload.hasError ? "FAIL" : "PASS"}`);
    });
  },
});

// Add test suites
const unitSuite = new Suite("unit", emitter, refiner);
runner.add(unitSuite);

// Execute tests
await runner.start();
await runner.exec();
await runner.end();

// Get results
const summary = runner.getSummary();
console.log(`Tests: ${summary.aggregates.total}, Passed: ${summary.aggregates.passed}`);

Test Class

Individual test instance with enhanced functionality and assertion capabilities.

/**
 * Test class represents an individual test and exposes API to tweak its runtime behavior
 */
class Test<TestData = undefined> {
  /** Assert the test throws an exception with a certain error message */
  throws(message: string | RegExp, errorConstructor?: any): this;
  
  /** Set timeout for this test */
  timeout(duration: number): this;
  
  /** Set retry count for this test */
  retry(count: number): this;
  
  /** Mark test as todo (not implemented) */
  todo(): this;
  
  /** Mark test as skipped */
  skip(): this;
  
  /** Pin test (only run this test) */
  pin(): this;
  
  /** Add test setup hook */
  setup(handler: TestHooksHandler<TestContext>): this;
  
  /** Add test cleanup hook */
  cleanup(handler: TestHooksCleanupHandler<TestContext>): this;
  
  /** Execute the test */
  run(executor: TestExecutor<TestContext, TestData>, debuggingError: Error): this;
}

type TestHooksHandler<Context> = (context: Context) => void | Promise<void>;
type TestHooksCleanupHandler<Context> = (context: Context) => void | Promise<void>;

Usage Examples:

import { Test, TestContext, Emitter, Refiner } from "@japa/runner/core";

// Create test instance
const emitter = new Emitter();
const refiner = new Refiner();
const test = new Test("should validate input", (test) => new TestContext(test), emitter, refiner);

// Configure test
test
  .timeout(5000)
  .retry(2)
  .setup(async (context) => {
    context.database = await setupTestDatabase();
  })
  .cleanup(async (context) => {
    await cleanupTestDatabase(context.database);
  });

// Test that expects an exception
const errorTest = new Test("should throw error", (test) => new TestContext(test), emitter, refiner);
errorTest
  .throws("Invalid input", ValidationError)
  .run(async (ctx) => {
    throw new ValidationError("Invalid input");
  }, new Error());

// Pin test for focused debugging
const debugTest = new Test("debug test", (test) => new TestContext(test), emitter, refiner);
debugTest
  .pin()
  .run(async (ctx) => {
    // This test will run exclusively when pinned
    // Note: You would need to import assert from your assertion library
    // assert.isTrue(true);
  }, new Error());

TestContext Class

Test context that carries data and provides utilities for individual tests.

/**
 * Test context carries context data for a given test
 */
class TestContext {
  /** The test instance this context belongs to */
  test: Test;
  
  /** Register a cleanup function that runs after the test finishes */
  cleanup(cleanupCallback: TestHooksCleanupHandler<TestContext>): void;
  
  constructor(test: Test);
}

Usage Examples:

import { test } from "@japa/runner";

test("should handle cleanup", async (ctx) => {
  // Set up resources
  const connection = await createDatabaseConnection();
  const tempFile = await createTempFile();
  
  // Register cleanup functions
  ctx.cleanup(async () => {
    await connection.close();
    console.log("Database connection closed");
  });
  
  ctx.cleanup(async () => {
    await deleteTempFile(tempFile);
    console.log("Temp file deleted");
  });
  
  // Test logic
  await connection.query("SELECT 1");
  await writeToFile(tempFile, "test data");
  
  // Cleanup functions will run automatically after test completion
});

Group Class

Container for organizing related tests with shared configuration and hooks.

/**
 * TestGroup is used to bulk configure a collection of tests and define lifecycle hooks
 */
class Group {
  /** Add a test to this group */
  add(test: Test): void;
  
  /** Enable bail mode for this group */
  bail(toggle?: boolean): this;
  
  /** Set timeout for all tests in group */
  timeout(duration: number): this;
  
  /** Set retry count for all tests in group */
  retry(count: number): this;
  
  /** Add group setup hook (runs once before all tests) */
  setup(handler: GroupHooksHandler<TestContext>): this;
  
  /** Add group teardown hook (runs once after all tests) */
  teardown(handler: GroupHooksHandler<TestContext>): this;
  
  /** Add hooks that run before/after each test */
  each: {
    setup(handler: GroupHooksHandler<TestContext>): Group;
    teardown(handler: GroupHooksHandler<TestContext>): Group;
  };
  
  /** Apply function to all tests in group */
  tap(handler: (test: Test) => void): this;
}

type GroupHooksHandler<Context> = (context: Context) => void | Promise<void>;

Usage Examples:

import { Group, Suite, Emitter, Refiner } from "@japa/runner/core";

// Create group
const emitter = new Emitter();
const refiner = new Refiner();
const group = new Group("Database Tests", emitter, refiner);

// Configure group
group
  .timeout(10000)
  .retry(1)
  .bail(false);

// Add group-level hooks
group.setup(async (context) => {
  console.log("Setting up database for all tests");
  context.db = await setupDatabase();
});

group.teardown(async (context) => {
  console.log("Cleaning up database after all tests");
  await cleanupDatabase(context.db);
});

// Add per-test hooks
group.each.setup(async (context) => {
  await context.db.beginTransaction();
});

group.each.teardown(async (context) => {
  await context.db.rollbackTransaction();
});

// Apply configuration to all tests
group.tap((test) => {
  test.timeout(5000);
});

Suite Class

Top-level container representing a collection of tests and groups for a specific testing category.

/**
 * A suite is a collection of tests created around a given testing type
 */
class Suite {
  /** The suite name */
  name: string;
  
  /** Add a test or group to this suite */
  add(testOrGroup: Test | Group): void;
  
  /** Enable bail mode for this suite */
  bail(toggle?: boolean): this;
  
  /** Set timeout for all tests in suite */
  timeout(duration: number): this;
  
  /** Set retry count for all tests in suite */
  retry(count: number): this;
  
  /** Register callback for group events */
  onGroup(callback: (group: Group) => void): void;
  
  /** Register callback for test events */
  onTest(callback: (test: Test) => void): void;
}

Usage Examples:

import { Suite, Group, Test, Emitter, Refiner } from "@japa/runner/core";

// Create suite
const emitter = new Emitter();
const refiner = new Refiner();
const suite = new Suite("Integration Tests", emitter, refiner);

// Configure suite
suite
  .timeout(30000)
  .bail(true);

// Listen to events
suite.onGroup((group) => {
  console.log(`Group added to suite: ${group.title}`);
});

suite.onTest((test) => {
  console.log(`Test added to suite: ${test.title}`);
});

// Add tests and groups
const apiGroup = new Group("API Tests", emitter, refiner);
suite.add(apiGroup);

const standaloneTest = new Test("standalone test", (test) => new TestContext(test), emitter, refiner);
suite.add(standaloneTest);

Emitter Class

Event emitter for communication between test runner components.

/**
 * Event emitter for test runner communication
 */
class Emitter {
  /** Listen to an event */
  on(event: string, callback: (...args: any[]) => void): void;
  
  /** Emit an event */
  emit(event: string, ...args: any[]): void;
  
  /** Listen to an event once */
  once(event: string, callback: (...args: any[]) => void): void;
  
  /** Remove event listener */
  off(event: string, callback: (...args: any[]) => void): void;
}

Usage Examples:

import { Emitter } from "@japa/runner/core";

const emitter = new Emitter();

// Listen to events
emitter.on("test:start", (payload) => {
  console.log(`Test started: ${payload.title}`);
});

emitter.on("test:end", (payload) => {
  const status = payload.hasError ? "FAILED" : "PASSED";
  console.log(`Test ${status}: ${payload.title}`);
});

// Emit events
emitter.emit("test:start", { title: "My Test" });
emitter.emit("test:end", { title: "My Test", hasError: false });

// One-time listener
emitter.once("runner:end", () => {
  console.log("All tests completed");
});

Types

Core Class Types

interface TestExecutor<Context, TestData> {
  (context: Context, done?: Function): void | Promise<void>;
}

interface ReporterContract {
  name: string;
  handler: (runner: Runner, emitter: Emitter) => void;
}

interface TestHooksHandler<Context> {
  (context: Context): void | Promise<void>;
}

interface TestHooksCleanupHandler<Context> {
  (context: Context): void | Promise<void>;
}

interface GroupHooksHandler<Context> {
  (context: Context): void | Promise<void>;
}

Summary and Result Types

interface RunnerSummary {
  hasError: boolean;
  aggregates: {
    passed: number;
    failed: number;
    skipped: number;
    todo: number;
    total: number;
  };
  failureTree: Array<{
    name: string;
    errors: Array<{ phase: string; error: Error }>;
    children: Array<{
      name: string;
      errors: Array<{ phase: string; error: Error }>;
    }>;
  }>;
}

docs

configuration.md

core-api.md

core-classes.md

factories.md

index.md

plugins.md

reporters.md

test-management.md

tile.json