# Testing Patterns

> Testing conventions for @webiny/stdlib — container setup, tmpdir patterns, browser environment directives.

- Skill: `webiny/testing-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add webiny/testing-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/webiny/testing-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: webiny (https://skillmd.com/u/webiny)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/webiny/testing-patterns

---


# Testing Patterns in @webiny/stdlib

Tests live in `__tests__/` at the repo root (outside `src/`), organised into `__tests__/node/` and `__tests__/browser/`. A single `vitest.config.ts` at the repo root handles both test discovery and coverage — there is no workspace file.

## Container setup — `makeContainer()`

Every test file that tests a tool creates a `makeContainer()` helper. Silence logs during tests by registering a config instance with `logLevel: "error"` before registering the logger feature, then register the features under test.

```ts
import { Container } from "@webiny/di";
import { FileTool, FileToolFeature } from "../features/FileTool/index.js";
import { DirectoryToolFeature } from "../features/DirectoryTool/index.js";
import { PinoLoggerConfig, PinoLoggerFeature } from "@webiny/stdlib/node";

function makeContainer(): Container {
  const container = new Container();
  container.registerInstance(PinoLoggerConfig, {
    getConfig: () => ({ logLevel: "error" as const, transport: "json" as const })
  });
  PinoLoggerFeature.register(container);
  DirectoryToolFeature.register(container);
  FileToolFeature.register(container);
  return container;
}
```

For stdlib common tests (testing with `ConsoleLogger` instead of `PinoLogger`), use `ConsoleLoggerConfig` and `ConsoleLoggerFeature` from `@webiny/stdlib` instead.

Resolve instances in `beforeEach` so each test gets a fresh container — this avoids singleton state bleeding across tests:

```ts
let tool: FileTool.Interface;

beforeEach(() => {
  tool = makeContainer().resolve(FileTool);
});
```

**Rule**: never construct an implementation class directly in a test. Always resolve via the container, so the test exercises the same wiring as production.

## tmpdir cleanup

Use Node's `tmpdir()` for temporary directories in filesystem tests. Always clean up in `afterEach`:

```ts
import { join } from "node:path";
import { tmpdir } from "node:os";
import { mkdirSync, rmSync } from "node:fs";

let tmpDir: string;

beforeEach(() => {
  tmpDir = join(tmpdir(), `wby-test-${Date.now()}`);
  mkdirSync(tmpDir, { recursive: true });
});

afterEach(() => {
  rmSync(tmpDir, { recursive: true, force: true });
});
```

## Browser environment directive

`vitest.config.ts` does NOT set a global `environment`. Browser tests opt into the happy-dom environment on a per-file basis using a directive at the very top of the test file:

```ts
// __tests__/browser/LocalStorageCache.test.ts
// @vitest-environment happy-dom
import { ... } from "@webiny/stdlib/browser";
```

Node tests need no environment directive — Vitest defaults to the Node environment.

**happy-dom spy cleanup**: `vi.restoreAllMocks()` does not reliably clean up `vi.spyOn` calls on happy-dom objects (e.g. `localStorage.setItem`). Always call `spy.mockRestore()` explicitly in a `finally` block when spying on happy-dom storage.

## Type-checking tests

Test files must be type-correct. `yarn typecheck` covers `__tests__/` via each slice's check config (`tsconfig.check.common.json`, `tsconfig.check.node.json`, `tsconfig.check.browser.json`). Type errors in tests are caught before any test runs.

## Coverage

`yarn test:coverage` runs the full suite with v8 coverage in one invocation, using the coverage block in `vitest.config.ts`:

```ts
coverage: {
  provider: "v8",
  include: ["src/**/*.ts"],
  exclude: ["**/__tests__/**", "**/index.ts", "**/abstractions/**"]
}
```

Barrel files (`index.ts`) and abstraction files are excluded from coverage since they contain no executable logic worth measuring — only implementation and feature files are covered.

## Running tests

```sh
yarn test            # all tests
yarn test:coverage   # with v8 coverage
```

Both must pass as part of the pre-commit chain before committing:

```sh
yarn && yarn adio && yarn format:fix && yarn lint:fix && yarn typecheck && yarn build && yarn test:coverage
```

See the `adding-a-feature` skill for where writing tests fits into the overall workflow when introducing a new tool.

