effect-patterns-testing

0
0
Source

Effect-TS patterns for Testing. Use when working with testing in Effect-TS applications.

Install

mkdir -p .claude/skills/effect-patterns-testing && curl -L -o skill.zip "https://mcp.directory/api/skills/download/6402" && unzip -o skill.zip -d .claude/skills/effect-patterns-testing && rm skill.zip

Installs to .claude/skills/effect-patterns-testing

About this skill

Effect-TS Patterns: Testing

This skill provides 10 curated Effect-TS patterns for testing. Use this skill when working on tasks related to:

  • testing
  • Best practices in Effect-TS applications
  • Real-world patterns and solutions

🟢 Beginner Patterns

Your First Effect Test

Rule: Use Effect.runPromise in tests to run and assert on Effect results.

Good Example:

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")
  })
})

Rationale:

Test Effect programs by running them with Effect.runPromise and using standard test assertions on the results.


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


Test Effects with Services

Rule: Provide test implementations of services to make Effect programs testable.

Good Example:

import { describe, it, expect } from "vitest"
import { Effect, Context } from "effect"

// ============================================
// 1. Define a service
// ============================================

class UserRepository extends Context.Tag("UserRepository")<
  UserRepository,
  {
    readonly findById: (id: string) => Effect.Effect<User | null>
    readonly save: (user: User) => Effect.Effect<void>
  }
>() {}

interface User {
  id: string
  name: string
  email: string
}

// ============================================
// 2. Code that uses the service
// ============================================

const getUser = (id: string) =>
  Effect.gen(function* () {
    const repo = yield* UserRepository
    const user = yield* repo.findById(id)
    
    if (!user) {
      return yield* Effect.fail(new Error(`User ${id} not found`))
    }
    
    return user
  })

const createUser = (name: string, email: string) =>
  Effect.gen(function* () {
    const repo = yield* UserRepository
    
    const user: User = {
      id: crypto.randomUUID(),
      name,
      email,
    }
    
    yield* repo.save(user)
    return user
  })

// ============================================
// 3. Create a test implementation
// ============================================

const makeTestUserRepository = (initialUsers: User[] = []) => {
  const users = new Map(initialUsers.map(u => [u.id, u]))
  
  return UserRepository.of({
    findById: (id) => Effect.succeed(users.get(id) ?? null),
    save: (user) => Effect.sync(() => { users.set(user.id, user) }),
  })
}

// ============================================
// 4. Write tests
// ============================================

describe("User Service Tests", () => {
  it("should find an existing user", async () => {
    const testUser: User = {
      id: "123",
      name: "Alice",
      email: "alice@example.com",
    }
    
    const testRepo = makeTestUserRepository([testUser])
    
    const result = await Effect.runPromise(
      getUser("123").pipe(
        Effect.provideService(UserRepository, testRepo)
      )
    )
    
    expect(result).toEqual(testUser)
  })

  it("should fail when user not found", async () => {
    const testRepo = makeTestUserRepository([])
    
    await expect(
      Effect.runPromise(
        getUser("999").pipe(
          Effect.provideService(UserRepository, testRepo)
        )
      )
    ).rejects.toThrow("User 999 not found")
  })

  it("should create and save a user", async () => {
    const savedUsers: User[] = []
    
    const trackingRepo = UserRepository.of({
      findById: () => Effect.succeed(null),
      save: (user) => Effect.sync(() => { savedUsers.push(user) }),
    })
    
    const result = await Effect.runPromise(
      createUser("Bob", "bob@example.com").pipe(
        Effect.provideService(UserRepository, trackingRepo)
      )
    )
    
    expect(result.name).toBe("Bob")
    expect(result.email).toBe("bob@example.com")
    expect(savedUsers).toHaveLength(1)
    expect(savedUsers[0].name).toBe("Bob")
  })
})

Rationale:

When testing Effects that require services, provide test implementations using Effect.provideService or test layers.


Effect's service pattern makes testing easy:

  1. Declare dependencies - Effects specify what they need
  2. Inject test doubles - Provide fake implementations for tests
  3. No mocking libraries - Just provide different service implementations
  4. Type-safe - Compiler ensures you provide all dependencies


🟡 Intermediate Patterns

Accessing the Current Time with Clock

Rule: Use the Clock service to get the current time, enabling deterministic testing with TestClock.

Good Example:

This example shows a function that checks if a token is expired. Its logic depends on Clock, making it fully testable.

import { Effect, Clock, Duration } from "effect";

interface Token {
  readonly value: string;
  readonly expiresAt: number; // UTC milliseconds
}

// This function is pure and testable because it depends on Clock
const isTokenExpired = (
  token: Token
): Effect.Effect<boolean, never, Clock.Clock> =>
  Clock.currentTimeMillis.pipe(
    Effect.map((now) => now > token.expiresAt),
    Effect.tap((expired) =>
      Clock.currentTimeMillis.pipe(
        Effect.flatMap((currentTime) =>
          Effect.log(
            `Token expired? ${expired} (current time: ${new Date(currentTime).toISOString()})`
          )
        )
      )
    )
  );

// Create a test clock service that advances time
const makeTestClock = (timeMs: number): Clock.Clock => ({
  currentTimeMillis: Effect.succeed(timeMs),
  currentTimeNanos: Effect.succeed(BigInt(timeMs * 1_000_000)),
  sleep: (duration: Duration.Duration) => Effect.succeed(void 0),
  unsafeCurrentTimeMillis: () => timeMs,
  unsafeCurrentTimeNanos: () => BigInt(timeMs * 1_000_000),
  [Clock.ClockTypeId]: Clock.ClockTypeId,
});

// Create a token that expires in 1 second
const token = { value: "abc", expiresAt: Date.now() + 1000 };

// Check token expiry with different clocks
const program = Effect.gen(function* () {
  // Check with current time
  yield* Effect.log("Checking with current time...");
  yield* isTokenExpired(token);

  // Check with past time
  yield* Effect.log("\nChecking with past time (1 minute ago)...");
  const pastClock = makeTestClock(Date.now() - 60_000);
  yield* isTokenExpired(token).pipe(
    Effect.provideService(Clock.Clock, pastClock)
  );

  // Check with future time
  yield* Effect.log("\nChecking with future time (1 hour ahead)...");
  const futureClock = makeTestClock(Date.now() + 3600_000);
  yield* isTokenExpired(token).pipe(
    Effect.provideService(Clock.Clock, futureClock)
  );
});

// Run the program with default clock
Effect.runPromise(
  program.pipe(Effect.provideService(Clock.Clock, makeTestClock(Date.now())))
);

Anti-Pattern:

Directly calling Date.now() inside your business logic. This creates an impure function that cannot be tested reliably without manipulating the system clock, which is a bad practice.

import { Effect } from "effect";

interface Token {
  readonly expiresAt: number;
}

// ❌ WRONG: This function's behavior changes every millisecond.
const isTokenExpiredUnsafely = (token: Token): Effect.Effect<boolean> =>
  Effect.sync(() => Date.now() > token.expiresAt);

// Testing this function would require complex mocking of global APIs
// or would be non-deterministic.

Rationale:

Whenever you need to get the current ti


Content truncated.

You might also like

flutter-development

aj-geddes

Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.

643969

drawio-diagrams-enhanced

jgtolentino

Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.

591705

ui-ux-pro-max

nextlevelbuilder

"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."

318398

godot

bfollington

This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.

339397

nano-banana-pro

garg-aayush

Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.

451339

fastapi-templates

wshobson

Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.

304231

Stay ahead of the MCP ecosystem

Get weekly updates on new skills and servers.