Claude
Skills
Sign in
Back

effect-testing

Included with Lifetime
$97 forever

Comprehensive testing patterns for Effect-TS services, errors, layers, and effects. Use this skill when writing tests for Effect-based code.

Writing & Docs

What this skill does


# Effect-TS Testing Patterns

Comprehensive testing patterns for Effect-TS services, errors, layers, and effects. Use this skill when writing tests for Effect-based code.

## Core Testing Setup

### @effect/vitest Integration

```typescript
import { describe, it, expect } from "@effect/vitest"
import { Effect } from "effect"

// Basic test - it.effect provides TestContext automatically
it.effect("test name", () =>
  Effect.gen(function* () {
    const result = yield* someEffect
    expect(result).toBe(expected)
  })
)

// Test with layers - provide dependencies to all tests
it.layer(MyServiceLive)("test with service", () =>
  Effect.gen(function* () {
    const service = yield* MyService
    const result = yield* service.doSomething()
    expect(result).toBe(expected)
  })
)

// Scoped tests - automatically handles resource cleanup
it.scoped("test with resources", () =>
  Effect.gen(function* () {
    const resource = yield* acquireResource
    // resource automatically released after test
    yield* useResource(resource)
  })
)
```

**Key Features:**
- `it.effect` - automatic TestContext provision (TestClock, TestRandom, etc.)
- `it.layer` - provide layers to test suite, shared across tests
- `it.scoped` - automatic resource cleanup
- Full fiber dumps with causes, spans, and logs for better errors

## Testing Services

### Mock Service with Layer.succeed

```typescript
import { Effect, Context, Layer } from "effect"

// Service definition
class DatabaseService extends Context.Tag("DatabaseService")<
  DatabaseService,
  {
    readonly query: (sql: string) => Effect.Effect<unknown>
  }
>() {}

// Live implementation (production)
export const DatabaseServiceLive = Layer.succeed(
  DatabaseService,
  {
    query: (sql) => Effect.promise(() => realDatabase.query(sql))
  }
)

// Test implementation (mocked)
export const DatabaseServiceTest = Layer.succeed(
  DatabaseService,
  {
    query: (sql) => Effect.succeed({ rows: [{ id: 1, name: "test" }] })
  }
)

// Usage in test
it.layer(DatabaseServiceTest)("queries database", () =>
  Effect.gen(function* () {
    const db = yield* DatabaseService
    const result = yield* db.query("SELECT * FROM users")
    expect(result).toEqual({ rows: [{ id: 1, name: "test" }] })
  })
)
```

**Convention:** Use "Live" suffix for production, "Test" suffix for mocks.

### Mock Service with Layer.mock

```typescript
import { Layer } from "effect"

// Partial mock - only implement methods you need
const PartialDatabaseMock = Layer.mock(DatabaseService, {
  query: (sql) => Effect.succeed({ rows: [] })
  // Other methods throw UnimplementedError when called
})

// Full mock with test doubles
const MockWithSpy = Layer.succeed(DatabaseService, {
  query: vi.fn().mockReturnValue(Effect.succeed({ rows: [] }))
})
```

### Testing Services with Dependencies

```typescript
class UserService extends Context.Tag("UserService")<
  UserService,
  {
    readonly getUser: (id: number) => Effect.Effect<User, UserNotFound>
  }
>() {}

class EmailService extends Context.Tag("EmailService")<
  EmailService,
  {
    readonly sendEmail: (to: string, body: string) => Effect.Effect<void>
  }
>() {}

// Service that depends on other services
class NotificationService extends Context.Tag("NotificationService")<
  NotificationService,
  {
    readonly notifyUser: (userId: number) => Effect.Effect<void, UserNotFound>
  }
>() {
  static Live = Layer.effect(
    NotificationService,
    Effect.gen(function* () {
      const users = yield* UserService
      const email = yield* EmailService

      return {
        notifyUser: (userId) =>
          Effect.gen(function* () {
            const user = yield* users.getUser(userId)
            yield* email.sendEmail(user.email, "Notification")
          })
      }
    })
  )
}

// Test with all dependencies mocked
const TestLayer = Layer.mergeAll(
  UserServiceTest,
  EmailServiceTest
).pipe(Layer.provideMerge(NotificationService.Live))

it.layer(TestLayer)("sends notification", () =>
  Effect.gen(function* () {
    const notif = yield* NotificationService
    yield* notif.notifyUser(1)
    // Verify email was sent using mocked EmailService
  })
)
```

## Testing Errors

### Expected Error Testing

```typescript
import { Effect, Exit } from "effect"

class MyError extends Data.TaggedError("MyError")<{
  readonly message: string
}> {}

it.effect("handles expected errors", () =>
  Effect.gen(function* () {
    const result = yield* Effect.exit(
      Effect.fail(new MyError({ message: "test error" }))
    )

    // Check error occurred
    expect(Exit.isFailure(result)).toBe(true)

    // Check error type
    if (Exit.isFailure(result)) {
      const cause = result.cause
      expect(Cause.isFailType(cause)).toBe(true)

      // Extract error value
      const error = Cause.failureOption(cause)
      expect(error).toBeSome()
      expect(Option.getOrThrow(error)).toBeInstanceOf(MyError)
    }
  })
)

// Alternative: use catchTag to verify error
it.effect("catches specific error type", () =>
  Effect.gen(function* () {
    let caught = false

    yield* effectThatFails.pipe(
      Effect.catchTag("MyError", (error) =>
        Effect.sync(() => {
          caught = true
          expect(error.message).toBe("test error")
        })
      )
    )

    expect(caught).toBe(true)
  })
)
```

### Multiple Error Types

```typescript
type UserServiceError = UserNotFound | DatabaseError | ValidationError

it.effect("handles multiple error types", () =>
  Effect.gen(function* () {
    const result = yield* Effect.either(service.getUser(999))

    expect(Either.isLeft(result)).toBe(true)

    if (Either.isLeft(result)) {
      // Pattern match on error type
      const error = result.left
      if (error._tag === "UserNotFound") {
        expect(error.userId).toBe(999)
      }
    }
  })
)
```

### Defect Testing (Unexpected Errors)

```typescript
it.effect("handles unexpected errors (defects)", () =>
  Effect.gen(function* () {
    const result = yield* Effect.exit(
      Effect.die(new Error("Unexpected"))
    )

    expect(Exit.isFailure(result)).toBe(true)

    if (Exit.isFailure(result)) {
      expect(Cause.isDie(result.cause)).toBe(true)
    }
  })
)
```

## Testing with TestClock

```typescript
import { TestClock } from "effect"

it.effect("delays execution", () =>
  Effect.gen(function* () {
    let executed = false

    // Fork effect that delays 1 second
    const fiber = yield* Effect.fork(
      Effect.delay("1 second")(
        Effect.sync(() => { executed = true })
      )
    )

    // Verify not executed yet
    expect(executed).toBe(false)

    // Advance time by 1 second
    yield* TestClock.adjust("1 second")

    // Wait for fiber to complete
    yield* Fiber.join(fiber)

    // Verify executed after time advance
    expect(executed).toBe(true)
  })
)

// Testing intervals
it.effect("processes scheduled tasks", () =>
  Effect.gen(function* () {
    const results: number[] = []

    const fiber = yield* Effect.fork(
      Effect.repeat(
        Effect.sync(() => results.push(Date.now())),
        Schedule.spaced("100 millis")
      ).pipe(Effect.timeout("1 second"))
    )

    // Advance time in increments
    yield* TestClock.adjust("100 millis")
    yield* TestClock.adjust("100 millis")
    yield* TestClock.adjust("100 millis")

    yield* Fiber.join(fiber)

    expect(results.length).toBeGreaterThan(0)
  })
)
```

## Testing with TestRandom

```typescript
import { TestRandom } from "effect"

it.effect("deterministic random values", () =>
  Effect.gen(function* () {
    // Set fixed random seed for reproducibility
    yield* TestRandom.setSeed(42)

    const random1 = yield* Random.next
    const random2 = yield* Random.next

    // Reset seed - same values again
    yield* TestRandom.setSeed(42)
    const random3 = yield* Random.next

    expect(random3).toBe(random1)
  })
)
```

## Testing Layers

### Fresh Layers Per Test

```typescript
// Helper to create fresh layer for each test
const makeFreshL

Related in Writing & Docs