# Testing

> This skill should be used when the user asks about "Effect testing", "@effect/vitest", "it.effect", "it.live", "it.scoped", "it.layer", "it.prop", "Schema Arbitrary", "property-based testing", "fast-check", "TestClock", "testing effects", "mocking services", "test layers", "TestContext", "Effect.provide test", "time testing", "Effect test utilities", "unit testing Effect", "generating test data",…

- **Type:** Skill
- **Install:** `agentstack add skill-andrueandersoncs-claude-skill-effect-ts-testing`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [andrueandersoncs](https://agentstack.voostack.com/s/andrueandersoncs)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [andrueandersoncs](https://github.com/andrueandersoncs)
- **Source:** https://github.com/andrueandersoncs/claude-skill-effect-ts/tree/main/skills/testing

## Install

```sh
agentstack add skill-andrueandersoncs-claude-skill-effect-ts-testing
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Testing in Effect

## Overview

Effect testing uses **`@effect/vitest`** as the standard test runner integration. This package provides Effect-aware test functions that handle Effect execution, scoped resources, layer composition, and TestClock injection automatically.

**The two pillars of Effect testing that enable 100% test coverage:**

1. **Service-Oriented Architecture** — Every external/effectful dependency (API calls, databases, file systems, third-party services, clocks, random number generators) MUST be wrapped in an Effect Service using `Context.Tag`. Tests provide test implementations via Layers, giving you complete control over all I/O and side effects.

2. **Schema-Driven Property Testing** — Since every data type has a Schema, every data type can generate test data via `Arbitrary`. This makes property-based testing the primary approach for verifying domain logic across thousands of automatically generated inputs.

Together, these two pillars mean: **services eliminate external dependencies from tests, and Arbitrary eliminates hand-crafted test data.** The result is fast, deterministic, comprehensive tests with 100% coverage.

**Core testing tools:**

- **@effect/vitest** - Effect-native test runner (`it.effect`, `it.scoped`, `it.live`, `it.layer`, `it.prop`)
- **Effect Services + Test Layers** - Replace ALL external dependencies with test doubles via `Context.Tag` and `it.layer`
- **Schema.Arbitrary** - Generate test data from any Schema (primary approach — never hand-craft test data)
- **Property Testing** - Test invariants with generated data via `it.prop` or fast-check
- **TestClock** - Control time in tests (automatically provided by `it.effect`)

## The Service-Oriented Testing Pattern (CRITICAL)

**This is the most important testing pattern in Effect.** Every external or effectful operation MUST be wrapped in a Service so that tests can provide a test implementation. This is how you achieve 100% test coverage without hitting real APIs, databases, or file systems.

### The Rule

> **If it makes a network call, reads from disk, talks to a database, calls a third-party API, generates random values, or performs any I/O — it MUST be behind an Effect Service.**

### Why Services Are Required for Testing

Without services, your code is **untestable** because it directly depends on external systems:

```typescript
// ❌ UNTESTABLE: Direct API call baked into business logic
const getUser = (id: string) =>
  Effect.tryPromise({
    try: () => fetch(`/api/users/${id}`).then((r) => r.json()),
    catch: (error) => new NetworkError({ cause: error }),
  });

// ❌ UNTESTABLE: Direct database access
const saveOrder = (order: Order) =>
  Effect.tryPromise({
    try: () => db.query("INSERT INTO orders ...", order),
    catch: (error) => new DatabaseError({ cause: error }),
  });
```

With services, your business logic is **pure and fully testable**:

```typescript
// ✅ TESTABLE: Service abstraction for API calls
class UserApi extends Context.Tag("UserApi") Effect.Effect;
    readonly saveUser: (user: User) => Effect.Effect;
  }
>() {}

// ✅ TESTABLE: Service abstraction for database
class OrderRepository extends Context.Tag("OrderRepository") Effect.Effect;
    readonly findById: (id: string) => Effect.Effect;
  }
>() {}

// ✅ Business logic is pure — depends only on service interfaces
const processOrder = (orderId: string) =>
  Effect.gen(function* () {
    const userApi = yield* UserApi;
    const orderRepo = yield* OrderRepository;

    const order = yield* orderRepo.findById(orderId);
    const user = yield* userApi.getUser(order.userId);
    // ... pure business logic using service abstractions
  });
```

### What MUST Be a Service

Every one of these MUST be wrapped in a `Context.Tag` service:

| External Dependency      | Service Example                                    |
| ------------------------ | -------------------------------------------------- |
| REST/GraphQL API calls   | `UserApi`, `PaymentGateway`, `NotificationService` |
| Database operations      | `UserRepository`, `OrderRepository`                |
| File system access       | `FileStorage`, `ConfigReader`                      |
| Third-party SDKs         | `StripeClient`, `SendGridClient`, `AwsS3Client`    |
| Email/SMS sending        | `EmailService`, `SmsService`                       |
| Message queues           | `EventPublisher`, `QueueConsumer`                  |
| Caching systems          | `CacheService`, `RedisClient`                      |
| Authentication providers | `AuthProvider`, `TokenService`                     |
| External clock/time      | Use Effect's built-in `Clock` service              |
| Random values            | Use Effect's built-in `Random` service             |

### Complete Service + Test Layer Pattern

```typescript
import { Context, Effect, Layer, Schema, Arbitrary } from "effect";
import { it, expect, layer } from "@effect/vitest";
import * as fc from "fast-check";

// 1. Define schemas for domain types
const TransactionId = Schema.String.pipe(
  Schema.pattern(/^txn_[a-zA-Z0-9]{16}$/),
  Schema.annotations({
    arbitrary: () => (fc) => fc.stringMatching(/^txn_[a-zA-Z0-9]{16}$/),
  }),
);

const PaymentStatus = Schema.Literal("succeeded", "pending", "failed");

class PaymentResult extends Schema.Class("PaymentResult")({
  transactionId: TransactionId,
  amount: Schema.Number.pipe(Schema.positive()),
  currency: Schema.Literal("usd", "eur", "gbp"),
  status: PaymentStatus,
}) {}

// 2. Define the service interface
class PaymentGateway extends Context.Tag("PaymentGateway") Effect.Effect;
    readonly refund: (transactionId: string) => Effect.Effect;
  }
>() {}

// 3. Live implementation (used in production)
const PaymentGatewayLive = Layer.succeed(PaymentGateway, {
  charge: (amount, currency) =>
    Effect.tryPromise({
      try: () => stripe.charges.create({ amount, currency }),
      catch: (error) => new PaymentError({ cause: error }),
    }),
  refund: (transactionId) =>
    Effect.tryPromise({
      try: () => stripe.refunds.create({ charge: transactionId }),
      catch: (error) => new RefundError({ cause: error }),
    }),
});

// 4. Test implementation using Arbitrary — generates varied test data
const PaymentGatewayTest = Layer.effect(
  PaymentGateway,
  Effect.sync(() => ({
    charge: (amount, currency) =>
      Effect.succeed(
        new PaymentResult({
          transactionId: fc.sample(Arbitrary.make(TransactionId)(fc), 1)[0],
          amount,
          currency: currency as "usd" | "eur" | "gbp",
          status: fc.sample(Arbitrary.make(PaymentStatus)(fc), 1)[0],
        }),
      ),
    refund: (_transactionId) => Effect.void,
  })),
);

// 5. Property test with the test layer — 100% coverage, zero external calls
layer(PaymentGatewayTest)("PaymentService", (it) => {
  it.effect.prop("should process payment for any valid amount", [Schema.Number.pipe(Schema.positive())], ([amount]) =>
    Effect.gen(function* () {
      const gateway = yield* PaymentGateway;
      const result = yield* gateway.charge(amount, "usd");
      expect(result.amount).toBe(amount);
      expect(["succeeded", "pending", "failed"]).toContain(result.status);
    }),
  );

  it.effect.prop("should handle refund for any transaction", [TransactionId], ([txnId]) =>
    Effect.gen(function* () {
      const gateway = yield* PaymentGateway;
      yield* gateway.refund(txnId);
      // No error = success
    }),
  );
});
```

### Stateful Test Layers (for Repository Testing)

For services that need to maintain state across operations within a test, use `Layer.effect` with `Ref`:

```typescript
import { Effect, Layer, Ref, Option } from "effect";

const OrderRepositoryTest = Layer.effect(
  OrderRepository,
  Effect.gen(function* () {
    const store = yield* Ref.make>(new Map());

    return {
      save: (order: Order) => Ref.update(store, (m) => new Map(m).set(order.id, order)),

      findById: (id: string) =>
        Effect.gen(function* () {
          const orders = yield* Ref.get(store);
          return yield* Option.match(Option.fromNullable(orders.get(id)), {
            onNone: () => Effect.fail(new OrderNotFound({ orderId: id })),
            onSome: Effect.succeed,
          }).pipe(Effect.flatten);
        }),

      findAll: () => Ref.get(store).pipe(Effect.map((m) => Array.from(m.values()))),
    };
  }),
);
```

### Composing Multiple Test Layers

Real tests often need multiple services. Compose test layers with `Layer.merge` and use Arbitrary for all test data:

```typescript
import { Schema, Arbitrary, Effect, Layer } from "effect";
import { it, expect, layer } from "@effect/vitest";
import * as fc from "fast-check";

// Define schemas for all domain types
const UserId = Schema.String.pipe(Schema.minLength(1));
const OrderId = Schema.String.pipe(Schema.minLength(1));

class OrderItem extends Schema.Class("OrderItem")({
  productId: Schema.String,
  price: Schema.Number.pipe(Schema.positive()),
  quantity: Schema.Number.pipe(Schema.int(), Schema.positive()),
}) {}

class Order extends Schema.Class("Order")({
  id: OrderId,
  userId: UserId,
  items: Schema.NonEmptyArray(OrderItem),
  total: Schema.Number.pipe(Schema.positive()),
}) {}

// Compose all test layers
const TestEnv = Layer.merge(UserApiTest, Layer.merge(OrderRepositoryTest, PaymentGatewayTest));

layer(TestEnv)("Order Processing", (it) => {
  it.effect.prop(
    "should process complete order flow for any user and order",
    [Arbitrary.make(UserId), Arbitrary.make(Order)],
    ([userId, order]) =>
      Effect.gen(function* () {
        const userApi = yield* UserApi;
        const orderRepo = yield* OrderRepository;
        const gateway = yield* PaymentGateway;

        // Full integration test with ALL services mocked + generated data
        const user = yield* userApi.getUser(userId);
        yield* orderRepo.save(order);
        const savedOrder = yield* orderRepo.findById(order.id);
        const payment = yield* gateway.charge(savedOrder.total, "usd");

        expect(payment.amount).toBe(savedOrder.total);
      }),
  );
});
```

### Anti-Pattern: Hard-Coded Service Mocks in Property Tests

**This is what NOT to do.** The following pattern defeats the purpose of property-based testing because it uses hard-coded values and `Effect.fail("Not implemented")` instead of using Arbitrary to generate varied test data:

```typescript
// ❌ WRONG: Hard-coded values and "Not implemented" errors
const defaultTestLayer = Layer.mergeAll(
  Layer.succeed(ImporterService, {
    import: (identifier: string) =>
      Effect.fail(
        new HalImportError({
          message: `No importer configured for: ${identifier}`,
          identifier,
        }),
      ),
  }),
  Layer.succeed(UserWalletService, {
    // ❌ Hard-coded address - every property test gets the same value
    getAddress: () => Effect.succeed("0xTestWallet1234567890123456789012345678"),
  }),
  Layer.succeed(BlockchainClientService, {
    // ❌ Hard-coded responses - no variation across test runs
    call: () => Effect.succeed("0x"),
    getLogs: () => Effect.succeed([]),
    // ❌ "Not implemented" - these won't be tested at all
    getTransactionTrace: () => Effect.fail(new Error("Not implemented")),
  }),
  Layer.succeed(ContractMetadataService, {
    getFunctionAbi: () => Effect.fail(new Error("Not implemented")),
    getEventAbi: () => Effect.fail(new Error("Not implemented")),
  }),
  Layer.succeed(TransactionService, {
    sendTransaction: () => Effect.fail(new Error("Not implemented")),
  }),
  Layer.succeed(SwapService, {
    executeSwap: () => Effect.fail(new Error("Not implemented")),
  }),
  Layer.succeed(CodeExecutionService, {
    execute: () => Effect.fail(new CodeExecutionNotImplementedError()),
  }),
);
```

**Why this is wrong:**

1. **Hard-coded values** - Property tests run the same inputs every time, missing edge cases
2. **`Effect.fail(new Error("Not implemented"))`** - These code paths are never exercised
3. **No Arbitrary** - The whole point of property testing is generating varied data

**The correct approach:** Use `Arbitrary` inside `Layer.effect` to generate different values for each property test run:

```typescript
import { Arbitrary, Schema, Effect, Layer, Ref } from "effect";
import * as fc from "fast-check";

// Define schemas for your service return types
const WalletAddress = Schema.String.pipe(
  Schema.pattern(/^0x[a-fA-F0-9]{40}$/),
  Schema.annotations({
    arbitrary: () => (fc) => fc.hexaString({ minLength: 40, maxLength: 40 }).map((hex) => `0x${hex}`),
  }),
);

const HexData = Schema.String.pipe(
  Schema.pattern(/^0x[a-fA-F0-9]*$/),
  Schema.annotations({
    arbitrary: () => (fc) => fc.hexaString({ minLength: 0, maxLength: 64 }).map((hex) => `0x${hex}`),
  }),
);

// ✅ CORRECT: Use Arbitrary to generate test data in the layer
const PropertyTestLayer = Layer.effect(
  UserWalletService,
  Effect.sync(() => {
    // Generate a random address for THIS test run
    const address = fc.sample(Arbitrary.make(WalletAddress)(fc), 1)[0];
    return {
      getAddress: () => Effect.succeed(address),
    };
  }),
);

// ✅ CORRECT: For stateful services, combine Ref with Arbitrary
const BlockchainClientTestLayer = Layer.effect(
  BlockchainClientService,
  Effect.gen(function* () {
    // Pre-generate test data for this test run
    const callResultArb = Arbitrary.make(HexData)(fc);
    const logsArb = Arbitrary.make(Schema.Array(EventLog))(fc);

    return {
      call: () => Effect.succeed(fc.sample(callResultArb, 1)[0]),
      getLogs: () => Effect.succeed(fc.sample(logsArb, 1)[0]),
      // ✅ Generate valid trace data instead of failing
      getTransactionTrace: () => Effect.succeed(fc.sample(Arbitrary.make(TransactionTrace)(fc), 1)[0]),
    };
  }),
);

// ✅ CORRECT: Full test layer with Arbitrary-generated data
const FullPropertyTestLayer = Layer.mergeAll(
  PropertyTestLayer,
  BlockchainClientTestLayer,
  // For services you actually want to test failure cases,
  // use Arbitrary to generate the ERROR data too:
  Layer.effect(
    ImporterService,
    Effect.sync(() => ({
      import: (identifier: string) =>
        // ✅ Either succeed with generated data OR fail with generated error
        fc.sample(fc.boolean(), 1)[0]
          ? Effect.succeed(fc.sample(Arbitrary.make(ImportResult)(fc), 1)[0])
          : Effect.fail(fc.sample(Arbitrary.make(HalImportError)(fc), 1)[0]),
    })),
  ),
);
```

**Key principles:**

1. **Every service method should return Arbitrary-generated data** - Not hard-coded strings
2. **Generate errors with Arbitrary too** - Error schemas should produce varied error cases
3. **Use `Layer.effect` + `Effect.sync`** - So each test run gets fresh generated values
4. **If a method "shouldn't be called"** - Either generate valid data anyway, or use a schema-based error

### Combining Services with Property Testing

The ultimate testing pattern: **service test layers + Schema Arbitrary.** This lets you test business logic across thousands of generated inputs with all external dependencies controlled:

```typescript
import { it, expect, layer } from "@effect/vitest";
import { Schema, Arbitrary, Effect } from "effect";

layer(TestEnv)("Order Processing Properties", (it) => {
  it.effect.prop("should calculate correct total for any valid order", [Arbitrary.make(Order)], ([order]) =>
    Effect.gen(function* () {
      const orderRepo = yield* OrderRepository;
      yield* orderRepo.save(order);
      const saved = yield* orderRepo.findById(order.id);
      expect(saved.total).toBe(order.items.reduce((sum, i) => sum + i.price, 0));
    }),
  );

  it.effect.prop("should never charge negative amounts", [Arbitrary.make(Order)], ([order]) =>
    Effect.gen(function* () {
      const gateway = yield* PaymentGateway;
      const result = yield* gateway.charge(order.total, "usd");
      expect(result.amount).toBeGreaterThanOrEqual(0);
    }),
  );
});
```

## Setup

Install `@effect/vitest` alongside vitest (v1.6

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [andrueandersoncs](https://github.com/andrueandersoncs)
- **Source:** [andrueandersoncs/claude-skill-effect-ts](https://github.com/andrueandersoncs/claude-skill-effect-ts)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-andrueandersoncs-claude-skill-effect-ts-testing
- Seller: https://agentstack.voostack.com/s/andrueandersoncs
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
