Testing.ts overview
Test helpers over the Volume and Caller services: a fresh volume with a
root caller as one layer, callers with other credentials on the volume in
context, and collecting a known number of watch changes.
Details
The helpers are plain Effect values with no test-runner dependency. Each
build of layer is a fresh volume, so provide it to every test that needs
its own state, and share one build only between tests that do not
interfere.
Since v0.6.0
combinators
collectChanges
Starts collecting the first n elements of stream and returns an effect
that waits for them.
Details
The collection runs in a fiber of the current scope, so the returned effect can be awaited after the writes that produce the changes. Open the watch stream before writing: a volume publishes only changes committed after the subscription.
Example
import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import * as Testing from "@effect-vfs/core/Testing"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const volume = yield* Vfs.Volume
const changes = yield* Testing.collectChanges(yield* volume.watch(), 1)
yield* (yield* Vfs.Caller).mkdir("/work")
return (yield* changes).map((change) => change._tag)
})
Effect.runPromise(program.pipe(Effect.scoped, Effect.provide(Testing.layer()), Effect.provide(BunCrypto.layer))).then(
console.log
)
// [ 'Create' ]Signature
declare const collectChanges: {
(
n: number
): <A, E, R>(stream: Stream.Stream<A, E, R>) => Effect.Effect<Effect.Effect<Array<A>, E>, never, R | Scope.Scope>
<A, E, R>(
stream: Stream.Stream<A, E, R>,
n: number
): Effect.Effect<Effect.Effect<Array<A>, E>, never, R | Scope.Scope>
}Since v0.6.0
constructors
callerAs
A root caller with identity on the volume in context.
Example
import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import * as Testing from "@effect-vfs/core/Testing"
import { Effect } from "effect"
const program = Effect.gen(function* () {
yield* (yield* Vfs.Caller).mkdir("/private", { mode: 0o700 })
const guest = yield* Testing.callerAs({ uid: 1000, gid: 1000, groups: [], privileged: false })
return (yield* Effect.flip(guest.readDirectory("/private"))).code
})
Effect.runPromise(program.pipe(Effect.provide(Testing.layer()), Effect.provide(BunCrypto.layer))).then(console.log)
// AccessDeniedSignature
declare const callerAs: (
identity: Identity,
options?: Omit<RootCallerOptions, "identity">
) => Effect.Effect<Caller, VfsError, Volume>Since v0.6.0
layers
layer
A fresh volume and a root caller on it, as one layer.
Details
Every build constructs a new volume, from fixture when one is given. The
layer fails with the VfsError the constructor or the caller reports for
invalid options. Provide Crypto.Crypto to build the volume.
Example
import * as BunCrypto from "@effect/platform-bun/BunCrypto"
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
import * as Testing from "@effect-vfs/core/Testing"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const fs = yield* Vfs.Caller
yield* fs.mkdir("/work")
return (yield* fs.readDirectory("/")).value.map((entry) => new TextDecoder().decode(entry.name))
})
// Each provide builds its own volume, so neither run sees the other's writes.
Effect.runPromise(program.pipe(Effect.provide(Testing.layer()), Effect.provide(BunCrypto.layer))).then(console.log)
// [ 'work' ]Signature
declare const layer: (options?: LayerOptions) => Layer.Layer<Volume | Caller, VfsError, Crypto.Crypto>Since v0.6.0
models
LayerOptions (interface)
Options of a test volume layer.
Signature
export interface LayerOptions {
/** Entries the volume starts with; without one the volume starts empty. */
readonly fixture?: Fixture
/** Execution mode, limits and identity of the volume. */
readonly volume?: VolumeOptions
/** Credentials and umask of the root caller the layer provides. */
readonly caller?: RootCallerOptions
}Since v0.6.0