Testing
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.
{
"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.
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
}
}
};
| Field | Description |
|---|---|
modules | Modules to load alongside your module during tests |
test.folder | Directory containing test files (defaults to test) |
test.setup | Async function that runs before tests — can return config overrides |
test.cleanup | Async 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.
InterfaceFunctioncalls reject with "Interface not implemented" unless a loaded module provides the implementationRegisteringProxy.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.
modules section.Writing Tests
Test files use standard Mocha syntax. Import interface functions directly and call them as you would in production code.
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.
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.
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.
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.
See also
- Creating a Module — Module structure and lifecycle
- Interfaces — Interface contracts that tests validate
Exporting Interfaces
Create interface contracts with typed proxies, implement them with concrete logic, and extract standalone packages when multiple implementations appear.
Interface Registry
Register interfaces and implementations in the official registry to make them discoverable through the AntelopeJS CLI.