| title |
Your First Effect Test |
| id |
testing-hello-world |
| skillLevel |
beginner |
| applicationPatternId |
testing |
| summary |
Write your first test for an Effect program using Vitest and Effect's testing utilities. |
| tags |
testing |
vitest |
getting-started |
|
| rule |
| description |
Use Effect.runPromise in tests to run and assert on Effect results. |
|
| author |
PaulJPhilp |
| related |
testing-with-services |
testing-mock-dependencies |
|
| lessonOrder |
2 |
Test Effect programs by running them with Effect.runPromise and using standard test assertions on the results.
Testing Effect code is straightforward:
- Effects are values - Build them in tests like any other value
- Run to get results - Use
Effect.runPromise to execute
- Assert normally - Standard assertions work on the results
import { describe, it, expect } from "vitest"
import { Effect } from "effect"
// ============================================
// Code to test
// ============================================
const add = (a: number, b: number): Effect.Effect<number> =>
Effect.succeed(a + b)
const divide = (a: number, b: number): Effect.Effect<number, Error> =>
b === 0
? Effect.fail(new Error("Cannot divide by zero"))
: Effect.succeed(a / b)
const fetchUser = (id: string): Effect.Effect<{ id: string; name: string }> =>
Effect.succeed({ id, name: `User ${id}` })
// ============================================
// Tests
// ============================================
describe("Basic Effect Tests", () => {
it("should add two numbers", async () => {
const result = await Effect.runPromise(add(2, 3))
expect(result).toBe(5)
})
it("should divide numbers", async () => {
const result = await Effect.runPromise(divide(10, 2))
expect(result).toBe(5)
})
it("should fail on divide by zero", async () => {
await expect(Effect.runPromise(divide(10, 0))).rejects.toThrow(
"Cannot divide by zero"
)
})
it("should fetch a user", async () => {
const user = await Effect.runPromise(fetchUser("123"))
expect(user).toEqual({
id: "123",
name: "User 123",
})
})
})
// ============================================
// Testing Effect.gen programs
// ============================================
const calculateDiscount = (price: number, quantity: number) =>
Effect.gen(function* () {
if (price <= 0) {
return yield* Effect.fail(new Error("Invalid price"))
}
const subtotal = price * quantity
const discount = quantity >= 10 ? 0.1 : 0
const total = subtotal * (1 - discount)
return { subtotal, discount, total }
})
describe("Effect.gen Tests", () => {
it("should calculate without discount", async () => {
const result = await Effect.runPromise(calculateDiscount(10, 5))
expect(result.subtotal).toBe(50)
expect(result.discount).toBe(0)
expect(result.total).toBe(50)
})
it("should apply bulk discount", async () => {
const result = await Effect.runPromise(calculateDiscount(10, 10))
expect(result.subtotal).toBe(100)
expect(result.discount).toBe(0.1)
expect(result.total).toBe(90)
})
it("should fail for invalid price", async () => {
await expect(
Effect.runPromise(calculateDiscount(-5, 10))
).rejects.toThrow("Invalid price")
})
})
| Scenario |
Approach |
| Success case |
await Effect.runPromise(effect) then assert |
| Failure case |
expect(...).rejects.toThrow() |
| Multiple effects |
Test each independently |
| Effect.gen |
Same as above - it's still an Effect |
- One assertion per test - Clear failure messages
- Test success and failure - Both paths matter
- Descriptive names - Explain what's being tested
- Arrange-Act-Assert - Clear test structure