#Unit Tests
Alepha uses Vitest as the test runner. Every alepha init scaffolds a Vitest config and a sample test file — Vitest ships embedded in alepha, so there is nothing to install. All tests run with globals: true, so you do not need to import test, expect, or describe.
#Setup
Every Alepha project is scaffolded with test support — no flag needed:
alepha init my-app
init writes a vitest.config.ts (it pins test.root so a parent monorepo config can't take over) and a starter test/dummy.spec.ts. Specs live in test/, named *.spec.ts.
Run tests with alepha test. Positional arguments are forwarded to Vitest as filename/test filters:
alepha test # All tests
alepha test auth # Only specs matching "auth"
alepha test test/user.spec.ts # A single file
#Lifecycle Management
Alepha.create() handles start and stop automatically in test environments. You do not need beforeAll/afterAll for lifecycle.
1test("should inject a service", async () => {2 const alepha = Alepha.create().with(MyService);3 const svc = alepha.inject(MyService);4 await alepha.start();5 6 const result = await svc.doSomething();7 expect(result).toBe("done");8});
#Testing Actions
Actions expose two call modes: .run() for in-process invocation and .fetch() for HTTP simulation.
1import { Alepha, z } from "alepha"; 2import { $action } from "alepha/server"; 3 4class UserController { 5 getUser = $action({ 6 path: "/users/:id", 7 schema: { params: z.object({ id: z.uuid() }) }, 8 handler: async ({ params }) => ({ id: params.id, name: "Alice" }), 9 });10}11 12test("should return user by id", async () => {13 const alepha = Alepha.create().with(UserController);14 const ctrl = alepha.inject(UserController);15 16 // Local call - no HTTP overhead17 const result = await ctrl.getUser.run({ params: { id: "abc-123" } });18 expect(result.name).toBe("Alice");19 20 // HTTP simulation - goes through the full request pipeline21 const response = await ctrl.getUser.fetch({ params: { id: "abc-123" } });22 expect(response.status).toBe(200);23});
#DI Substitution (No vi.mock)
Never use vi.mock() or vi.spyOn(). Alepha's dependency injection system replaces the need for traditional mocking. Use .with({ provide, use }) to swap implementations.
1const alepha = Alepha.create()2 .with({ provide: PaymentService, use: FakePaymentService });3 4const svc = alepha.inject(PaymentService);5// svc is now an instance of FakePaymentService
#Memory Providers
Alepha ships memory implementations for all I/O-bound services. These run in-process with no external dependencies and include test assertion helpers.
| Provider | Import | Replaces |
|---|---|---|
MemoryFileSystemProvider |
alepha/system |
File system |
MemoryShellProvider |
alepha/system |
Shell commands |
MemoryQueueProvider |
alepha/queue |
Job queues |
MemoryTopicProvider |
alepha/topic |
Pub/sub topics |
MemoryLockProvider |
alepha/lock |
Distributed locks |
MemoryCacheProvider |
alepha/cache |
Caching layer |
MemoryFileStorageProvider |
alepha/bucket |
File storage (S3, R2, etc.) |
MemoryEmailProvider |
alepha/email |
Email sending |
MemorySmsProvider |
alepha/sms |
SMS sending |
In test environments, FileSystemProvider and ShellProvider both default to
their memory implementations automatically — file writes and shell commands
stay inside the container unless a test opts back into the real thing:
1import { FileSystemProvider, NodeFileSystemProvider } from "alepha/system";2 3// Only when a test really needs the disk (e.g. real fixture files):4const alepha = Alepha.create()5 .with({ provide: FileSystemProvider, use: NodeFileSystemProvider });
#Example: File System
1import { MemoryFileSystemProvider } from "alepha/system"; 2 3const alepha = Alepha.create(); // memory file system is the test default 4 5const fs = alepha.inject(MemoryFileSystemProvider); 6 7// Run code that writes files... 8await myService.generateReport(); 9 10// Assert with built-in helpers11expect(fs.wasWritten("/path/report.txt")).toBe(true);12expect(fs.wasWrittenMatching("/path/report.txt", /summary/)).toBe(true);13expect(fs.wasDeleted("/path/temp.txt")).toBe(true);
#Example: Shell Commands
1import { MemoryShellProvider } from "alepha/system"; 2 3const alepha = Alepha.create(); // memory shell is the test default 4 5const shell = alepha.inject(MemoryShellProvider); 6 7// Run code that executes shell commands... 8await myService.installDependencies(); 9 10expect(shell.wasCalled("yarn install")).toBe(true);11 12// Structured results work too: a configured error becomes exitCode 1,13// mirroring ShellProvider.capture() on the real runtimes.14shell.errors.set("git diff --quiet", "dirty");15const result = await shell.capture("git diff --quiet");16expect(result.exitCode).toBe(1);
#Database Testing
Alepha can run tests against real Postgres. Each test file gets its own schema. Migrations run automatically before tests and the schema is dropped after tests complete.
Point DATABASE_URL at your test database via the env block of vitest.config.ts (the scaffolded config doesn't set one — without it, tests use the default embedded SQLite database):
1env: {2 DATABASE_URL: "postgres://postgres:postgres@127.0.0.1:5432/postgres",3}
#TestProvider Pattern
To unit test protected methods on a class, create a test subclass that exposes them:
1class TestCliProvider extends CliProvider {2 public testParseFlags = this.parseFlags.bind(this);3 public testResolveCommand = this.resolveCommand.bind(this);4}5 6const alepha = Alepha.create();7const cli = alepha.inject(TestCliProvider);8const result = cli.testParseFlags(["--verbose"], flagDefs);