|
1 | 1 | [](https://pkg.go.dev/github.com/rekby/fixenv) |
2 | 2 | [](https://coveralls.io/github/rekby/fixenv?branch=master) |
3 | 3 | [](https://goreportcard.com/report/github.com/rekby/fixenv) |
4 | | -[](https://github.com/avelino/awesome-go) |
| 4 | +[](https://github.com/avelino/awesome-go) |
5 | 5 |
|
6 | | -Go Fixtures |
7 | | -=========== |
| 6 | +# Fixenv |
8 | 7 |
|
9 | | -Inspired by pytest fixtures. No dependencies. |
| 8 | +Pytest-inspired fixture caching for Go tests with precise scope control, automatic cleanup, and no external dependencies. |
10 | 9 |
|
11 | | -[Examples](https://github.com/rekby/fixenv/tree/master/examples) |
| 10 | +Fixenv helps Go developers describe repeatable test environments once and reuse them safely across an entire test suite. Fixtures are cached per configurable scope, dependencies between fixtures are tracked automatically, and cleanup hooks make resource lifecycle management explicit. |
12 | 11 |
|
13 | | -The package provide engine for write and use own fixtures. |
| 12 | +--- |
14 | 13 |
|
15 | | -Fixture - function-helper for provide some object/service for test. |
16 | | -Fixture calls with same parameters cached and many time calls of the fixture return same result |
17 | | -and work did once only. |
| 14 | +- [Features](#features) |
| 15 | +- [Installation](#installation) |
| 16 | +- [Quick start](#quick-start) |
| 17 | +- [Fixture scopes & caching](#fixture-scopes--caching) |
| 18 | +- [Cleanup & ordering guarantees](#cleanup--ordering-guarantees) |
| 19 | +- [Documentation](#documentation) |
| 20 | +- [Example gallery](#example-gallery) |
| 21 | +- [Comparison with alternatives](#comparison-with-alternatives) |
| 22 | +- [Project roadmap](#project-roadmap) |
| 23 | +- [Contributing](#contributing) |
| 24 | +- [License](#license) |
18 | 25 |
|
19 | | -```golang |
20 | | -package example |
| 26 | +--- |
21 | 27 |
|
22 | | -// counter fixture - increment globalCounter every non cached call |
23 | | -// and return new globalCounter value |
24 | | -// cache shared one test |
25 | | -func counter(e fixenv.Env) int {...} |
| 28 | +## Features |
26 | 29 |
|
27 | | -func TestCounter(t *testing.T) { |
28 | | - e := fixenv.NewEnv(t) |
29 | | - |
30 | | - r1 := counter(e) |
31 | | - r2 := counter(e) |
32 | | - if r1 != r2 { |
33 | | - t.Error() |
34 | | - } |
35 | | - |
36 | | - t.Run("subtest", func(t *testing.T) { |
37 | | - e := fixenv.NewEnv(t) |
38 | | - r3 := counter(e) |
39 | | - if r3 == r1 { |
40 | | - t.Error() |
41 | | - } |
42 | | - }) |
43 | | -} |
| 30 | +- **Scope-aware caching** – Run expensive setup only once per test, test tree, or package using [`CacheOptions.Scope`](https://pkg.go.dev/github.com/rekby/fixenv#CacheOptions). |
| 31 | +- **Deterministic cleanup** – Register cleanup callbacks via [`NewGenericResultWithCleanup`](https://pkg.go.dev/github.com/rekby/fixenv#NewGenericResultWithCleanup); Fixenv invokes them when the owning scope ends. |
| 32 | +- **Fixture dependency tree** – Compose fixtures freely; cached results are shared across the dependency tree to avoid duplicated work. |
| 33 | +- **Skip-aware execution** – Return [`ErrSkipTest`](https://pkg.go.dev/github.com/rekby/fixenv#pkg-variables) from fixtures to short-circuit tests without leaking resources. |
| 34 | +- **Extensible environment** – Embed [`EnvT`](https://pkg.go.dev/github.com/rekby/fixenv#EnvT) or implement the [`Env`](https://pkg.go.dev/github.com/rekby/fixenv#Env) interface to customise behaviour for your project. |
| 35 | +- **Parallel-ready fixtures** – Caching and cleanup stay correct when tests call [`t.Parallel`](https://pkg.go.dev/testing#T.Parallel); each parallel test receives its own safe environment. |
| 36 | +- **Zero dependencies** – Fixenv is a lightweight helper that plays well with the standard `testing` package. |
| 37 | +- **Starter fixtures included** – Import [`github.com/rekby/fixenv/sf`](sf) for ready-to-use helpers like cancellable contexts, temporary directories, and TCP listeners. |
| 38 | + |
| 39 | +## Installation |
| 40 | + |
| 41 | +```bash |
| 42 | +go get github.com/rekby/fixenv |
44 | 43 | ``` |
45 | 44 |
|
| 45 | +Fixenv follows Go modules semantic import versioning. Re-run the command above to upgrade to the latest tag. |
46 | 46 |
|
47 | | -For example with default scope test - it work will done once per test. |
48 | | -With scope TestAndSubtests - cache shared by test (TestFunction(t *testing.T)) and all of that subtest. |
49 | | -With package scope - result cached for all test in package. |
50 | | -Fixture can have cleanup function, that called while out of scope. |
51 | | - |
52 | | -Fixture can call other fixtures, cache shared between them. |
53 | | - |
54 | | -For example simple account test: |
55 | | -```golang |
56 | | -package example |
57 | | - |
58 | | -// db create database abd db struct, cached per package - call |
59 | | -// once and same db shared with all tests |
60 | | -func db(e Env)*DB{...} |
61 | | - |
62 | | -// DbCustomer - create customer with random personal data |
63 | | -// but fixed name. Fixture result shared by test and subtests, |
64 | | -// then mean many calls Customer with same name will return same |
65 | | -// customer object. |
66 | | -// Call Customer with other name will create new customer |
67 | | -// and resurn other object. |
68 | | -func DbCustomer(e Env, name string) Customer { |
69 | | - // ... create customer |
70 | | - db(e).CustomerStore(cust) |
71 | | - // ... |
72 | | - return cust |
73 | | -} |
| 47 | +## Quick start |
| 48 | + |
| 49 | +### Use bundled fixtures instantly |
74 | 50 |
|
75 | | -// DbAccount create bank account for customer with given name. |
76 | | -func DbAccount(e Env, customerName, accountName string)Account{ |
77 | | - cust := DbCustomer(e, customerName) |
78 | | - // ... create account |
79 | | - db(e).AccountStore(acc) |
80 | | - // ... |
81 | | - return acc |
| 51 | +```go |
| 52 | +package context_test |
| 53 | + |
| 54 | +import ( |
| 55 | + "testing" |
| 56 | + |
| 57 | + "github.com/rekby/fixenv" |
| 58 | + "github.com/rekby/fixenv/sf" |
| 59 | +) |
| 60 | + |
| 61 | +func TestAutoCanceledContext(t *testing.T) { |
| 62 | + e := fixenv.New(t) |
| 63 | + ctx := sf.Context(e) |
| 64 | + |
| 65 | + // Exercise your code under test with ctx. |
| 66 | + // When the test finishes, the context is cancelled automatically. |
82 | 67 | } |
| 68 | +``` |
| 69 | + |
| 70 | +`sf.Context` ships with Fixenv and returns a cancellable context bound to the current test scope. When the test ends, the fixture automatically cancels the context through its registered cleanup—no manual teardown required. |
83 | 71 |
|
84 | | -func TestFirstOwnAccounts(t *testing.T){ |
85 | | - e := NewEnv(t) |
86 | | - // background: |
87 | | - // create database |
88 | | - // create customer bob |
89 | | - // create account from |
90 | | - accFrom := DbAccount(e, "bob", "from") |
91 | | - |
92 | | - // get existed db, get existed bob, create account to |
93 | | - accTo := DbAccount(e, "bob", "to") |
94 | | - |
95 | | - PutMoney(accFrom, 100) |
96 | | - SendMoney(accFrom, accTo, 20) |
97 | | - if accFrom != 80 { |
98 | | - t.Error() |
99 | | - } |
100 | | - if accTo != 20 { |
101 | | - t.Error() |
102 | | - } |
103 | | - |
104 | | - // background: |
105 | | - // delete account to |
106 | | - // delete account from |
107 | | - // delete customer bob |
| 72 | +### Write a custom fixture |
| 73 | + |
| 74 | +```go |
| 75 | +package counter_test |
| 76 | + |
| 77 | +import ( |
| 78 | + "testing" |
| 79 | + |
| 80 | + "github.com/rekby/fixenv" |
| 81 | +) |
| 82 | + |
| 83 | +var globalCounter int |
| 84 | + |
| 85 | +func counter(e fixenv.Env) int { |
| 86 | + return fixenv.CacheResult(e, func() (*fixenv.GenericResult[int], error) { |
| 87 | + globalCounter++ |
| 88 | + return fixenv.NewGenericResult(globalCounter), nil |
| 89 | + }) |
108 | 90 | } |
109 | 91 |
|
110 | | -func TestSecondTransferBetweenCustomers(t *testing.T){ |
111 | | - e := NewEnv(t) |
112 | | - |
113 | | - // background: |
114 | | - // get db, existed from prev test |
115 | | - // create customer bob |
116 | | - // create account main for bob |
117 | | - accFrom := DbAccount(e, "bob", "main") |
118 | | - |
119 | | - // background: |
120 | | - // get existed db |
121 | | - // create customer alice |
122 | | - // create account main for alice |
123 | | - accTo := DbAccount(e, "alice", "main") |
124 | | - PutMoney(accFrom, 100) |
125 | | - SendMoney(accFrom, accTo, 20) |
126 | | - if accFrom != 80 { |
127 | | - t.Error() |
128 | | - } |
129 | | - if accTo != 20 { |
130 | | - t.Error() |
131 | | - } |
132 | | - |
133 | | - // background: |
134 | | - // remove account of alice |
135 | | - // remove customer alice |
136 | | - // remove account of bob |
137 | | - // remove customer bob |
| 92 | +func TestCounter(t *testing.T) { |
| 93 | + e := fixenv.New(t) |
| 94 | + if counter(e) != counter(e) { |
| 95 | + t.Fatal("value must be cached within a single test scope") |
| 96 | + } |
138 | 97 | } |
| 98 | +``` |
| 99 | + |
| 100 | +This pattern—create an environment with `fixenv.New(t)`, wrap setup logic in `fixenv.CacheResult`, and optionally attach a cleanup—is the basis for all fixtures. Continue with the [Getting started guide](docs/getting-started.md) for step-by-step instructions and cleanup examples. |
| 101 | + |
| 102 | +## Fixture scopes & caching |
| 103 | + |
| 104 | +Fixtures default to `ScopeTest`: cached per `testing.T`. Subtests get their own scope unless you opt in to sharing. Override the scope through `CacheOptions` to share expensive setup more broadly: |
| 105 | + |
| 106 | +| Scope | Lifetime | Typical use cases | |
| 107 | +|-------|----------|-------------------| |
| 108 | +| `ScopeTest` | Current `testing.T` only | Pure unit tests, short-lived resources | |
| 109 | +| `ScopeTestAndSubtests` | Top-level test and all nested subtests | Reusing setup across a parent test and its subtests | |
| 110 | +| `ScopePackage` | Entire package (requires `TestMain`) | Shared databases, external services, heavy caches | |
| 111 | + |
| 112 | +Scope names combine automatically with the fixture's call site to produce a stable cache key. You can extend the cache key with serialisable parameters via `CacheOptions.CacheKey` when a fixture accepts arguments. |
139 | 113 |
|
140 | | -// background: |
141 | | -// after all test finished drop database |
| 114 | +Learn more in [Scopes and lifetimes](docs/scopes-and-lifetimes.md). |
| 115 | + |
| 116 | +## Cleanup & ordering guarantees |
| 117 | + |
| 118 | +Each fixture may optionally return a cleanup callback. Fixenv registers the callback on the owning `testing.T` and guarantees **LIFO** execution when the scope ends. Cleanups run even if a test fails or is skipped via `ErrSkipTest`, making it safe to provision external services, create temporary files, or adjust global state. |
| 119 | + |
| 120 | +See [Cleanup and ordering](docs/cleanup-and-ordering.md) for practical recipes. |
| 121 | + |
| 122 | +## Documentation |
| 123 | + |
| 124 | +The `docs/` directory provides extended guides: |
| 125 | + |
| 126 | +- [Getting started](docs/getting-started.md) – installation, basic fixtures, and first tests. |
| 127 | +- [Scopes and lifetimes](docs/scopes-and-lifetimes.md) – choosing the right cache level and structuring packages. |
| 128 | +- [Advanced fixtures](docs/advanced-fixtures.md) – parameterised fixtures, dependency injection patterns, and custom environments. |
| 129 | +- [Cleanup and ordering](docs/cleanup-and-ordering.md) – teardown techniques, deterministic ordering, and debugging tips. |
| 130 | +- [Example walkthroughs](docs/examples/README.md) – narrative tours of the sample projects. |
| 131 | + |
| 132 | +## Example gallery |
| 133 | + |
| 134 | +Explore runnable examples under [`examples/`](examples): |
| 135 | + |
| 136 | +| Example | Highlights | |
| 137 | +| ------- | ---------- | |
| 138 | +| [`simple`](examples/simple) | Minimal fixtures, scope defaults, and cleanup basics. | |
| 139 | +| [`custom_env`](examples/custom_env) | Embedding `EnvT` in a domain-specific helper to expose project-specific fixtures. | |
| 140 | +| [`sf_helpers`](examples/sf_helpers) | Using bundled fixtures from `sf` for contexts, temp directories, and local TCP listeners. | |
| 141 | +| [`simple_main_test`](examples/simple_main_test) | Using `TestMain` to install package-level fixtures with cleanup control. | |
| 142 | + |
| 143 | +Run the full set with: |
| 144 | + |
| 145 | +```bash |
| 146 | +go test ./examples/... |
142 | 147 | ``` |
| 148 | + |
| 149 | +Detailed walkthroughs and expected outputs live in [docs/examples/README.md](docs/examples/README.md). |
| 150 | + |
| 151 | +## Comparison with alternatives |
| 152 | + |
| 153 | +| Feature / Project | Fixenv | `testify/suite` | `dockertest` | `gotest.tools/fs` | |
| 154 | +| -------------------------------- | :----: | :-------------: | :----------: | :---------------: | |
| 155 | +| Scoped caching (test / package) | ✅ | ⚪️ manual | ⚪️ manual | ⚪️ manual | |
| 156 | +| Declarative fixture tree | ✅ | ⚪️ | ⚪️ | ⚪️ | |
| 157 | +| Cleanup integration | ✅ (LIFO with fixtures) | ⚪️ (`testing.T.Cleanup`) | ✅ (containers) | ⚪️ (`testing.T.Cleanup`) | |
| 158 | +| Skip-aware setup | ✅ (`ErrSkipTest`) | ⚪️ | ⚪️ | ⚪️ | |
| 159 | +| Works with plain `testing.T` | ✅ | ✅ | ✅ | ✅ | |
| 160 | +| Extra runtime requirements | Go stdlib only | Go stdlib only | Docker daemon | Go stdlib only | |
| 161 | + |
| 162 | +Fixenv focuses on composing reusable fixtures with deterministic lifecycle control. It complements assertion libraries and environment provisioning tools—you can mix Fixenv with them instead of choosing only one approach. |
| 163 | + |
| 164 | +## Project roadmap |
| 165 | + |
| 166 | +- Additional built-in helpers for temporary directories and HTTP servers. |
| 167 | +- Optional tracing hooks for fixture execution and cache hits. |
| 168 | +- Community-contributed examples covering databases, message queues, and cloud resources. |
| 169 | +- Automatic detection of scope-mixing mistakes (e.g. invoking a test-scoped fixture from a package-scoped one) to surface lifecycle issues early. |
| 170 | + |
| 171 | +Interested in a feature? [Open an issue](https://github.com/rekby/fixenv/issues/new) or start a discussion. |
| 172 | + |
| 173 | +## Contributing |
| 174 | + |
| 175 | +Contributions are welcome! Please: |
| 176 | + |
| 177 | +1. Fork the repository and create a feature branch. |
| 178 | +2. Include thorough automated tests with each code change. |
| 179 | +3. Run `go test ./...` before opening a pull request. |
| 180 | +4. Describe your changes in detail and link to related issues. |
| 181 | + |
| 182 | +Bug reports and documentation improvements are also appreciated. |
| 183 | + |
| 184 | +## License |
| 185 | + |
| 186 | +Licensed under the [MIT License](LICENSE.txt). |
0 commit comments