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 +21 -0
- package/README.md +462 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/internal.d.ts +82 -0
- package/dist/internal/internal.d.ts.map +1 -0
- package/dist/internal/internal.js +152 -0
- package/dist/internal/internal.js.map +1 -0
- package/dist/utils.d.ts +30 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +158 -0
- package/dist/utils.js.map +1 -0
- package/package.json +55 -0
- package/src/index.ts +106 -0
- package/src/internal/internal.ts +407 -0
- package/src/utils.ts +215 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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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"}
|