effect-bun-testing 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Justin Menga
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,462 @@
1
+ # effect-bun-testing
2
+
3
+ Effect test helpers for Bun's built-in test runner.
4
+
5
+ This library ports the [`@effect/vitest`](https://github.com/Effect-TS/effect/tree/main/packages/vitest) API to [`bun:test`](https://bun.sh/docs/cli/test), providing first-class support for running Effect programs in Bun's test runner — including test services (`TestClock`, `TestConsole`), scoping, property-based testing, and all standard test modifiers.
6
+
7
+ > For Effect v3, install `effect-bun-testing@v3`.
8
+
9
+ ## Requirements
10
+
11
+ - [Bun](https://bun.sh) >= 1.0
12
+ - [Effect](https://effect.website) v4 (`^4.0.0-beta.10`)
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ bun add effect-bun-testing
18
+ ```
19
+
20
+ ## Overview
21
+
22
+ | API | Description |
23
+ |---|---|
24
+ | `it.effect` | Run an Effect test with test services (auto-scoped) |
25
+ | `it.live` | Run an Effect test without test services (auto-scoped) |
26
+ | `it.effect.skip` | Skip an Effect test |
27
+ | `it.effect.only` | Run only this Effect test |
28
+ | `it.effect.skipIf(cond)` | Skip when condition is truthy |
29
+ | `it.effect.if(cond)` | Run when condition is truthy |
30
+ | `it.effect.each(cases)` | Parameterized Effect tests |
31
+ | `it.effect.failing` | Test that is expected to fail |
32
+ | `it.effect.prop` | Property-based Effect test |
33
+ | `it.prop` | Property-based test (non-Effect) |
34
+ | `it.flakyTest` | Retry an Effect up to 10 times within a timeout |
35
+ | `layer(L)` | Share a Layer across tests with `beforeAll`/`afterAll` lifecycle |
36
+
37
+ ## Writing Tests
38
+
39
+ ### Basic Effect tests
40
+
41
+ Import `it` from `effect-bun-testing` as a drop-in replacement for `bun:test`'s `it`. All standard `bun:test` functionality is preserved, with Effect methods added.
42
+
43
+ ```ts
44
+ import { describe, expect } from "bun:test"
45
+ import { it } from "effect-bun-testing"
46
+ import { Effect } from "effect"
47
+
48
+ describe("my tests", () => {
49
+ it.effect("runs a basic Effect test", () =>
50
+ Effect.gen(function*() {
51
+ const result = yield* Effect.succeed(42)
52
+ expect(result).toBe(42)
53
+ })
54
+ )
55
+ })
56
+ ```
57
+
58
+ ### Defining services
59
+
60
+ ```ts
61
+ import { Effect, Layer, ServiceMap } from "effect"
62
+
63
+ interface Greeter {
64
+ readonly greet: (name: string) => Effect.Effect<string>
65
+ }
66
+ const Greeter = ServiceMap.Service<Greeter>("app/Greeter")
67
+ ```
68
+
69
+ ### Providing layers per-test
70
+
71
+ The standard pattern is to pipe layers directly to individual tests using `Effect.provide`. Each test gets a fresh layer instance.
72
+
73
+ ```ts
74
+ const GreeterLive = Layer.succeed(Greeter)({
75
+ greet: (name) => Effect.succeed(`Hello, ${name}!`)
76
+ })
77
+
78
+ describe("greeter", () => {
79
+ it.effect("greets by name", () =>
80
+ Effect.gen(function*() {
81
+ const greeter = yield* Greeter
82
+ const msg = yield* greeter.greet("World")
83
+ expect(msg).toBe("Hello, World!")
84
+ }).pipe(Effect.provide(GreeterLive))
85
+ )
86
+ })
87
+ ```
88
+
89
+ You can compose multiple layers for a single test:
90
+
91
+ ```ts
92
+ it.effect("uses multiple services", () =>
93
+ Effect.gen(function*() {
94
+ const counter = yield* Counter
95
+ const logger = yield* Logger
96
+ yield* counter.increment
97
+ const count = yield* counter.get
98
+ yield* logger.log(`count is ${count}`)
99
+ const msgs = yield* logger.messages
100
+ expect(msgs).toHaveLength(1)
101
+ }).pipe(Effect.provide(Layer.mergeAll(CounterLive, LoggerLive)))
102
+ )
103
+ ```
104
+
105
+ ### TestClock
106
+
107
+ `it.effect` provides `TestClock` automatically. Use `TestClock.adjust` to advance time without waiting.
108
+
109
+ ```ts
110
+ import { TestClock } from "effect/testing"
111
+
112
+ it.effect("advances time via TestClock", () =>
113
+ Effect.gen(function*() {
114
+ const before = yield* Effect.clockWith((clock) => clock.currentTimeMillis)
115
+ yield* TestClock.adjust("1 second")
116
+ const after = yield* Effect.clockWith((clock) => clock.currentTimeMillis)
117
+ expect(after - before).toBe(1000)
118
+ })
119
+ )
120
+ ```
121
+
122
+ ### Scoped tests
123
+
124
+ `it.effect` and `it.live` auto-scope in Effect v4, so no special handling is needed for `Effect.addFinalizer` or other scoped resources:
125
+
126
+ ```ts
127
+ it.effect("auto-scopes resources", () =>
128
+ Effect.gen(function*() {
129
+ const ref = yield* Ref.make(0)
130
+ yield* Effect.addFinalizer(() => Ref.set(ref, 99))
131
+ const before = yield* Ref.get(ref)
132
+ expect(before).toBe(0)
133
+ // finalizer runs automatically after this test
134
+ })
135
+ )
136
+ ```
137
+
138
+ ### Handling failures
139
+
140
+ ```ts
141
+ it.effect("handles failures", () =>
142
+ Effect.gen(function*() {
143
+ const result = yield* Effect.fail("boom").pipe(
144
+ Effect.catch(() => Effect.succeed("recovered"))
145
+ )
146
+ expect(result).toBe("recovered")
147
+ })
148
+ )
149
+ ```
150
+
151
+ ### Live tests
152
+
153
+ `it.live` runs effects without test services (`TestClock`, `TestConsole`), using the real runtime:
154
+
155
+ ```ts
156
+ it.live("uses real clock", () =>
157
+ Effect.gen(function*() {
158
+ const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis)
159
+ expect(now).toBeGreaterThan(0)
160
+ })
161
+ )
162
+ ```
163
+
164
+ ### Modifiers
165
+
166
+ All standard `bun:test` modifiers are available on `it.effect`:
167
+
168
+ ```ts
169
+ // Skip a test
170
+ it.effect.skip("not ready yet", () => ...)
171
+
172
+ // Run only this test
173
+ it.effect.only("focus on this", () => ...)
174
+
175
+ // Conditional skip
176
+ it.effect.skipIf(process.env.CI)("skip in CI", () => ...)
177
+
178
+ // Conditional run
179
+ it.effect.if(process.env.CI)("only in CI", () => ...)
180
+
181
+ // Alias for .if (vitest compat)
182
+ it.effect.runIf(someCondition)("conditional", () => ...)
183
+
184
+ // Expected failure
185
+ it.effect.failing("known bug", () =>
186
+ Effect.gen(function*() {
187
+ yield* Effect.fail("not implemented")
188
+ })
189
+ )
190
+
191
+ // Alias for .failing (vitest compat)
192
+ it.effect.fails("also known bug", () => ...)
193
+ ```
194
+
195
+ ### Parameterized tests
196
+
197
+ Use `it.effect.each` to run the same test with different inputs:
198
+
199
+ ```ts
200
+ it.effect.each([1, 2, 3])("doubles %d", (n) =>
201
+ Effect.gen(function*() {
202
+ const result = yield* Effect.succeed(n * 2)
203
+ expect(result).toBe(n * 2)
204
+ })
205
+ )
206
+ ```
207
+
208
+ ### Flaky tests
209
+
210
+ `flakyTest` retries an Effect up to 10 times within a timeout (default 30 seconds):
211
+
212
+ ```ts
213
+ import { it } from "effect-bun-testing"
214
+ import { Effect, Duration } from "effect"
215
+
216
+ it.effect("retries flaky operations", () =>
217
+ it.flakyTest(
218
+ Effect.gen(function*() {
219
+ const n = Math.random()
220
+ if (n < 0.8) yield* Effect.fail("unlucky")
221
+ expect(n).toBeGreaterThanOrEqual(0.8)
222
+ }),
223
+ Duration.seconds(5)
224
+ )
225
+ )
226
+ ```
227
+
228
+ ## Property-Based Testing
229
+
230
+ Property-based testing is supported via [fast-check](https://github.com/dubzzz/fast-check).
231
+
232
+ ### Non-Effect properties
233
+
234
+ ```ts
235
+ import * as fc from "fast-check"
236
+
237
+ it.prop(
238
+ "arrays always have non-negative length",
239
+ [fc.array(fc.string())],
240
+ (arr) => {
241
+ expect(arr.length).toBeGreaterThanOrEqual(0)
242
+ }
243
+ )
244
+
245
+ it.prop(
246
+ "multiple arbitraries",
247
+ [fc.string(), fc.integer({ min: 0, max: 100 })],
248
+ (str, num) => {
249
+ expect(typeof str).toBe("string")
250
+ expect(num).toBeGreaterThanOrEqual(0)
251
+ }
252
+ )
253
+ ```
254
+
255
+ ### Effect properties
256
+
257
+ ```ts
258
+ it.effect.prop(
259
+ "positive numbers remain positive after increment",
260
+ [fc.integer({ min: 1, max: 1000 })],
261
+ (n) =>
262
+ Effect.gen(function*() {
263
+ const result = yield* Effect.succeed(n + 1)
264
+ expect(result).toBeGreaterThan(n)
265
+ })
266
+ )
267
+ ```
268
+
269
+ ## Shared Layer Lifecycle
270
+
271
+ For expensive resources (database connections, server instances) or tests that intentionally share state, use `layer()` to build a layer once in `beforeAll` and tear it down in `afterAll`:
272
+
273
+ ```ts
274
+ import { describe, expect } from "bun:test"
275
+ import { it, layer } from "effect-bun-testing"
276
+ import { Effect, Layer, ServiceMap } from "effect"
277
+
278
+ interface Counter {
279
+ readonly get: Effect.Effect<number>
280
+ readonly increment: Effect.Effect<void>
281
+ }
282
+ const Counter = ServiceMap.Service<Counter>("app/Counter")
283
+
284
+ const CounterLive = Layer.effect(
285
+ Counter,
286
+ Effect.gen(function*() {
287
+ let count = 0
288
+ return {
289
+ get: Effect.sync(() => count),
290
+ increment: Effect.sync(() => { count++ })
291
+ }
292
+ })
293
+ )
294
+
295
+ describe("shared counter", () => {
296
+ layer(CounterLive)("shared lifecycle", (it) => {
297
+ it.effect("starts at zero", () =>
298
+ Effect.gen(function*() {
299
+ const counter = yield* Counter
300
+ const count = yield* counter.get
301
+ expect(count).toBe(0)
302
+ })
303
+ )
304
+
305
+ it.effect("state persists across tests (shared instance)", () =>
306
+ Effect.gen(function*() {
307
+ const counter = yield* Counter
308
+ yield* counter.increment
309
+ const count = yield* counter.get
310
+ expect(count).toBeGreaterThanOrEqual(1)
311
+ })
312
+ )
313
+ })
314
+ })
315
+ ```
316
+
317
+ > **Note:** For most tests, prefer the per-test `Effect.provide(layer)` pattern. It gives each test a fresh layer instance, which avoids shared-state coupling. Use `layer()` only when you need the layer to be built once and shared.
318
+
319
+ ## Mocking Effect Services
320
+
321
+ Effect services are interfaces resolved from the environment, which makes them straightforward to mock using Bun's built-in `mock()`. The pattern uses three steps:
322
+
323
+ 1. Create dynamically typed mocks with `mock()`
324
+ 2. Wire them into test layers with `Layer.mock` (partial implementation — only mock what you need)
325
+ 3. Set default implementations in `beforeEach`
326
+
327
+ ### Full example
328
+
329
+ ```ts
330
+ import { beforeEach, describe, expect, mock } from "bun:test"
331
+ import { it } from "effect-bun-testing"
332
+ import { Effect, Layer, ServiceMap } from "effect"
333
+
334
+ interface UserRepository {
335
+ readonly findById: (id: string) => Effect.Effect<{ id: string; name: string } | null>
336
+ }
337
+ const UserRepository = ServiceMap.Service<UserRepository>("app/UserRepository")
338
+
339
+ interface NotificationService {
340
+ readonly send: (userId: string, message: string) => Effect.Effect<void>
341
+ readonly test: () => Effect.Effect<void>
342
+ }
343
+ const NotificationService = ServiceMap.Service<NotificationService>("app/NotificationService")
344
+
345
+ // Business logic under test
346
+ const notifyUser = (userId: string, message: string) =>
347
+ Effect.gen(function*() {
348
+ const repo = yield* UserRepository
349
+ const notifications = yield* NotificationService
350
+ const user = yield* repo.findById(userId)
351
+ if (!user) {
352
+ return yield* Effect.fail(new Error(`User ${userId} not found`))
353
+ }
354
+ yield* notifications.send(userId, `${user.name}: ${message}`)
355
+ return { sent: true, to: user.name }
356
+ })
357
+
358
+ // 1. Create dynamically typed mocks
359
+ const mockFindById = mock()
360
+ const mockSend = mock()
361
+
362
+ // 2. Wire mocks into test layers using Layer.mock
363
+ // Layer.mock accepts a partial implementation — only mock the methods you need.
364
+ const TestUserRepository = Layer.mock(UserRepository)({
365
+ findById: mockFindById
366
+ })
367
+
368
+ // NotificationService has both `send` and `test`, but we only need `send`
369
+ const TestNotificationService = Layer.mock(NotificationService)({
370
+ send: mockSend
371
+ })
372
+
373
+ const TestLayer = Layer.mergeAll(TestUserRepository, TestNotificationService)
374
+
375
+ // 3. Set defaults in beforeEach, provide layer per-test
376
+ describe("notifyUser", () => {
377
+ beforeEach(() => {
378
+ mockFindById.mockClear()
379
+ mockSend.mockClear()
380
+
381
+ mockFindById.mockImplementation((id: string) =>
382
+ Effect.succeed(id === "user-1" ? { id: "user-1", name: "Alice" } : null)
383
+ )
384
+ mockSend.mockReturnValue(Effect.void)
385
+ })
386
+
387
+ it.effect("sends notification to existing user", () =>
388
+ Effect.gen(function*() {
389
+ const result = yield* notifyUser("user-1", "hello!")
390
+ expect(result).toEqual({ sent: true, to: "Alice" })
391
+ expect(mockSend).toHaveBeenCalledTimes(1)
392
+ expect(mockSend).toHaveBeenCalledWith("user-1", "Alice: hello!")
393
+ }).pipe(Effect.provide(TestLayer))
394
+ )
395
+
396
+ it.effect("can override mock per-test", () =>
397
+ Effect.gen(function*() {
398
+ mockFindById.mockReturnValue(
399
+ Effect.succeed({ id: "user-42", name: "Bob" })
400
+ )
401
+ const result = yield* notifyUser("user-42", "hey!")
402
+ expect(result).toEqual({ sent: true, to: "Bob" })
403
+ expect(mockSend).toHaveBeenCalledWith("user-42", "Bob: hey!")
404
+ }).pipe(Effect.provide(TestLayer))
405
+ )
406
+ })
407
+ ```
408
+
409
+ ### Key points
410
+
411
+ - **Use `Layer.mock` (not `Layer.succeed`)** — `Layer.mock` accepts a partial implementation, so you only mock the methods your test actually exercises. `Layer.succeed` requires the full service interface.
412
+ - **Use `mock()` (not `mock<T>()`)** — dynamically typed mocks avoid wrestling with generics and work naturally with `Layer.mock`.
413
+ - **`mockImplementation`** for methods that take arguments and return an `Effect` with a value based on those arguments.
414
+ - **`mockReturnValue(Effect.void)`** for side-effect methods that always return the same thing.
415
+ - **`mockReturnValue(Effect.succeed(value))`** for methods that return a fixed value (useful for per-test overrides).
416
+ - **`mockClear()` in `beforeEach`** resets call counts and implementations, so each test starts clean.
417
+ - **Per-test overrides** simply call `mockReturnValue` or `mockImplementation` again before running the effect — `beforeEach` resets it for the next test automatically.
418
+
419
+ ## Assertion Utilities
420
+
421
+ A set of assertion helpers is available at `effect-bun-testing/utils`, ported from `@effect/vitest/utils`:
422
+
423
+ ```ts
424
+ import {
425
+ assertEquals, // Compare via Effect's Equal.equals
426
+ assertTrue, // Truthy assertion
427
+ assertFalse, // Falsy assertion
428
+ assertNone, // Option is None
429
+ assertSome, // Option is Some with expected value
430
+ assertSuccess_, // Result is Success with expected value
431
+ assertFailure_, // Result is Failure with expected value
432
+ assertRight, // Alias for assertSuccess_ (vitest compat)
433
+ assertExitSuccess, // Exit is Success with expected value
434
+ assertExitFailure, // Exit is Failure with expected Cause
435
+ deepStrictEqual, // Structural equality (toStrictEqual)
436
+ strictEqual, // Reference equality (toBe)
437
+ assertInstanceOf, // instanceof check
438
+ assertInclude, // String contains substring
439
+ assertMatch, // String matches regex
440
+ throws, // Function throws
441
+ throwsAsync, // Async function throws
442
+ fail // Always throws (unconditional failure)
443
+ } from "effect-bun-testing/utils"
444
+ ```
445
+
446
+ ## API Mapping from @effect/vitest
447
+
448
+ | @effect/vitest | effect-bun-testing | Notes |
449
+ |---|---|---|
450
+ | `it.effect(name, (ctx) => ...)` | `it.effect(name, () => ...)` | No TestContext param (Bun has none) |
451
+ | `it.live(name, (ctx) => ...)` | `it.live(name, () => ...)` | Same |
452
+ | `it.effect.fails` | `it.effect.failing` | Bun's name; `fails` alias also available |
453
+ | `it.effect.runIf(cond)` | `it.effect.if(cond)` | Bun's name; `runIf` alias also available |
454
+ | `it.effect.skip/only/skipIf/each` | `it.effect.skip/only/skipIf/each` | Direct mapping |
455
+ | `it.effect.prop` | `it.effect.prop` | Same API |
456
+ | `it.prop` | `it.prop` | Same API |
457
+ | `layer(L)(name, fn)` | `layer(L)(name, fn)` | Same API |
458
+ | `flakyTest(effect, timeout)` | `flakyTest(effect, timeout)` | Same API |
459
+
460
+ ## License
461
+
462
+ MIT
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Effect test helpers for Bun's built-in test runner.
3
+ *
4
+ * Ports the `@effect/vitest` API to `bun:test`, including `it.effect`,
5
+ * `it.live`, `layer()`, `flakyTest`, property-based testing, and assertion
6
+ * utilities.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { it } from "effect-bun-test"
11
+ * import { Effect } from "effect"
12
+ *
13
+ * it.effect("adds numbers", () =>
14
+ * Effect.gen(function*() {
15
+ * const result = 1 + 1
16
+ * expect(result).toBe(2)
17
+ * })
18
+ * )
19
+ * ```
20
+ *
21
+ * @module
22
+ */
23
+ export { describe, expect, beforeAll, afterAll, beforeEach, afterEach, test, mock, spyOn, jest, vi, setSystemTime } from "bun:test";
24
+ export { effect, live, makeTester, flakyTest, prop, layer, makeMethods, addEqualityTesters } from "./internal/internal.js";
25
+ export type { TestFunction, Test, Tester, PropEffect, Prop, TestServices, Methods, MethodsNonLive, LayerOptions, LayerFn } from "./internal/internal.js";
26
+ import { test as bunTest } from "bun:test";
27
+ import type { Methods } from "./internal/internal.js";
28
+ /**
29
+ * Drop-in replacement for bun:test's `it` / `test`, augmented with Effect
30
+ * methods.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { it } from "effect-bun-test"
35
+ *
36
+ * it.effect("runs an Effect test", () =>
37
+ * Effect.succeed(42).pipe(Effect.map((n) => expect(n).toBe(42)))
38
+ * )
39
+ *
40
+ * it.live("runs without test services", () =>
41
+ * Effect.sync(() => expect(Date.now()).toBeGreaterThan(0))
42
+ * )
43
+ * ```
44
+ */
45
+ export declare const it: typeof bunTest & Methods;
46
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EACL,QAAQ,EACR,MAAM,EACN,SAAS,EACT,QAAQ,EACR,UAAU,EACV,SAAS,EACT,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,EAAE,EACF,aAAa,EACd,MAAM,UAAU,CAAA;AAKjB,OAAO,EACL,MAAM,EACN,IAAI,EACJ,UAAU,EACV,SAAS,EACT,IAAI,EACJ,KAAK,EACL,WAAW,EACX,kBAAkB,EACnB,MAAM,wBAAwB,CAAA;AAK/B,YAAY,EACV,YAAY,EACZ,IAAI,EACJ,MAAM,EACN,UAAU,EACV,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,cAAc,EACd,YAAY,EACZ,OAAO,EACR,MAAM,wBAAwB,CAAA;AAK/B,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,UAAU,CAAA;AAQ1C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAErD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,EAAE,EAAE,OAAO,OAAO,GAAG,OAMd,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Effect test helpers for Bun's built-in test runner.
3
+ *
4
+ * Ports the `@effect/vitest` API to `bun:test`, including `it.effect`,
5
+ * `it.live`, `layer()`, `flakyTest`, property-based testing, and assertion
6
+ * utilities.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { it } from "effect-bun-test"
11
+ * import { Effect } from "effect"
12
+ *
13
+ * it.effect("adds numbers", () =>
14
+ * Effect.gen(function*() {
15
+ * const result = 1 + 1
16
+ * expect(result).toBe(2)
17
+ * })
18
+ * )
19
+ * ```
20
+ *
21
+ * @module
22
+ */
23
+ // Re-export bun:test for convenience (parallel to @effect/vitest re-exporting vitest)
24
+ export { describe, expect, beforeAll, afterAll, beforeEach, afterEach, test, mock, spyOn, jest, vi, setSystemTime } from "bun:test";
25
+ // ---------------------------------------------------------------------------
26
+ // Core exports from internal
27
+ // ---------------------------------------------------------------------------
28
+ export { effect, live, makeTester, flakyTest, prop, layer, makeMethods, addEqualityTesters } from "./internal/internal.js";
29
+ // ---------------------------------------------------------------------------
30
+ // Composed `it` — the primary import for most users
31
+ // ---------------------------------------------------------------------------
32
+ import { test as bunTest } from "bun:test";
33
+ import { effect, live, flakyTest, layer, prop } from "./internal/internal.js";
34
+ /**
35
+ * Drop-in replacement for bun:test's `it` / `test`, augmented with Effect
36
+ * methods.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { it } from "effect-bun-test"
41
+ *
42
+ * it.effect("runs an Effect test", () =>
43
+ * Effect.succeed(42).pipe(Effect.map((n) => expect(n).toBe(42)))
44
+ * )
45
+ *
46
+ * it.live("runs without test services", () =>
47
+ * Effect.sync(() => expect(Date.now()).toBeGreaterThan(0))
48
+ * )
49
+ * ```
50
+ */
51
+ export const it = Object.assign(bunTest, {
52
+ effect,
53
+ live,
54
+ flakyTest,
55
+ layer,
56
+ prop
57
+ });
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,sFAAsF;AACtF,OAAO,EACL,QAAQ,EACR,MAAM,EACN,SAAS,EACT,QAAQ,EACR,UAAU,EACV,SAAS,EACT,IAAI,EACJ,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,EAAE,EACF,aAAa,EACd,MAAM,UAAU,CAAA;AAEjB,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAC9E,OAAO,EACL,MAAM,EACN,IAAI,EACJ,UAAU,EACV,SAAS,EACT,IAAI,EACJ,KAAK,EACL,WAAW,EACX,kBAAkB,EACnB,MAAM,wBAAwB,CAAA;AAkB/B,8EAA8E;AAC9E,oDAAoD;AACpD,8EAA8E;AAC9E,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EACL,MAAM,EACN,IAAI,EACJ,SAAS,EACT,KAAK,EACL,IAAI,EACL,MAAM,wBAAwB,CAAA;AAG/B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,EAAE,GAA6B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE;IACjE,MAAM;IACN,IAAI;IACJ,SAAS;IACT,KAAK;IACL,IAAI;CACa,CAAC,CAAA"}
@@ -0,0 +1,82 @@
1
+ import { Duration, Effect, Layer, Scope } from "effect";
2
+ import { TestClock, TestConsole } from "effect/testing";
3
+ import { test as bunTest } from "bun:test";
4
+ import * as fc from "fast-check";
5
+ /** A test body that returns an Effect. */
6
+ export interface TestFunction<A, E, R> {
7
+ (): Effect.Effect<A, E, R>;
8
+ }
9
+ /** Registers a named test whose body returns an Effect. */
10
+ export interface Test<R> {
11
+ <A, E>(name: string, self: TestFunction<A, E, R>, timeout?: number): void;
12
+ }
13
+ /** Full tester with modifiers (skip, only, each, etc.). */
14
+ export interface Tester<R> extends Test<R> {
15
+ readonly skip: Test<R>;
16
+ readonly only: Test<R>;
17
+ readonly skipIf: (condition: unknown) => Test<R>;
18
+ readonly if: (condition: unknown) => Test<R>;
19
+ /** Alias for `if` (vitest compat). */
20
+ readonly runIf: (condition: unknown) => Test<R>;
21
+ readonly each: <T>(cases: ReadonlyArray<T>) => <A, E>(name: string, self: (args: T) => Effect.Effect<A, E, R>, timeout?: number) => void;
22
+ readonly failing: Test<R>;
23
+ /** Alias for `failing` (vitest compat). */
24
+ readonly fails: Test<R>;
25
+ readonly prop: PropEffect<R>;
26
+ }
27
+ /** Property-based test that returns an Effect. */
28
+ export interface PropEffect<R> {
29
+ <const Arbs extends ReadonlyArray<fc.Arbitrary<any>>>(name: string, arbitraries: Arbs, self: (...args: {
30
+ [K in keyof Arbs]: Arbs[K] extends fc.Arbitrary<infer T> ? T : never;
31
+ }) => Effect.Effect<any, any, R>, timeout?: number | {
32
+ readonly fastCheck?: fc.Parameters<any>;
33
+ }): void;
34
+ }
35
+ /** Property-based test that returns void / Promise<void>. */
36
+ export interface Prop {
37
+ <const Arbs extends ReadonlyArray<fc.Arbitrary<any>>>(name: string, arbitraries: Arbs, self: (...args: {
38
+ [K in keyof Arbs]: Arbs[K] extends fc.Arbitrary<infer T> ? T : never;
39
+ }) => void | Promise<void>, timeout?: number | {
40
+ readonly fastCheck?: fc.Parameters<any>;
41
+ }): void;
42
+ }
43
+ export type TestServices = TestClock.TestClock | TestConsole.TestConsole;
44
+ /** Full method set exposed on `it` and returned from `makeMethods`. */
45
+ export interface Methods {
46
+ readonly effect: Tester<TestServices | Scope.Scope>;
47
+ readonly live: Tester<Scope.Scope>;
48
+ readonly flakyTest: typeof flakyTest;
49
+ readonly layer: typeof layer;
50
+ readonly prop: Prop;
51
+ }
52
+ /** Method set available inside a `layer()` callback. */
53
+ export interface MethodsNonLive<R, ExcludeTestServices extends boolean = false> {
54
+ readonly effect: Tester<ExcludeTestServices extends true ? R | Scope.Scope : R | TestServices | Scope.Scope>;
55
+ readonly flakyTest: typeof flakyTest;
56
+ readonly layer: <R2, E2>(layer_: Layer.Layer<R2, E2>, options?: LayerOptions) => LayerFn<R2, ExcludeTestServices>;
57
+ readonly prop: Prop;
58
+ }
59
+ export interface LayerOptions {
60
+ readonly memoMap?: Layer.MemoMap;
61
+ readonly timeout?: Duration.Input;
62
+ readonly excludeTestServices?: boolean;
63
+ }
64
+ export type LayerFn<R, ExcludeTestServices extends boolean = false> = {
65
+ (f: (it: MethodsNonLive<R, ExcludeTestServices>) => void): void;
66
+ (name: string, f: (it: MethodsNonLive<R, ExcludeTestServices>) => void): void;
67
+ };
68
+ export declare const makeTester: <R>(mapEffect: (self: Effect.Effect<any, any, R>) => Effect.Effect<any, any, never>, testFn?: typeof bunTest) => Tester<R>;
69
+ /** Run effects with TestClock + TestConsole, auto-scoped. */
70
+ export declare const effect: Tester<TestServices | Scope.Scope>;
71
+ /** Run effects live (no test services), auto-scoped. */
72
+ export declare const live: Tester<Scope.Scope>;
73
+ export declare const flakyTest: <A, E, R>(self: Effect.Effect<A, E, R>, timeout?: Duration.Input) => Effect.Effect<A, never, R>;
74
+ export declare const prop: Prop;
75
+ export declare const layer: <R, E, const ExcludeTestServices extends boolean = false>(layer_: Layer.Layer<R, E>, options?: {
76
+ readonly memoMap?: Layer.MemoMap;
77
+ readonly timeout?: Duration.Input;
78
+ readonly excludeTestServices?: ExcludeTestServices;
79
+ }) => LayerFn<R, ExcludeTestServices>;
80
+ export declare const makeMethods: () => Methods;
81
+ export declare const addEqualityTesters: () => void;
82
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../../src/internal/internal.ts"],"names":[],"mappings":"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"}