Effect-TS Testing
Basic Test Structure
import { describe, it, expect } from "vitest"
import { Effect, Layer } from "effect"
import { Foo, FooLive } from "../services/Foo.js"
describe("Foo", () => {
it("does the thing", async () => {
const result = await Effect.runPromise(
Effect.gen(function* () {
const foo = yield* Foo
return yield* foo.doThing()
}).pipe(Effect.provide(testLayer))
)
expect(result).toBe(expected)
})
})
Mock Services with Layer.succeed
const MockDep = Layer.succeed(Dep, {
getValue: () => Effect.succeed("mock-value"),
save: () => Effect.succeed(undefined),
})
Composing Test Layers
const testLayer = FooLive.pipe(
Layer.provide(MockDep),
Layer.provide(MockConfig),
)
Testing Errors
it("fails with NotFound", async () => {
const exit = await Effect.runPromiseExit(
Effect.gen(function* () {
const foo = yield* Foo
return yield* foo.getById("nonexistent")
}).pipe(Effect.provide(testLayer))
)
expect(exit._tag).toBe("Failure")
})
Config in Tests
const MockConfig = Layer.succeed(ConfigService, {
database: { path: ":memory:" },
server: { port: 0 },
// ... minimal config for the test
})
Rules
- Every test layer must provide ALL dependencies the service needs
- Use
Layer.succeedfor mocks — notLayer.effect - Do NOT use
Effect.runSyncfor async operations in tests - Test timeout is 10 seconds — keep tests fast
- Name test files
*.test.tsinsrc/__tests__/
Converted and distributed by TomeVault — claim your Tome and manage your conversions.