Skip to content

Instantly share code, notes, and snippets.

@dan-myles
Created January 15, 2026 21:55
Show Gist options
  • Select an option

  • Save dan-myles/5025ffe07df7a0b17e010573e333dda7 to your computer and use it in GitHub Desktop.

Select an option

Save dan-myles/5025ffe07df7a0b17e010573e333dda7 to your computer and use it in GitHub Desktop.
/**
* Minimal Effect test wrapper for Bun's native test runner.
*
* This provides `it.effect()` and `it.scoped()` that work with `bun:test`,
* similar to `@effect/vitest`. When the official `@effect/bun-test` package
* is released, replace this with that package.
*
* @see https://github.com/Effect-TS/effect/pull/5973
*
* @example
* ```ts
* import { describe, expect, it } from "./bun-effect"
* import { Effect, Layer } from "effect"
*
* describe("my test", () => {
* it.effect("runs an effect", () =>
* Effect.gen(function* () {
* const result = yield* someEffect
* expect(result).toBe(expected)
* }).pipe(Effect.provide(TestLayer))
* )
*
* it.scoped("runs a scoped effect", () =>
* Effect.gen(function* () {
* const resource = yield* acquireResource
* expect(resource).toBeDefined()
* })
* )
*
* it.effect.skip("skipped test", () => Effect.succeed(1))
* it.effect.only("only this test", () => Effect.succeed(1))
* })
* ```
*/
import { afterAll, beforeAll, describe, expect, test } from "bun:test"
import { Effect, TestServices } from "effect"
import type { Scope } from "effect"
export { afterAll, beforeAll, describe, expect }
type TestOptions = { timeout?: number }
const runTest = <E, A>(
effect: Effect.Effect<A, E, TestServices.TestServices>,
) => Effect.runPromise(effect.pipe(Effect.provide(TestServices.liveServices)))
const runTestScoped = <E, A>(
effect: Effect.Effect<A, E, TestServices.TestServices | Scope.Scope>,
) =>
Effect.runPromise(
effect.pipe(Effect.scoped, Effect.provide(TestServices.liveServices)),
)
type EffectFn<A, E, R> = () => Effect.Effect<A, E, R>
type EffectTester = {
<A, E>(
name: string,
fn: EffectFn<A, E, TestServices.TestServices>,
options?: number | TestOptions,
): void
skip: <A, E>(
name: string,
fn: EffectFn<A, E, TestServices.TestServices>,
options?: number | TestOptions,
) => void
only: <A, E>(
name: string,
fn: EffectFn<A, E, TestServices.TestServices>,
options?: number | TestOptions,
) => void
}
type ScopedTester = {
<A, E>(
name: string,
fn: EffectFn<A, E, TestServices.TestServices | Scope.Scope>,
options?: number | TestOptions,
): void
skip: <A, E>(
name: string,
fn: EffectFn<A, E, TestServices.TestServices | Scope.Scope>,
options?: number | TestOptions,
) => void
only: <A, E>(
name: string,
fn: EffectFn<A, E, TestServices.TestServices | Scope.Scope>,
options?: number | TestOptions,
) => void
}
const makeEffectTest =
(runner: typeof test) =>
<A, E>(
name: string,
fn: EffectFn<A, E, TestServices.TestServices>,
options?: number | TestOptions,
) => {
const timeout = typeof options === "number" ? options : options?.timeout
runner(name, () => runTest(fn()), timeout ? { timeout } : undefined)
}
const makeScopedTest =
(runner: typeof test) =>
<A, E>(
name: string,
fn: EffectFn<A, E, TestServices.TestServices | Scope.Scope>,
options?: number | TestOptions,
) => {
const timeout = typeof options === "number" ? options : options?.timeout
runner(name, () => runTestScoped(fn()), timeout ? { timeout } : undefined)
}
export const effect: EffectTester = Object.assign(makeEffectTest(test), {
skip: makeEffectTest(test.skip),
only: makeEffectTest(test.only),
})
export const scoped: ScopedTester = Object.assign(makeScopedTest(test), {
skip: makeScopedTest(test.skip),
only: makeScopedTest(test.only),
})
export const it = Object.assign(test, { effect, scoped })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment