Skip to content

Commit c6141eb

Browse files
Merge pull request #77 from EtaCassiopeia/docs/testkit-isolation
Update docs for isolated test API instances
2 parents c42abf8 + 6395375 commit c6141eb

3 files changed

Lines changed: 33 additions & 31 deletions

File tree

docs/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ FeatureFlags.fromProvider(provider)
116116
| Method | Description |
117117
|:-------|:------------|
118118
| `fromProvider(provider)` | Create from any OpenFeature provider |
119-
| `fromProviderWithDomain(provider, domain)` | Create with named domain for test isolation |
119+
| `fromProviderWithDomain(provider, domain)` | Create with named domain/client |
120120
| `fromProviderWithHooks(provider, hooks)` | Create with initial hooks |
121121
| `fromMultiProvider(providers)` | Combine multiple providers (first-match strategy) |
122122
| `fromMultiProvider(providers, strategy)` | Combine multiple providers with custom strategy |

docs/providers.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -279,15 +279,14 @@ val layer: ZLayer[Scope, Throwable, FeatureFlags] =
279279

280280
### fromProviderWithDomain
281281

282-
Create with a named domain for test isolation. Each domain gets its own client:
282+
Create with a named domain. Each domain gets its own client, useful for segmenting feature flag configuration:
283283

284284
```scala
285285
val layer = FeatureFlags.fromProviderWithDomain(provider, "my-service")
286-
287-
// Useful for testing - each test can use a different domain
288-
val testLayer = FeatureFlags.fromProviderWithDomain(testProvider, "test-domain-123")
289286
```
290287

288+
> For test isolation, prefer `TestFeatureProvider.layer` which automatically creates isolated API instances.
289+
291290
### fromProviderWithHooks
292291

293292
Create with initial hooks:
@@ -567,12 +566,14 @@ val ctx = EvaluationContext(userId)
567566
FeatureFlags.boolean("feature", false, ctx)
568567
```
569568

570-
### 5. Use Domain Isolation in Tests
569+
### 5. Use Testkit Layers for Isolation
570+
571+
`TestFeatureProvider.layer` creates an isolated API instance per test — no manual domain management needed:
571572

572573
```scala
573-
val testLayer = FeatureFlags.fromProviderWithDomain(
574-
testProvider,
575-
s"test-${java.util.UUID.randomUUID()}"
576-
)
574+
test("my test") {
575+
for result <- FeatureFlags.boolean("flag", false)
576+
yield assertTrue(result == true)
577+
}.provide(Scope.default >>> TestFeatureProvider.layer(Map("flag" -> true)))
577578
```
578579

docs/testkit.md

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -357,39 +357,40 @@ The `asyncLayer` creates a provider that starts in `NotReady` state. Call `setSt
357357

358358
## Test Isolation
359359

360-
### Domain-Based Isolation
360+
### Automatic Isolation
361361

362-
Use `FeatureFlags.fromProviderWithDomain` for test isolation when tests run in parallel:
362+
`TestFeatureProvider.layer`, `asyncLayer`, and `layerFrom` each create an **isolated `OpenFeatureAPI` instance** with its own provider repository and event support. This means tests using these layers can run in parallel without cross-test contamination — no extra configuration needed.
363363

364364
```scala
365-
test("isolated test 1") {
366-
for
367-
provider <- TestFeatureProvider.make(Map("flag" -> true))
368-
layer = TestFeatureProvider.layerFrom(provider)
369-
result <- FeatureFlags.boolean("flag", false).provide(Scope.default >>> layer)
365+
// These tests run in parallel safely — each gets its own isolated API instance
366+
test("test 1") {
367+
for result <- FeatureFlags.boolean("flag", false)
370368
yield assertTrue(result == true)
371-
}
369+
}.provide(Scope.default >>> TestFeatureProvider.layer(Map("flag" -> true)))
372370

373-
test("isolated test 2") {
374-
for
375-
provider <- TestFeatureProvider.make(Map("flag" -> false))
376-
layer = TestFeatureProvider.layerFrom(provider)
377-
result <- FeatureFlags.boolean("flag", false).provide(Scope.default >>> layer)
371+
test("test 2") {
372+
for result <- FeatureFlags.boolean("flag", false)
378373
yield assertTrue(result == false)
379-
}
374+
}.provide(Scope.default >>> TestFeatureProvider.layer(Map("flag" -> false)))
380375
```
381376

382-
### Sequential Tests
383-
384-
For tests that share state, run them sequentially:
377+
If you need to access both the provider and the `FeatureFlags` service (e.g. to track evaluations or emit events), use `layerFrom`:
385378

386379
```scala
387-
suite("shared state tests")(
388-
test("test 1") { ... },
389-
test("test 2") { ... }
390-
) @@ TestAspect.sequential
380+
test("tracks evaluations") {
381+
for
382+
provider <- TestFeatureProvider.make(Map("flag" -> true))
383+
layer = TestFeatureProvider.layerFrom(provider)
384+
_ <- FeatureFlags.boolean("flag", false).provide(Scope.default >>> layer)
385+
was <- provider.wasEvaluated("flag")
386+
yield assertTrue(was)
387+
}
391388
```
392389

390+
> **Note:** The public factory methods (`FeatureFlags.fromProvider`, `fromMultiProvider`, etc.)
391+
> use the global `OpenFeatureAPI` singleton and are **not** isolated. If you test with these
392+
> directly, use `@@ TestAspect.sequential` to prevent conflicts.
393+
393394
---
394395

395396
## Best Practices

0 commit comments

Comments
 (0)