alepha@docs:~/docs/guides/testing$
cat 1-unit-tests.md | pretty
2 min read
Last commit:

#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:

bash
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:

bash
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.

typescript
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.

typescript
 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.

typescript
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

typescript
 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

typescript
 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. 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:

typescript
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:

typescript
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);