--- 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 --- ## Guideline Test Effect programs by running them with `Effect.runPromise` and using standard test assertions on the results. --- ## Rationale Testing Effect code is straightforward: 1. **Effects are values** - Build them in tests like any other value 2. **Run to get results** - Use `Effect.runPromise` to execute 3. **Assert normally** - Standard assertions work on the results --- ## Good Example ```typescript import { describe, it, expect } from "vitest" import { Effect } from "effect" // ============================================ // Code to test // ============================================ const add = (a: number, b: number): Effect.Effect => Effect.succeed(a + b) const divide = (a: number, b: number): Effect.Effect => 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") }) }) ``` ## Testing Patterns | 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 | ## Best Practices 1. **One assertion per test** - Clear failure messages 2. **Test success and failure** - Both paths matter 3. **Descriptive names** - Explain what's being tested 4. **Arrange-Act-Assert** - Clear test structure