mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-26 15:55:45 +00:00
4.4 KiB
4.4 KiB
Test Fixtures Guide
Temporary Directory Fixture
The tmpdir function in fixture/fixture.ts creates temporary directories for tests with automatic cleanup.
Basic Usage
import { tmpdir } from "./fixture/fixture"
test("example", async () => {
await using tmp = await tmpdir()
// tmp.path is the temp directory path
// automatically cleaned up when test ends
})
Options
git?: boolean- Initialize a git repo with a root commitconfig?: Partial<Config.Info>- Write anopencode.jsonconfig fileinit?: (dir: string) => Promise<T>- Custom setup function, returns value accessible astmp.extradispose?: (dir: string) => Promise<T>- Custom cleanup function
Examples
Git repository:
await using tmp = await tmpdir({ git: true })
With config file:
await using tmp = await tmpdir({
config: { model: "test/model", username: "testuser" },
})
Custom initialization (returns extra data):
await using tmp = await tmpdir<string>({
init: async (dir) => {
await Bun.write(path.join(dir, "file.txt"), "content")
return "extra data"
},
})
// Access extra data via tmp.extra
console.log(tmp.extra) // "extra data"
With cleanup:
await using tmp = await tmpdir({
init: async (dir) => {
const specialDir = path.join(dir, "special")
await fs.mkdir(specialDir)
return specialDir
},
dispose: async (dir) => {
// Custom cleanup logic
await fs.rm(path.join(dir, "special"), { recursive: true })
},
})
Returned Object
path: string- Absolute path to the temp directory (realpath resolved)extra: T- Value returned by theinitfunction[Symbol.asyncDispose]- Enables automatic cleanup viaawait using
Notes
- Directories are created in the system temp folder with prefix
opencode-test- - Use
await usingfor automatic cleanup when the variable goes out of scope - Paths are sanitized to strip null bytes (defensive fix for CI environments)
Testing With Effects
Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows.
Core Pattern
import { describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import { provideTmpdirInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
const it = testEffect(Layer.mergeAll(MyService.defaultLayer))
describe("my service", () => {
it.live("does the thing", () =>
provideTmpdirInstance(() =>
Effect.gen(function* () {
const svc = yield* MyService.Service
const out = yield* svc.run()
expect(out).toEqual("ok")
}),
),
)
})
it.effect vs it.live
- Use
it.effect(...)when the test should run withTestClockandTestConsole. - Use
it.live(...)when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior. - Most integration-style tests in this package use
it.live(...).
Effect Fixtures
Prefer the Effect-aware helpers from fixture/fixture.ts instead of building a manual runtime in each test.
tmpdirScoped(options?)creates a scoped temp directory and cleans it up when the Effect scope closes.provideInstance(dir)(effect)is the low-level helper. It does not create a directory; it just runs an Effect withInstance.currentbound todir.provideTmpdirInstance((dir) => effect, options?)is the convenience helper. It creates a temp directory, binds it as the active instance, and disposes the instance on cleanup.provideTmpdirServer((input) => effect, options?)does the same, but also provides the test LLM server.
Use provideTmpdirInstance(...) by default when a test only needs one temp instance. Use tmpdirScoped() plus provideInstance(...) when a test needs multiple directories, custom setup before binding, or needs to switch instance context within one test.
Style
- Define
const it = testEffect(...)near the top of the file. - Keep the test body inside
Effect.gen(function* () { ... }). - Yield services directly with
yield* MyService.Serviceoryield* MyTool. - Avoid custom
ManagedRuntime,attach(...), or ad hocrun(...)wrappers whentestEffect(...)already provides the runtime. - When a test needs instance-local state, prefer
provideTmpdirInstance(...)orprovideInstance(...)over manualInstance.provide(...)inside Promise-style tests.