#Unit Tests
Alepha uses Vitest as the test runner. The alepha init --test flag scaffolds a Vitest configuration and a sample test file. All tests run with globals: true, so you do not need to import test, expect, or describe.
#Setup
Scaffold a project with test support:
alepha init my-app --test
This installs Vitest and creates a vitest.config.ts with two test projects:
- Node tests -- all
*.spec.tsfiles (default environment) - Browser tests -- all
*.browser.spec.ts/*.browser.spec.tsxfiles (jsdom environment)
Run tests:
yarn test # All tests
yarn w alepha test # Single package
yarn w alepha vitest run init.spec # Filtered by pattern
#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, t } from "alepha"; 2import { $action } from "alepha/server"; 3 4class UserController { 5 getUser = $action({ 6 path: "/users/:id", 7 schema: { params: t.object({ id: t.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 |
#Example: File System
1import { FileSystemProvider, MemoryFileSystemProvider } from "alepha/system"; 2 3const alepha = Alepha.create() 4 .with({ provide: FileSystemProvider, use: MemoryFileSystemProvider }); 5 6const fs = alepha.inject(MemoryFileSystemProvider); 7 8// Run code that writes files... 9await myService.generateReport();10 11// Assert with built-in helpers12expect(fs.wasWritten("/path/report.txt")).toBe(true);13expect(fs.wasWrittenMatching("/path/report.txt", /summary/)).toBe(true);14expect(fs.wasRead("/path/input.csv")).toBe(true);15expect(fs.wasDeleted("/path/temp.txt")).toBe(true);
#Example: Shell Commands
1import { ShellProvider, MemoryShellProvider } from "alepha/system"; 2 3const alepha = Alepha.create() 4 .with({ provide: ShellProvider, use: MemoryShellProvider }); 5 6const shell = alepha.inject(MemoryShellProvider); 7 8// Run code that executes shell commands... 9await myService.installDependencies();10 11expect(shell.wasCalled("yarn install")).toBe(true);
#Database Testing
Alepha uses real Postgres for tests, running via Docker (docker-compose). Each test file gets its own schema. Migrations run automatically before tests and the schema is dropped after tests complete.
The connection string is set in vitest.config.ts:
1env: {2 DATABASE_URL: "postgres://postgres:[email protected]: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);