Skip to content

Testing

Alchemy provides test utilities that integrate with Bun and Vitest, wrapping their test APIs with Effect support and stack lifecycle management.

The test harness provides Effect-aware versions of test, beforeAll, afterAll, and expect:

import { beforeAll, deploy, expect, test } from "alchemy/Test/Bun";

Each test(name, effect) runs an Effect generator instead of an async function. The harness provides platform layers (HttpClient, etc.) automatically.

deploy(Stack) returns an Effect that plans and applies a stack, resolving to its outputs. destroy(Stack) tears it down:

import { afterAll, beforeAll, deploy, destroy } from "alchemy/Test/Bun";
import Stack from "../alchemy.run.ts";
const stack = beforeAll(deploy(Stack));
afterAll.skipIf(!process.env.CI)(destroy(Stack));
  • beforeAll(effect) runs the Effect once before all tests and returns a lazy accessor
  • afterAll.skipIf(!process.env.CI) skips destroy locally for fast iteration
  • On CI (CI=true), the stack is torn down after tests complete

Use yield* stack inside a test to get the deployed stack’s outputs:

test(
"worker is reachable",
Effect.gen(function* () {
const { url } = yield* stack;
expect(url).toBeString();
}),
);

HttpClient is provided automatically by the test harness:

import * as HttpClient from "effect/unstable/http/HttpClient";
import * as HttpBody from "effect/unstable/http/HttpBody";
test(
"PUT and GET round-trip",
Effect.gen(function* () {
const { url } = yield* stack;
const put = yield* HttpClient.put(`${url}/hello.txt`, {
body: HttpBody.text("Hello!"),
});
expect(put.status).toBe(201);
const get = yield* HttpClient.get(`${url}/hello.txt`);
expect(yield* get.text).toBe("Hello!");
}),
);

Tests use the same state management as production deploys. By default, LocalState persists to .alchemy/ so subsequent test runs reuse existing resources (making re-runs fast).

For unit testing providers, Alchemy provides an in-memory state store:

import * as TestState from "alchemy/Test/TestState";
// Start with empty state
const state = TestState.defaultState;
// Or seed with existing resources
const state = TestState.state({
MyResource: {
/* ResourceState */
},
});

The test harness includes a TestCli that auto-approves plans and suppresses interactive prompts. This is provided automatically — you don’t need to configure it.

Alchemy also supports Vitest with the same API:

import { beforeAll, deploy, expect, test } from "alchemy/Test/Vitest";

The Vitest harness provides identical functionality with Vitest’s test runner instead of Bun’s.