Part 3: Testing
In Part 2 you deployed a Worker with R2 Bucket bindings. Now you’ll write integration tests that deploy the stack, hit the live Worker over HTTP, and verify it works.
Create the test file
Section titled “Create the test file”Alchemy ships test utilities for Bun that wrap bun:test with Effect
support. Configure providers (and state) once at the top of the file
with Test.make({...}) — the same Cloudflare.providers() /
Cloudflare.state() you used in your Stack:
import * as import Cloudflare
Cloudflare from "alchemy/Cloudflare";import * as import Test
Test from "alchemy/Test/Bun";import { const expect: Expect
Asserts that a value matches some criteria.
expect } from "bun:test";import * as import Effect
Effect from "effect/Effect";import import Stack
Stack from "../alchemy.run.ts";
const { const test: TestFn
test, const beforeAll: BeforeAllFn
beforeAll, const afterAll: AfterAllFn
afterAll, const deploy: <A>(stack: Test.TestEffect<CompiledStack<A>, Stage | AlchemyContext>, options?: { stage?: string;}) => ReturnType<typeof deploy>
deploy, const destroy: (stack: Test.TestEffect<CompiledStack, Stage | AlchemyContext>, options?: { stage?: string;}) => ReturnType<typeof destroy>
destroy } = import Test
Test.const make: <Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry>(options: Test.MakeOptions<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry>) => Test.TestApi
Build the per-file test API. Configure providers / state once at the top of
the test file:
import * as Test from "alchemy/Test/Bun";import * as Cloudflare from "alchemy/Cloudflare";
const { test, deploy, destroy, beforeAll, afterAll } = Test.make({ providers: Cloudflare.providers(), state: Cloudflare.state(),});
make({ MakeOptions<Providers | Provider<Command> | Provider<Random> | Credentials | CloudflareEnvironment | Access | Retry>.providers: Layer<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, StackServices>
Provider layer for the stack — e.g. AWS.providers(), Cloudflare.providers().
providers: import Cloudflare
Cloudflare.const providers: () => Layer<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, any>
Cloudflare providers, bindings, and credentials for Worker-based stacks.
providers(), MakeOptions<ROut = any>.state?: Layer<State, never, StackServices> | undefined
State store for top-level deploy(Stack) / destroy(Stack); defaults to
State.localState
.
state: import Cloudflare
Cloudflare.function state(props?: { workerName?: string; noTrack?: boolean;}): Layer<...>export state
state(),});expect (and any other bun:test helpers like describe) come
from bun:test directly — Test.make only provides the
Effect-aware test runner pieces.
Deploy the stack before tests
Section titled “Deploy the stack before tests”Use beforeAll with deploy to deploy your stack once before any
tests run:
import * as import Cloudflare
Cloudflare from "alchemy/Cloudflare";import * as import Test
Test from "alchemy/Test/Bun";import { const expect: Expect
Asserts that a value matches some criteria.
expect } from "bun:test";import * as import Effect
Effect from "effect/Effect";import import Stack
Stack from "../alchemy.run.ts";
const { const test: TestFn
test, const beforeAll: BeforeAllFn
beforeAll, const afterAll: AfterAllFn
afterAll, const deploy: <A>(stack: Test.TestEffect<CompiledStack<A>, Stage | AlchemyContext>, options?: { stage?: string;}) => ReturnType<typeof deploy>
deploy, const destroy: (stack: Test.TestEffect<CompiledStack, Stage | AlchemyContext>, options?: { stage?: string;}) => ReturnType<typeof destroy>
destroy } = import Test
Test.const make: <Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry>(options: Test.MakeOptions<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry>) => Test.TestApi
Build the per-file test API. Configure providers / state once at the top of
the test file:
import * as Test from "alchemy/Test/Bun";import * as Cloudflare from "alchemy/Cloudflare";
const { test, deploy, destroy, beforeAll, afterAll } = Test.make({ providers: Cloudflare.providers(), state: Cloudflare.state(),});
make({ MakeOptions<Providers | Provider<Command> | Provider<Random> | Credentials | CloudflareEnvironment | Access | Retry>.providers: Layer<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, StackServices>
Provider layer for the stack — e.g. AWS.providers(), Cloudflare.providers().
providers: import Cloudflare
Cloudflare.const providers: () => Layer<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, any>
Cloudflare providers, bindings, and credentials for Worker-based stacks.
providers(), MakeOptions<ROut = any>.state?: Layer<State, never, StackServices> | undefined
State store for top-level deploy(Stack) / destroy(Stack); defaults to
State.localState
.
state: import Cloudflare
Cloudflare.function state(props?: { workerName?: string; noTrack?: boolean;}): Layer<...>export state
state(),});
const const stack: Effect.Effect<never, never, never>
stack = const beforeAll: BeforeAllFn<never>(eff: Test.TestEffect<never, never>, options?: test.HookOptions) => Effect.Effect<never, never, never>
beforeAll(const deploy: <unknown>(stack: Test.TestEffect<CompiledStack<unknown, any>, Stage | AlchemyContext>, options?: { stage?: string;}) => Effect.Effect<never, Stage | AlchemyContext | PlatformError | InvalidReferenceError | MissingSourceError | StateStoreError, FileSystem | Path | AlchemyContext | Cli | State>
deploy(import Stack
Stack));deploy(Stack) returns an Effect that plans and applies the stack.
beforeAll runs it once, then returns a lazy accessor you can
yield* inside each test to get the stack outputs.
Access the stack outputs
Section titled “Access the stack outputs”Write your first test. Use yield* stack to get the outputs you
returned from your Stack in Part 2:
import * as import Cloudflare
Cloudflare from "alchemy/Cloudflare";import * as import Test
Test from "alchemy/Test/Bun";import { const expect: Expect
Asserts that a value matches some criteria.
expect } from "bun:test";import * as import Effect
Effect from "effect/Effect";import import Stack
Stack from "../alchemy.run.ts";
const { const test: TestFn
test, const beforeAll: BeforeAllFn
beforeAll, const afterAll: AfterAllFn
afterAll, const deploy: <A>(stack: Test.TestEffect<CompiledStack<A>, Stage | AlchemyContext>, options?: { stage?: string;}) => ReturnType<typeof deploy>
deploy, const destroy: (stack: Test.TestEffect<CompiledStack, Stage | AlchemyContext>, options?: { stage?: string;}) => ReturnType<typeof destroy>
destroy } = import Test
Test.const make: <Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry>(options: Test.MakeOptions<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry>) => Test.TestApi
Build the per-file test API. Configure providers / state once at the top of
the test file:
import * as Test from "alchemy/Test/Bun";import * as Cloudflare from "alchemy/Cloudflare";
const { test, deploy, destroy, beforeAll, afterAll } = Test.make({ providers: Cloudflare.providers(), state: Cloudflare.state(),});
make({ MakeOptions<Providers | Provider<Command> | Provider<Random> | Credentials | CloudflareEnvironment | Access | Retry>.providers: Layer<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, StackServices>
Provider layer for the stack — e.g. AWS.providers(), Cloudflare.providers().
providers: import Cloudflare
Cloudflare.const providers: () => Layer<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, any>
Cloudflare providers, bindings, and credentials for Worker-based stacks.
providers(), MakeOptions<ROut = any>.state?: Layer<State, never, StackServices> | undefined
State store for top-level deploy(Stack) / destroy(Stack); defaults to
State.localState
.
state: import Cloudflare
Cloudflare.function state(props?: { workerName?: string; noTrack?: boolean;}): Layer<...>export state
state(),});
const const stack: Effect.Effect<never, never, never>
stack = const beforeAll: BeforeAllFn<never>(eff: Test.TestEffect<never, never>, options?: test.HookOptions) => Effect.Effect<never, never, never>
beforeAll(const deploy: <unknown>(stack: Test.TestEffect<CompiledStack<unknown, any>, Stage | AlchemyContext>, options?: { stage?: string;}) => Effect.Effect<never, Stage | AlchemyContext | PlatformError | InvalidReferenceError | MissingSourceError | StateStoreError, FileSystem | Path | AlchemyContext | Cli | State>
deploy(import Stack
Stack));
const test: TestFn(name: string, eff: Test.TestEffect<void>, options?: TestOptions) => void
test( "worker returns a url", import Effect
Effect.const gen: <Effect.Effect<never, never, never>, void>(f: () => Generator<Effect.Effect<never, never, never>, void, never>) => Effect.Effect<void, never, never> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
gen allows you to write code that looks and behaves like synchronous
code, but it can handle asynchronous tasks, errors, and complex control flow
(like loops and conditions). It helps make asynchronous code more readable
and easier to manage.
The generator functions work similarly to async/await but with more
explicit control over the execution of effects. You can yield* values from
effects and return the final result at the end.
gen(function* () { const { const url: never
url } = yield* const stack: Effect.Effect<never, never, never>
stack; function expect(actual?: never, customFailMessage?: string): Matchers<undefined> (+2 overloads)
expect(const url: never
url).MatchersBuiltin<undefined>.toBeString(): void
Asserts that a value is a string.
toBeString(); }),);test(name, effect) wraps bun:test — you write an Effect generator
instead of an async function.
Run the tests
Section titled “Run the tests”bun test test/integ.test.tsThe first run deploys the stack (or reuses the existing one if already deployed). Subsequent runs are fast because Alchemy diffs and skips unchanged resources.
Add HTTP assertions
Section titled “Add HTTP assertions”The basic test just checks that a URL exists. Let’s verify the Worker actually handles requests:
import * as import Cloudflare
Cloudflare from "alchemy/Cloudflare";import * as import Test
Test from "alchemy/Test/Bun";import { const expect: Expect
Asserts that a value matches some criteria.
expect } from "bun:test";import * as import Effect
Effect from "effect/Effect";import * as import HttpBody
HttpBody from "effect/unstable/http/HttpBody";import * as import HttpClient
HttpClient from "effect/unstable/http/HttpClient";import import Stack
Stack from "../alchemy.run.ts";
const { const test: TestFn
test, const beforeAll: BeforeAllFn
beforeAll, const afterAll: AfterAllFn
afterAll, const deploy: <A>(stack: Test.TestEffect<CompiledStack<A>, Stage | AlchemyContext>, options?: { stage?: string;}) => ReturnType<typeof deploy>
deploy, const destroy: (stack: Test.TestEffect<CompiledStack, Stage | AlchemyContext>, options?: { stage?: string;}) => ReturnType<typeof destroy>
destroy } = import Test
Test.const make: <Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry>(options: Test.MakeOptions<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry>) => Test.TestApi
Build the per-file test API. Configure providers / state once at the top of
the test file:
import * as Test from "alchemy/Test/Bun";import * as Cloudflare from "alchemy/Cloudflare";
const { test, deploy, destroy, beforeAll, afterAll } = Test.make({ providers: Cloudflare.providers(), state: Cloudflare.state(),});
make({ MakeOptions<Providers | Provider<Command> | Provider<Random> | Credentials | CloudflareEnvironment | Access | Retry>.providers: Layer<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, StackServices>
Provider layer for the stack — e.g. AWS.providers(), Cloudflare.providers().
providers: import Cloudflare
Cloudflare.const providers: () => Layer<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, any>
Cloudflare providers, bindings, and credentials for Worker-based stacks.
providers(), MakeOptions<ROut = any>.state?: Layer<State, never, StackServices> | undefined
State store for top-level deploy(Stack) / destroy(Stack); defaults to
State.localState
.
state: import Cloudflare
Cloudflare.function state(props?: { workerName?: string; noTrack?: boolean;}): Layer<...>export state
state(),});
const const stack: Effect.Effect<never, never, never>
stack = const beforeAll: BeforeAllFn<never>(eff: Test.TestEffect<never, never>, options?: test.HookOptions) => Effect.Effect<never, never, never>
beforeAll(const deploy: <unknown>(stack: Test.TestEffect<CompiledStack<unknown, any>, Stage | AlchemyContext>, options?: { stage?: string;}) => Effect.Effect<never, Stage | AlchemyContext | PlatformError | InvalidReferenceError | MissingSourceError | StateStoreError, FileSystem | Path | AlchemyContext | Cli | State>
deploy(import Stack
Stack));
const test: TestFn(name: string, eff: Test.TestEffect<void>, options?: TestOptions) => void
test( "worker returns a url", import Effect
Effect.const gen: <Effect.Effect<never, never, never>, void>(f: () => Generator<Effect.Effect<never, never, never>, void, never>) => Effect.Effect<void, never, never> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
gen allows you to write code that looks and behaves like synchronous
code, but it can handle asynchronous tasks, errors, and complex control flow
(like loops and conditions). It helps make asynchronous code more readable
and easier to manage.
The generator functions work similarly to async/await but with more
explicit control over the execution of effects. You can yield* values from
effects and return the final result at the end.
gen(function* () { const { const url: never
url } = yield* const stack: Effect.Effect<never, never, never>
stack; function expect(actual?: never, customFailMessage?: string): Matchers<undefined> (+2 overloads)
expect(const url: never
url).MatchersBuiltin<undefined>.toBeString(): void
Asserts that a value is a string.
toBeString(); }),);const test: TestFn(name: string, eff: Test.TestEffect<void>, options?: TestOptions) => void
test( "PUT and GET round-trip an object", import Effect
Effect.const gen: <Effect.Effect<string, HttpClientError, never> | Effect.Effect<HttpClientResponse, HttpClientError, HttpClient.HttpClient>, void>(f: () => Generator<Effect.Effect<string, HttpClientError, never> | Effect.Effect<HttpClientResponse, HttpClientError, HttpClient.HttpClient>, void, never>) => Effect.Effect<void, HttpClientError, HttpClient.HttpClient> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
gen allows you to write code that looks and behaves like synchronous
code, but it can handle asynchronous tasks, errors, and complex control flow
(like loops and conditions). It helps make asynchronous code more readable
and easier to manage.
The generator functions work similarly to async/await but with more
explicit control over the execution of effects. You can yield* values from
effects and return the final result at the end.
gen(function* () { const { const url: never
url } = yield* const stack: Effect.Effect<never, never, never>
stack; const const put: HttpClientResponse
put = yield* import HttpClient
HttpClient.const put: (url: string | URL, options?: Options.NoUrl | undefined) => Effect.Effect<HttpClientResponse, HttpClientError, HttpClient.HttpClient>
put(`${const url: never
url}/hello.txt`, { body?: HttpBody.HttpBody | undefined
body: import HttpBody
HttpBody.const text: (body: string, contentType?: string) => HttpBody.Uint8Array
text("Hello, World!"), }); expect<number>(actual: number, customFailMessage?: string): Matchers<number> (+2 overloads)
expect(const put: HttpClientResponse
put.HttpClientResponse.status: number
status).MatchersBuiltin<number>.toBe(expected: number): void (+1 overload)
Asserts that a value equals what is expected.
- For non-primitive values, like objects and arrays,
use
toEqual() instead.
- For floating-point numbers, use
toBeCloseTo() instead.
toBe(201); const const get: HttpClientResponse
get = yield* import HttpClient
HttpClient.const get: (url: string | URL, options?: Options.NoUrl | undefined) => Effect.Effect<HttpClientResponse, HttpClientError, HttpClient.HttpClient>
get(`${const url: never
url}/hello.txt`); expect<string>(actual: string, customFailMessage?: string): Matchers<string> (+2 overloads)
expect(yield* const get: HttpClientResponse
get.HttpIncomingMessage<HttpClientError>.text: Effect.Effect<string, HttpClientError, never>
text).MatchersBuiltin<string>.toBe(expected: string): void (+1 overload)
Asserts that a value equals what is expected.
- For non-primitive values, like objects and arrays,
use
toEqual() instead.
- For floating-point numbers, use
toBeCloseTo() instead.
toBe("Hello, World!"); }),); const test: TestFn(name: string, eff: Test.TestEffect<void>, options?: TestOptions) => void
test( "GET missing key returns 404", import Effect
Effect.const gen: <Effect.Effect<HttpClientResponse, HttpClientError, HttpClient.HttpClient>, void>(f: () => Generator<Effect.Effect<HttpClientResponse, HttpClientError, HttpClient.HttpClient>, void, never>) => Effect.Effect<void, HttpClientError, HttpClient.HttpClient> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
gen allows you to write code that looks and behaves like synchronous
code, but it can handle asynchronous tasks, errors, and complex control flow
(like loops and conditions). It helps make asynchronous code more readable
and easier to manage.
The generator functions work similarly to async/await but with more
explicit control over the execution of effects. You can yield* values from
effects and return the final result at the end.
gen(function* () { const { const url: never
url } = yield* const stack: Effect.Effect<never, never, never>
stack; const const response: HttpClientResponse
response = yield* import HttpClient
HttpClient.const get: (url: string | URL, options?: Options.NoUrl | undefined) => Effect.Effect<HttpClientResponse, HttpClientError, HttpClient.HttpClient>
get(`${const url: never
url}/no-such-key`); expect<number>(actual: number, customFailMessage?: string): Matchers<number> (+2 overloads)
expect(const response: HttpClientResponse
response.HttpClientResponse.status: number
status).MatchersBuiltin<number>.toBe(expected: number): void (+1 overload)
Asserts that a value equals what is expected.
- For non-primitive values, like objects and arrays,
use
toEqual() instead.
- For floating-point numbers, use
toBeCloseTo() instead.
toBe(404); }),);HttpClient is provided automatically by the test harness — no
extra setup needed.
Destroy after tests on CI
Section titled “Destroy after tests on CI”Right now the stack stays deployed after tests finish. That’s great locally — you can re-run tests instantly against the already-deployed stack. But on CI you want to clean up.
Add afterAll with destroy, using skipIf to only tear down when
CI is set:
import * as import Cloudflare
Cloudflare from "alchemy/Cloudflare";import * as import Test
Test from "alchemy/Test/Bun";import { const expect: Expect
Asserts that a value matches some criteria.
expect } from "bun:test";import * as import Effect
Effect from "effect/Effect";import * as import HttpBody
HttpBody from "effect/unstable/http/HttpBody";import * as import HttpClient
HttpClient from "effect/unstable/http/HttpClient";import import Stack
Stack from "../alchemy.run.ts";
const { const test: TestFn
test, const beforeAll: BeforeAllFn
beforeAll, const afterAll: AfterAllFn
afterAll, const deploy: <A>(stack: Test.TestEffect<CompiledStack<A>, Stage | AlchemyContext>, options?: { stage?: string;}) => ReturnType<typeof deploy>
deploy, const destroy: (stack: Test.TestEffect<CompiledStack, Stage | AlchemyContext>, options?: { stage?: string;}) => ReturnType<typeof destroy>
destroy } = import Test
Test.const make: <Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry>(options: Test.MakeOptions<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry>) => Test.TestApi
Build the per-file test API. Configure providers / state once at the top of
the test file:
import * as Test from "alchemy/Test/Bun";import * as Cloudflare from "alchemy/Cloudflare";
const { test, deploy, destroy, beforeAll, afterAll } = Test.make({ providers: Cloudflare.providers(), state: Cloudflare.state(),});
make({ MakeOptions<Providers | Provider<Command> | Provider<Random> | Credentials | CloudflareEnvironment | Access | Retry>.providers: Layer<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, StackServices>
Provider layer for the stack — e.g. AWS.providers(), Cloudflare.providers().
providers: import Cloudflare
Cloudflare.const providers: () => Layer<Cloudflare.Providers | Provider<Command> | Provider<Random> | Cloudflare.Credentials | Cloudflare.CloudflareEnvironment | Access | Retry, never, any>
Cloudflare providers, bindings, and credentials for Worker-based stacks.
providers(), MakeOptions<ROut = any>.state?: Layer<State, never, StackServices> | undefined
State store for top-level deploy(Stack) / destroy(Stack); defaults to
State.localState
.
state: import Cloudflare
Cloudflare.function state(props?: { workerName?: string; noTrack?: boolean;}): Layer<...>export state
state(),});
const const stack: Effect.Effect<never, never, never>
stack = const beforeAll: BeforeAllFn<never>(eff: Test.TestEffect<never, never>, options?: test.HookOptions) => Effect.Effect<never, never, never>
beforeAll(const deploy: <unknown>(stack: Test.TestEffect<CompiledStack<unknown, any>, Stage | AlchemyContext>, options?: { stage?: string;}) => Effect.Effect<never, Stage | AlchemyContext | PlatformError | InvalidReferenceError | MissingSourceError | StateStoreError, FileSystem | Path | AlchemyContext | Cli | State>
deploy(import Stack
Stack));
const afterAll: AfterAllFn
afterAll.AfterAllFn.skipIf: (predicate: boolean) => (eff: Test.TestEffect<any>, options?: test.HookOptions) => void
skipIf(!var process: NodeJS.Process
process.NodeJS.Process.env: NodeJS.ProcessEnv
The process.env property returns an object containing the user environment.
See environ(7).
An example of this object looks like:
{ TERM: 'xterm-256color', SHELL: '/usr/local/bin/bash', USER: 'maciej', PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin', PWD: '/Users/maciej', EDITOR: 'vim', SHLVL: '1', HOME: '/Users/maciej', LOGNAME: 'maciej', _: '/usr/local/bin/node'}
It is possible to modify this object, but such modifications will not be
reflected outside the Node.js process, or (unless explicitly requested)
to other Worker threads.
In other words, the following example would not work:
Terminal window node -e 'process.env.foo = "bar"' && echo $foo
While the following will:
import { env } from 'node:process';
env.foo = 'bar';console.log(env.foo);
Assigning a property on process.env will implicitly convert the value
to a string. This behavior is deprecated. Future versions of Node.js may
throw an error when the value is not a string, number, or boolean.
import { env } from 'node:process';
env.test = null;console.log(env.test);// => 'null'env.test = undefined;console.log(env.test);// => 'undefined'
Use delete to delete a property from process.env.
import { env } from 'node:process';
env.TEST = 1;delete env.TEST;console.log(env.TEST);// => undefined
On Windows operating systems, environment variables are case-insensitive.
import { env } from 'node:process';
env.TEST = 1;console.log(env.test);// => 1
Unless explicitly specified when creating a Worker instance,
each Worker thread has its own copy of process.env, based on its
parent thread's process.env, or whatever was specified as the env option
to the Worker constructor. Changes to process.env will not be visible
across Worker threads, and only the main thread can make changes that
are visible to the operating system or to native add-ons. On Windows, a copy of process.env on a Worker instance operates in a case-sensitive manner
unlike the main thread.
env.string | undefined
CI)(const destroy: (stack: Test.TestEffect<CompiledStack, Stage | AlchemyContext>, options?: { stage?: string;}) => ReturnType<typeof destroy>
destroy(import Stack
Stack));
const test: TestFn(name: string, eff: Test.TestEffect<void>, options?: TestOptions) => void
test(/* .. */);
const test: TestFn(name: string, eff: Test.TestEffect<void>, options?: TestOptions) => void
test(/* .. */);- Locally —
CIis not set, soskipIfskips the destroy. You iterate fast against the live stack. - On CI — set
CI=trueand the stack is torn down automatically after tests complete.
You now have:
Test.make({ providers, state })to wire your provider Layer and state store into the test runner once per filebeforeAll(deploy(Stack))to deploy once before testsyield* stackto access outputs in each test- HTTP assertions using Effect’s
HttpClient afterAll.skipIf(!process.env.CI)(destroy(Stack))for automatic cleanup on CI with fast iteration locally
In Part 4, you’ll run your stack locally with
alchemy dev for instant feedback during development.