1---
2title: Test harness
3description: Reference for alchemy/Test — every helper, hook, and option exposed by Test.make for Bun and Vitest.
4---
5
6`alchemy/Test/Bun` and `alchemy/Test/Vitest` expose the same
7Effect-aware harness. For the end-to-end walkthrough, see
8[Testing a Stack](/testing/testing-a-stack); for provider-lifecycle
9testing, see [Testing Providers](/testing/testing-providers).
10
11## What Test.make returns
12
13A single call returns a self-contained API for the file:
14
15```typescript
16const { test, beforeAll, beforeEach, afterAll, afterEach, deploy, destroy } =
17 Test.make({
18 providers: Cloudflare.providers(),
19 state: Cloudflare.state(),
20 });
21```
22
23| Helper | Purpose |
24|---|---|
25| `test(name, effect)` | Effect-aware test. `HttpClient` and your providers Layer are in scope. |
26| `test.skip` / `test.skipIf` / `test.only` / `test.todo` | Skip / focus / todo modifiers (same shape as `bun.test`). |
27| `test.provider(name, fn)` | [Provider-lifecycle test](/testing/testing-providers) against a scratch in-memory stack. |
28| `beforeAll(effect)` | Run an Effect once. Returns a lazy accessor (`yield* result`) usable inside tests. |
29| `beforeEach(effect)` | Run an Effect before every test. |
30| `afterAll(effect)` / `afterAll.skipIf(predicate)` | Cleanup hook with conditional teardown. |
31| `afterEach(effect)` | Run an Effect after every test. |
32| `deploy(Stack, opts?)` | Plan + apply a stack, resolve to its outputs. |
33| `destroy(Stack, opts?)` | Plan + apply against an empty desired state. |
34
35`Test.getWhenReady` / `Test.executeWhenReady` are module-level HTTP
36cold-start helpers, taught in [Testing a Stack](/testing/testing-a-stack).
37
38`expect` (and `describe`) come from the underlying runner —
39`bun:test` or `@effect/vitest` — directly.
40
41## Test.make options
42
43```typescript
44Test.make({
45 providers, // required
46 state, // optional
47 profile, // optional
48 stage, // optional
49 dev, // optional — run the suite against local emulators
50 adopt, // optional
51 sidecar, // optional — defaults to dev
52});
53```
54
55### `providers` (required)
56
57The provider Layer that resolves resource implementations.
58Usually the same one your `Stack` uses:
59
60```typescript
61providers: Cloudflare.providers(),
62// or merge multiple:
63providers: Layer.mergeAll(Cloudflare.providers(), Stripe.providers()),
64```
65
66Credentials resolve through the same `AuthProviders` registry as
67`alchemy deploy`, so tests pick up stored profiles or
68the env-var auth methods registered by each provider.
69
70### `state`
71
72The state store used by top-level `deploy(Stack)` and
73`destroy(Stack)` (not by `test.provider`). Defaults to
74`localState()` — `.alchemy/` on disk.
75
76```typescript
77state: Cloudflare.state(), // R2-backed, survives across CI runners
78state: localState({ path: ".alchemy-test/" }), // separate dir
79state: undefined, // omit → defaults to localState()
80```
81
82Persistent state lets `deploy(Stack)` skip recreating unchanged
83resources between runs. The run-against-an-existing-stack pattern
84built on it lives in [Testing a Stack](/testing/testing-a-stack).
85
86### `profile`
87
88Override `ALCHEMY_PROFILE` for this file only. Useful for
89pinning tests to a sandbox profile regardless of what's set in
90the environment:
91
92```typescript
93Test.make({
94 providers: AWS.providers(),
95 profile: "test-sandbox",
96});
97```
98
99When omitted, the harness reads `ALCHEMY_PROFILE` from env / `.env`
100the same way the CLI does.
101
102### `stage`
103
104Default stage for `deploy(Stack)` / `destroy(Stack)`. Defaults to
105`test_$USER` (e.g. `test_sam`) so two people running the same suite
106against one account don't collide. Override per file, or per call:
107
108```typescript
109Test.make({ providers, stage: "ci-pr-42" });
110
111// or per-call:
112beforeAll(deploy(Stack, { stage: "ci-pr-42" }));
113afterAll.skipIf(!process.env.CI)(destroy(Stack, { stage: "ci-pr-42" }));
114```
115
116A unique stage per PR or test run lets multiple suites run in
117parallel against the same provider account without colliding.
118
119### `dev`
120
121Off by default — tests deploy to the real cloud. `dev: true` runs
122the whole file in local-dev mode, the same wiring as
123[`alchemy dev`](/environments/local-development): Cloudflare
124Workers, Durable Objects, KV, R2, D1, Queues, and Workflows run in
125workerd with local simulators, and AWS Lambda/ECS plus the
126emulated AWS surface run in a local Docker emulator. No cloud
127account is touched for emulated resources.
128
129```typescript
130Test.make({
131 providers: Cloudflare.providers(),
132 dev: true, // deploy(Stack) boots workerd / Docker simulators
133});
134```
135
136Everything else in the file is unchanged — `beforeAll(deploy(Stack))`
137boots the stack locally and the outputs (`url`, ids) point at the
138local instances, so the same HTTP assertions run against
139`http://localhost:<port>`. Local resource ids are `dev:`-prefixed,
140which doubles as proof no cloud call ran. `Alchemy.remote()` still
141pins individual resources live, exactly as it does under
142`alchemy dev`.
143
144When omitted, the flag falls back to the `ALCHEMY_DEV` env var —
145leave it out of the file and set `ALCHEMY_DEV=1` locally to run the
146same suite against emulators while CI runs it live. Separately,
147`ALCHEMY_TEST_DEV=1` **overrides** the option in both directions —
148use it to force an entire existing suite through local providers
149without editing each `Test.make`.
150
151What each cloud emulates is covered in
152[Cloudflare local development](/cloudflare/local-development) and
153[AWS local development](/aws/local-development); the step-by-step
154walkthrough is [Cloudflare Tutorial Part 4](/cloudflare/tutorial/part-4).
155
156### `adopt`
157
158Engine-level adoption policy for the run, matching the CLI's
159`--adopt` flag. When `true`, a resource with no prior state whose
160physical counterpart already exists in the cloud is adopted via
161`provider.read` instead of failing. Defaults to `false`.
162
163### `sidecar`
164
165Only meaningful in dev mode, and defaults to the resolved `dev`
166flag: dev tests run local providers behind the same RPC sidecar
167process the real `alchemy dev` command uses. `sidecar: false` runs
168them in-process instead — useful when debugging provider code,
169since there's no child process between you and the breakpoint.
170
171## Hooks
172
173### `beforeAll(effect) → Effect.Effect<A>`
174
175Runs the Effect once before any test in the file. Stores the
176result and returns a lazy accessor — `yield* accessor` inside
177any test or other hook returns the resolved value:
178
179```typescript
180const stack = beforeAll(deploy(Stack));
181const seed = beforeAll(Effect.gen(function* () {
182 yield* DynamoDB.putItem({ /* ... */ });
183 return Date.now();
184}));
185
186test(
187 "uses both",
188 Effect.gen(function* () {
189 const { url } = yield* stack;
190 const startedAt = yield* seed;
191 /* ... */
192 }),
193);
194```
195
196Default timeout is **120s**. Override with the second argument:
197
198```typescript
199beforeAll(deploy(Stack), { timeout: 300_000 });
200```
201
202### `beforeEach(effect)`
203
204Runs the Effect before every test. No accessor returned — for
205side-effect setup only (truncate a table, reset a feature flag,
206…).
207
208### `afterAll(effect)` and `afterAll.skipIf(predicate)`
209
210Cleanup hook with conditional teardown:
211
212```typescript
213afterAll(destroy(Stack)); // always destroy
214afterAll.skipIf(!process.env.CI)(destroy(Stack)); // CI only
215afterAll.skipIf(true)(destroy(Stack)); // never (debugging)
216```
217
218`afterAll.skipIf(true)` short-circuits without registering a
219hook at all — there's no risk of an `Effect` being constructed
220and dropped.
221
222### `afterEach(effect)`
223
224Runs after every test. Combine with `beforeEach` for
225test-isolated fixtures.
226
227## Test variants
228
229```typescript
230test.skip("not ready yet", Effect.gen(function* () { /* ... */ }));
231
232test.skipIf(process.env.CI)(
233 "local-only smoke test",
234 Effect.gen(function* () { /* ... */ }),
235);
236
237test.only(
238 "the one I'm debugging",
239 Effect.gen(function* () { /* ... */ }),
240);
241
242test.todo("backfill once R2 has multipart helper");
243```
244
245`test.provider` mirrors the same shape (semantics in
246[Testing Providers](/testing/testing-providers)):
247
248```typescript
249test.provider.skip(name, fn);
250test.provider.skipIf(condition)(name, fn);
251```
252
253:::note[Vitest differences]
254On Vitest, `test.only` is supported but `test.todo` is a stub
255(maps to `it.todo`). `test.skip` / `test.skipIf` work identically.
256:::
257
258## HttpClient is in scope
259
260`HttpClient` is wired into every `test` Effect, so you can call
261it directly:
262
263```typescript
264import * as HttpClient from "effect/http/HttpClient";
265
266test(
267 "health check",
268 Effect.gen(function* () {
269 const { url } = yield* stack;
270 const res = yield* HttpClient.get(`${url}/health`);
271 expect(res.status).toBe(200);
272 }),
273);
274```
275
276The implementation comes from
277`effect/http/FetchHttpClient` — same client the CLI
278uses. For a full PUT/GET round-trip against a deployed stack,
279see [Testing a Stack → Drive the live URL](/testing/testing-a-stack).
280
281## Bun vs Vitest
282
283The two adapters expose the **same API**:
284
285```diff lang="typescript"
286-import * as Test from "alchemy/Test/Bun";
287-import { expect } from "bun:test";
288+import * as Test from "alchemy/Test/Vitest";
289+import { expect } from "@effect/vitest";
290
291const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
292 providers: Cloudflare.providers(),
293 state: Cloudflare.state(),
294});
295```
296
297- **Bun** uses `bun:test` directly. Every `test(...)` becomes a
298 `bun.test(...)` call wrapped with `Effect.runPromise`.
299- **Vitest** uses `@effect/vitest`'s `it.live`, so Effect-aware
300 tests stay first-class. Default hook timeout is the same
301 (120s).
302
303Pick whichever runner your project already uses; nothing in the
304test code changes.
305
306## Where next
307
308- [Testing a Stack](/testing/testing-a-stack) — deploy once, drive the live URL, tear down.
309- [Testing Providers](/testing/testing-providers) — exercise create / update / replace / delete with `test.provider`.
310- [Testing](/testing) — the testing overview.
311- [State Store](/state-store) — choosing between `localState()`, `Cloudflare.state()`, and friends.
312- [Profiles](/environments/profiles) — how `ALCHEMY_PROFILE` and the `profile` factory option resolve credentials.