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>
}

Source

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)
// AccessDenied

Signature

declare const callerAs: (
  identity: Identity,
  options?: Omit<RootCallerOptions, "identity">
) => Effect.Effect<Caller, VfsError, Volume>

Source

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>

Source

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
}

Source

Since v0.6.0