effect-concurrency-testing
from front-depiction/claude-setup
Reusable Claude Code configuration for Effect TypeScript projects with specialized agents and skills
10 stars4 forksUpdated Jan 19, 2026
npx skills add https://github.com/front-depiction/claude-setup --skill effect-concurrency-testingSKILL.md
Effect Concurrency Testing Skill
This skill provides patterns for testing Effect's concurrency primitives: fibers, latches, deferreds, PubSub, SubscriptionRef, and streams.
Core Principles
CRITICAL: Choose the correct coordination primitive based on what you need to synchronize.
| Need | Use |
|---|---|
| Simple fiber yield | Effect.yieldNow |
| Forked PubSub subscriber ready | yieldNow after fork, yieldNow after each publish |
| Wait for subscriber ready | Deferred.make() + Deferred.await |
| Wait for stream element | Effect.makeLatch() + Stream.tap(() => latch.open) |
| Time-dependent behavior | TestClock.adjust |
| Verify events published | PubSub.subscribe + PubSub.takeAll |
| Check fiber status | fiber.unsafePoll() |
Fiber Coordination Patterns
Effect.yieldNow - Simple Fiber Scheduling
Use Effect.yieldNow when you need to allow other fibers to execute. This is preferred over TestClock.adjust for non-time-dependent code.
import { it } from "@effect/vitest"
import { Effect, Exit, Fiber } from "effect"
it.effect("fiber polling with yieldNow", () =>
Effect.gen(function* () {
const latch = yield* Effect.makeLatch()
const fiber = yield* latch.await.pipe(Effect.fork)
yield* Effect.yieldNow()
expect(fiber.unsafePoll()).toBeNull()
yield* latch.open
expect(yield* fiber.await).toEqual(Exit.void)
})
)
Latch - Explicit Coordination
Effect.makeLatch() creates a gate that blocks fibers until opened:
import { it } from "@effect/vitest"
import { Effect, Fiber } from "effect"
it.effect("latch coordination", () =>
Effect.gen(function* () {
const latch = yield* Effect.makeLatch()
const fiber = yield* Effect.gen(function* () {
yield* latch.await
return "completed"
}).pipe(Effect.fork)
yield* Effect.yieldNow()
expect(fiber.unsafePoll()).toBeNull()
yield* latch.open
const result = yield* Fiber.join(fiber)
expect(result).toBe("completed")
})
)
Latch Operations
import { Effect } from "effect"
declare const latch: Effect.Effect.Success<ReturnType<typeof Effect.makeLatch>>
latch.await // Wait until latch is open
latch.open // Open the latch (allows waiters through)
latch.close // Close the latch (blocks future waiters)
latch.release // Open once, then close
latch.whenOpen // Run effect only when latch is open
Deferred - Signal Readiness Between Fibers
Use Deferred when one fiber needs to signal another with a value:
import { it } from "@effect/vitest"
import { Effect, Deferred, Fiber } from "effect"
it.effect("deferred signaling", () =>
Effect.gen(function* () {
const signal = yield* Deferred.make<number>()
const consumer = yield* Effect.gen(function* () {
const value = yield* Deferred.await(signal)
return value * 2
}).pipe(Effect.fork)
yield* Deferred.succeed(signal, 21)
const result = yield* Fiber.join(consumer)
expect(result).toBe(42)
})
)
fiber.unsafePoll() - Check Completion Without Blocking
import { Effect, Exit, Fiber } from "effect"
declare const fiber: Fiber.RuntimeFiber<string>
fiber.unsafePoll()
// Returns null if running
// Returns Exit<A, E> if completed (success, failure, or interrupted)
// Check if still running
expect(fiber.unsafePoll()).toBeNull()
// Check if completed
expect(fiber.unsafePoll()).toBeDefined()
// Check specific completion
expect(fiber.unsafePoll()).toEqual(Exit.succeed("result"))
PubSub Event Testing
Direct Event Verification
Use Effect.scoped to manage PubSub subscription lifecycle:
import { it } from "@effect/vitest"
import { Effect, PubSub } from "effect"
it.effect("verify published events", () =>
Effect.gen(function* () {
const pubsub = yield* PubSub.unbounded<string>()
yield* Effect.scoped(
Effect.gen(function* () {
const sub = yield* PubSub.subscribe(pubsub)
yield* PubSub.publish(pubsub, "event-1")
yield* PubSub.publish(pubsub, "event-2")
const events = yield* PubSub.takeAll(sub)
expect(events).toEqual(["event-1", "event-2"])
})
)
})
)
Testing Event Publishers
When testing a service that publishes events:
import { it } from "@effect/vitest"
import { Effect, PubSub, Context, Layer } from "effect"
interface UserEvent {
readonly type: "created" | "deleted"
readonly userId: string
}
class EventBus extends Context.Tag("EventBus")<
EventBus,
PubSub.PubSub<UserEvent>
>() {}
class UserService extends Context.Tag("UserService")<
UserService,
{ readonly createUser: (id: string) => Effect.Effect<void> }
>() {}
declare const UserServiceLive: Layer.Layer<UserService, never, EventBus>
it.effect("should publish user created event", () =>
Effect.gen(function* () {
const pubsub = yield* PubSub.unbounded<UserEvent>()
yield* E
...
Repository Stats
Stars10
Forks4