Module Development

Testing

Test Antelopejs modules with the built-in test runner — configure the test environment, write isolated tests, and validate interface implementations.

Running Tests

The CLI includes a test runner that boots the module in an isolated environment, loads dependencies, and executes test files.

ajs module test

To run a specific test file instead of the entire suite:

ajs module test -f test/specific.test.ts

The test runner compiles TypeScript, initializes the module lifecycle, runs all matching test files, and reports results. The runner tears down the environment after tests complete.

Test Configuration

Point your module to a test configuration file by adding the test field in the antelopeJs section of package.json.

package.json
{
  "antelopeJs": {
    "test": "antelope.test.ts"
  }
}

The test configuration file defines which modules to load, how to set them up, and how to clean up after tests finish.

antelope.test.ts
import { defineConfig } from "@antelopejs/interface-core/config";

export default {
  modules: {
    // Modules needed for testing
    "db-module": {
      source: { type: "local", path: "../db-module" },
      config: { connectionString: "mongodb://localhost/test" }
    }
  },
  test: {
    folder: "test",
    setup: async () => {
      // Run before tests — return config overrides if needed
      return {
        modules: { /* additional overrides */ }
      };
    },
    cleanup: async () => {
      // Run after tests — clean up test resources
    }
  }
};
FieldDescription
modulesModules to load alongside your module during tests
test.folderDirectory containing test files (defaults to test)
test.setupAsync function that runs before tests — can return config overrides
test.cleanupAsync function that runs after all tests complete

The setup function runs after modules are loaded but before test files execute. Use it to seed test data, start mock services, or apply configuration overrides. The cleanup function runs after all tests finish, regardless of pass or fail status.

Test File Conventions

Test files must match the pattern *.test.ts or *.spec.ts. Place them in the test/ directory or the directory specified by test.folder in your test configuration.

my-module/
├── test/
│   ├── user-service.test.ts
│   ├── auth.test.ts
│   └── helpers/
│       └── fixtures.ts
└── antelope.test.ts

Helper files and fixtures that do not match the test pattern are not executed as tests. You can import them from your test files to share setup logic or test data.

Stub Mode

During testing, the framework enables stub mode by default. Stub mode enforces strict interface resolution so that unimplemented interfaces fail loudly instead of silently returning undefined.

  • InterfaceFunction calls reject with "Interface not implemented" unless a loaded module provides the implementation
  • RegisteringProxy.register() throws an error if no module handles the registration

This behavior forces you to explicitly load all required modules in your test configuration. Tests become isolated and predictable because every dependency must be declared upfront.

If a test fails with "Interface not implemented", check that the module providing that interface is listed in your test configuration's modules section.

Writing Tests

Test files use standard Mocha syntax. Import interface functions directly and call them as you would in production code.

test/user-service.test.ts
import { expect } from "chai";
import { GetUser, CreateUser } from "@antelopejs/interface-user-service";

describe("User Service", () => {
  it("should create and retrieve a user", async () => {
    const user = await CreateUser("Alice", "[email protected]");
    expect(user.name).to.equal("Alice");

    const retrieved = await GetUser(user.id);
    expect(retrieved.id).to.equal(user.id);
  });
});

Tests call the same interface functions that consumers use in production. The test runner loads your module's implementation and wires it to the interface proxies, so tests exercise the real code path through the interface layer.

The test runner uses Mocha under the hood. You can use any assertion library compatible with Mocha, including Chai, Node's built-in assert, or any other library that throws on failure.

Interface Tests

Standalone interface packages can include their own test files in a tests/ directory. These tests define the expected behavior of the contract — any module that implements the interface must pass them.

@antelopejs/interface-email/
├── src/
│   ├── index.ts              # Interface contract
│   └── tests/
│       └── send.test.ts      # Contract tests
└── dist/
    └── tests/
        └── send.test.js      # Compiled tests

When you run ajs module test on an implementing module, the test runner automatically discovers and runs these interface tests. The runner reads the implements array from the module's package.json, locates each interface package, and looks for compiled test files in dist/tests/. The runner then combines the interface tests with the module's own local tests and executes them all together.

Test execution order:
1. Local tests from the module's test/ directory
2. Interface tests from each package listed in implements

This mechanism ensures that every implementation satisfies the contract defined by the interface. The interface author writes the tests once, and every implementing module runs them automatically.

When creating a standalone interface package, include tests that verify the contract behavior. These tests act as a compliance suite — any module claiming to implement the interface must pass them.

Implementation-Specific Tests

A module can include its own tests alongside the interface tests. Local tests in the module's test/ directory cover implementation-specific behavior that the interface contract does not define.

my-email-module/
├── test/
│   ├── retry-logic.test.ts     # Implementation-specific
│   └── rate-limiting.test.ts   # Implementation-specific
└── package.json                # implements: ["@antelopejs/interface-email"]

The test runner collects both sets of test files and runs them in a single Mocha session. Interface tests verify that the contract is fulfilled, and local tests verify that implementation details work correctly.

Testing with Dependencies

When your module depends on interfaces from other modules, declare those modules in the test configuration. The test runner loads them and resolves all interface bindings before running tests.

antelope.test.ts
export default {
  modules: {
    "auth-module": {
      source: { type: "local", path: "../auth-module" },
      config: { secretKey: "test-secret" }
    },
    "db-module": {
      source: { type: "local", path: "../db-module" },
      config: { connectionString: "mongodb://localhost/test" }
    }
  },
  test: {
    folder: "test",
    cleanup: async () => {
      // Drop test database after tests
    }
  }
};

Each module in the modules section receives its own config object. Use test-specific values — test database URLs, mock API keys, or reduced timeouts — to keep tests fast and isolated from production data.

Keep test configurations minimal. Only load the modules your tests actually depend on. Fewer modules mean faster test startup and clearer failure messages when something breaks.

See also