Agent skill

Nullables Testing Pattern

by lexler in lexler/skill-factory

Teaches the Nullables pattern for testing without mocking libraries: production classes with an off switch, stubbed only at the third-party edge.

Apache-2.0Auto-check passedTesting & QA

Install Nullables Testing Pattern

skills CLI
$ npx skills add lexler/skill-factory --skill nullables -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install lexler/skill-factory nullables --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/lexler/skill-factory.git skills-src && mkdir -p .claude/skills && cp -r skills-src/output_skills/testing/nullables .claude/skills/nullables && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
nullables
GitHub stars
239
Token cost
~2.2k tokens
SKILL.md length
1,127 words
Files
9 (incl. references)
Skills in repo
25
Repo updated
First seen
Licence
Apache-2.0

At a glance

Teaches the Nullables pattern for testing without mocking libraries: production classes with an off switch, stubbed only at the third-party edge.

  • Works in 4 steps: List the dependencies of the class under… → Follow that dependency's chain down… → Walk back up the chain, giving each… → …
  • Writing unit tests for code that calls HTTP, databases, files, the clock or random
  • SKILL.md covers The cut, Two channels, plus events, Procedure and Rules at every layer, plus 1 more section
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Nullables are production classes with an off switch. A class that touches external I/O anywhere in its dependencies offers `create()` for the real thing and `createNull()` for a version with I/O disabled while everything else runs normally. Tests are narrow, focused on one class, sociable, with real dependencies, and state-based, asserting outputs and state rather than method calls. Mocking libraries and dependency injection frameworks are not used.

The only stub sits at the lowest point, the third-party edge. A high-level wrapper abstracts one service and speaks domain language, a low-level wrapper abstracts one technology, and only the low-level wrapper holds the embedded stub that returns canned data. Parsing, mapping and normalization code you wrote must stay above that cut so tests exercise it. Reference files cover architecture, high- and low-level wrappers, consuming Nullables, migration and utilities, and the excerpt is cut off before the interaction moves.

When your agent uses it

  • Writing unit tests for code that calls HTTP, databases, files, the clock or random
  • Replacing mocking libraries in an existing test suite
  • Making a system testable without a dependency injection framework
  • Speeding up tests that are slow or flaky because of real I/O

Example prompts

  • “Rewrite the OrderService tests to use Nullables instead of mocks.”
  • “Add a createNull() to our HttpClient wrapper that returns canned responses.”
  • “Make the tests that depend on the system clock deterministic.”

Workflow steps

4 steps, taken from the first numbered list in SKILL.md.

  1. List the dependencies of the class under test. Classify each one you need to control
  2. Follow that dependency's chain down until you reach code you don't own — a third-party library doing I/O. That is the edge.
  3. Walk back up the chain, giving each class create() and createNull() that compose its nulled dependencies — follow…
  4. Write the tests following consuming-nullables.md — the fixture shape, error configs, and time travel live there. Done when every read…

What it can do on your machine

Read from SKILL.md and the folder at commit 8017333. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are javascript).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Nullables Testing Pattern loads about 2.2k tokens when it runs, and up to ~16k if it reads all its reference files. Until then it costs about 72 tokens; SKILL.md has 1,127 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~72
When it runs · the whole SKILL.md, loaded when a task matches
~2.2k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~16k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from lexler/skill-factory at commit 8017333, republished under its Apache-2.0 licence (© lexler). 1,127 words, ~2,212 tokens.

Download SKILL.mdSave it as .claude/skills/nullables/SKILL.md (or your agent's skills folder). This skill also uses 8 other files; get the full folder from GitHub.
name
nullables
description
Nullables — testing technique alternative to using mocking libraries. Use when writing unit tests, when code touches external I/O or state (HTTP, databases, files, clock, random) anywhere in its dependency chain, when making a system testable, or when tests are slow or flaky.

Nullables: Testing Without Mocks

STARTER_CHARACTER = ⭕️

Nullables are production code with an off switch: classes with external I/O anywhere in their dependencies offer create() (real) and createNull() (I/O disabled, everything else runs normally). Tests are narrow (focused on one class), sociable (dependencies run for real), and state-based (assert outputs and state, never method calls). Don't use mocking libraries or DI frameworks — Nullables make them unnecessary.

The cut

Stub at the lowest point — the third-party edge — never your own code:

OrderService  →  PaymentClient  →  HttpClient  →  third-party lib
  app code       high-level        low-level       ✂ stubbed when nulled
                 wrapper           wrapper
  • PaymentClient is a high-level wrapper: it abstracts one service and speaks domain language.
  • HttpClient is a low-level wrapper: it abstracts one technology and is generic and highly reusable.
  • The low-level wrapper holds the fork: create() wires the real library (node http, RestTemplate); createNull() wires an embedded stub — your code, returning canned data, doing no I/O.
  • Everything left of the cut is your code and runs for real in tests.

With mocks, you only mock code you own; with Nullables, you only stub code you don't own. Only the bottom layer has a stub — one per technology. Everything above runs real in tests, so a bug anywhere in your code turns tests red. Mocking your own classes breaks that chain: mocked code never runs, and its bugs hide behind green tests.

An invented internal seam is the same break in disguise: cutting at rows() → List<Row> instead of the driver puts your mapping loop below the seam, where nulled tests never run it. The test: any parsing, mapping, or normalization you wrote must sit above the cut. When the third-party API is a chain of objects, mirror it — one stub class can play the whole chain (see the low-level wrapper files).

Two channels, plus events

Every class that talks to infrastructure anywhere in its dependencies offers the same two factory methods:

javascript
Clock.create()                            // production: the real system clock
Clock.createNull({ now: "2024-01-01" })   // test: frozen time, no external state

Tests interact with a nulled instance through three moves:

  • Reads — configure what the world answers, as createNull(...) parameters in the caller's domain terms: PaymentClient.createNull({ approved: false }), DieRoller.createNull([3, 5, 1]). A single value repeats forever; a list is consumed in order, then fails fast. An error is just another configured response: createNull([{ error: "boom" }]).

  • Writes — observe what the code sent, as domain data (track the data, not the rendered string):

    javascript
    const emails = emailer.trackOutput();
    await service.register("a@b.com");
    assert.deepEqual(emails.data, [{ to: "a@b.com", subject: "Welcome" }]);

    The same tracker can prove a negative — a test where registration is refused asserts emails.data is []: no email went out.

  • Pushed events — fire a simulated incoming event through the same handler path a real event takes: network.simulateMessage("client-1", "Hello").

These ride on two tiny utilities, OutputListener/OutputTracker and ConfigurableResponses. When the codebase lacks them, add them — example implementations in utilities.md.

Procedure

Start from the code you need to test. For a whole system, pick one class and repeat; conversion order across many classes is in migration.md.

  1. List the dependencies of the class under test. Classify each one you need to control:
    • Pure logic, nothing external below → test directly, nothing to null.
    • Value object or config → createTestInstance() with safe overridable defaults. If it holds an infrastructure object, default it to the nulled version.
    • Already has createNull() → go to step 4.
    • Touches infrastructure below but has no createNull() → step 2.
  2. Follow that dependency's chain down until you reach code you don't own — a third-party library doing I/O. That is the edge.
  3. Walk back up the chain, giving each class create() and createNull() that compose its nulled dependencies — follow building-high-level-wrappers.md; its recipe carries the decomposition and tracker moves that keep layers honest, and this is where abstractions leak if you rush. Done when every class between the edge and the class under test has both factories, configuration in its own language decomposed downward, and its write channel tracked.
  4. Write the tests following consuming-nullables.md — the fixture shape, error configs, and time travel live there. Done when every read, write, and error path of the class is asserted — both directions per dependency: the exact outgoing request (via that dependency's tracker) and the returned answer being used.

Converting a mock-based suite → migration.md. Improving existing nullables → walk their layers and check each against The cut and the rules below, plus: no leftover throwaway stubs. Structuring a new app around this (optional) → architecture.md.

Show full SKILL.md (402 more words)Show less

Rules at every layer

Before committing a converted class, walk these as a checklist against it.

  • create() wires production, createNull() wires nulled — both factories live on the wrapped class, never on the stub. The plain constructor is the test seam: tests use it to inject dependencies they hold handles on.
  • Configure and assert as the state of the world the caller wants to control, in the caller's language: PaymentClient.createNull({ approved: false }), not HTTP statuses. Each layer decomposes its configuration into its dependency's language.
  • Bare createNull() always works: every parameter has a safe default, so one call nulls the whole dependency chain from the top (parameterless instantiation).
  • Every invented default is loud and self-naming — "Nulled HttpClient default body", status 503, timezone Australia/Lord_Howe; stub errors self-name too ("Nulled Jdbc: the database is down"). Nothing breaks on these, but a test that accidentally depends on one sees obviously fake data instead of passing by luck. Collections default empty — the default world is empty; absurd entries would be mistakable for real data. Failing fast is reserved for overrunning explicit configuration: an exhausted response list throws "No more responses configured…".
  • Constructors do no work. Connecting, starting, listening happen in explicit methods, so instantiating the whole dependency tree is always safe.
  • One test helper owns construction and wiring (signature shielding): optional named parameters with IRRELEVANT_* defaults, returning a bag of results and trackers. A signature change hits one place.
  • Stay in consumer scope: assert that the request went out and the answer got used. The dependency's own tests cover its behavior.
  • Wrappers validate external responses hard and throw detailed errors on anything unexpected (paranoic telemetry); callers decide how to recover. Test error paths as thoroughly as happy paths — they cost the same now.
  • Only the lowest wrapper gets narrow integration tests against the real system. They document the third-party behavior the stub must match — that pairing keeps the stub honest.

Anti-patterns

  • Stubs in test files — the embedded stub is production code and lives with its wrapper. Nulled instances have production uses of their own: a dry-run flag, cache warming.
  • A stub that reimplements the real system — stubs return canned data; needing real logic means you're cutting at the wrong level.
  • A nulled flag forking the wrapper's logic with if-branches — nulling swaps the wrapped dependency at the seam; the wrapper keeps one code path.
  • Computing an assertion's expected value with the code under test — the test then verifies nothing.

© lexler, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 8 other files (references) in output_skills/testing/nullables of lexler/skill-factory.

  • SKILL.md
  • credits.md
  • references/architecture.md
  • references/building-high-level-wrappers.md
  • references/building-low-level-wrappers-dynamic.md
  • references/building-low-level-wrappers-static.md
  • references/consuming-nullables.md
  • references/migration.md
  • references/utilities.md

Open the folder on GitHubat commit 8017333

Compare with similar skills

Nullables Testing Pattern next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Nullables Testing Pattern compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Nullables Testing Pattern this skilllexler/skill-factory239—~2.2kAutomated safety check: PassApache-2.0
Write Vibe Testsmistralai/mistral-vibe5.1k—~1.2kAutomated safety check: PassApache-2.0
cmux Testing Rulesdisler/learning-cmux-with-agents115—~1.2kAutomated safety check: PassMIT
Nestjs Expertdavila7/claude-code-templates32k6 repos~5.3kAutomated safety check: NotesMIT
Testing OpenLogi UIAprilNEA/OpenLogi23k—~1.1kAutomated safety check: PassApache-2.0
Async Test And Doc Syncdiscord-php/DiscordPHP1.1k—~3.4kAutomated safety check: NotesMIT

Similar skills

  • Write Vibe Tests

    mistralai/mistral-vibe

    Official

    Guides writing or refactoring tests for the Mistral Vibe CLI agent so they check behavior through stable boundaries, like tool invocation or saved session shape, instead of internal calls.

    5.1k GitHub stars~1.2k tokensUpdated yesterday
    Testing & QAAuto-check passed
  • cmux Testing Rules

    disler/learning-cmux-with-agents

    Testing rules for the cmux Swift codebase: Swift Testing as the default framework, a two-commit regression policy, and tests that check runtime behavior, not source text.

    115 GitHub stars~1.2k tokensUpdated 3 mo ago
    Testing & QAAuto-check passed
  • Nestjs Expert

    davila7/claude-code-templates

    Nest.js framework expert specializing in module architecture, dependency injection, middleware, guards, interceptors, testing with Jest/Supertest, TypeORM/Mongoose integration, and Passport.js…

    32k GitHub starsUsed in 6 repos~5.3k tokens
    Testing & QAAuto-check: notes
  • Testing OpenLogi UI

    AprilNEA/OpenLogi

    Verifies OpenLogi's native GPUI interface with focused tests, the component gallery and a mock agent, choosing the evidence that fits each change.

    23k GitHub stars~1.1k tokensUpdated 4 days ago
    Testing & QAAuto-check passed
  • Async Test And Doc Sync

    discord-php/DiscordPHP

    Maintain test and documentation alignment — PHPUnit tests, async testing patterns, PHPDoc contracts, guide pages, and documentation workflow.

    1.1k GitHub stars~3.4k tokensUpdated yesterday
    Testing & QAAuto-check: notes
  • Designing Tests

    CloudAI-X/claude-workflow-v2

    Designs and implements testing strategies for any codebase. An agent skill from CloudAI-X/claude-workflow-v2.

    1.4k GitHub starsUsed in 1 repo~1.5k tokens
    Testing & QAAuto-check passed

More from lexler/skill-factory

All 25 skills in this repo
  • C4 Architecture Diagrams

    lexler/skill-factory

    Creates C4 model diagrams at every zoom level, from system landscape to code, in ASCII, Mermaid or Structurizr, for designing or documenting software architecture.

    239 GitHub stars~2.3k tokensUpdated 1 mo ago
    Auto-check passed
  • Launching Agent Teams

    lexler/skill-factory

    Plans and launches Claude Code agent teams with distinct roles, right-sized tasks and detailed spawn prompts, and says when subagents or worktrees fit better.

    239 GitHub stars~1.3k tokensUpdated 1 mo ago
    Auto-check passed
  • Claude Code Statusline Writer

    lexler/skill-factory

    Guides writing and debugging Claude Code status line scripts that read session JSON from stdin and print one line of text.

    239 GitHub stars~872 tokensUpdated 1 mo ago
    Auto-check passed
  • Catalog of obstacles, anti-patterns and patterns for working with AI coding agents, covering context management and reliability, from a published patterns collection.

    239 GitHub stars~1.5k tokensUpdated 1 mo ago
    Auto-check passed
  • Approval Testing Toolkit

    lexler/skill-factory

    Writes snapshot-style approval tests in Python, JavaScript, TypeScript or Java, comparing output against an approved file instead of writing individual assertions.

    239 GitHub stars~1.2k tokensUpdated 1 mo ago
    Auto-check passed
  • Hotspots

    lexler/skill-factory

    Find where a codebase actually costs time by mining its git history (Tornhill hotspot analysis).

    239 GitHub stars~1.2k tokensUpdated 1 mo ago
    Auto-check passed

Questions about Nullables Testing Pattern

What does Nullables Testing Pattern do?

Teaches the Nullables pattern for testing without mocking libraries: production classes with an off switch, stubbed only at the third-party edge. Nullables are production classes with an off switch. A class that touches external I/O anywhere in its dependencies offers `create()` for the real thing and `createNull()` for a version with I/O disabled while everything else runs normally.

When should I use Nullables Testing Pattern?

Nullables Testing Pattern fits situations like: writing unit tests for code that calls HTTP, databases, files, the clock or random; replacing mocking libraries in an existing test suite; making a system testable without a dependency injection framework; speeding up tests that are slow or flaky because of real I/O.

How do I install Nullables Testing Pattern in Claude Code?

Run `npx skills add lexler/skill-factory --skill nullables -a claude-code`. Or copy the skill folder (output_skills/testing/nullables in lexler/skill-factory) into .claude/skills/nullables in your project. Claude Code loads it when a task matches its description.

How do I install Nullables Testing Pattern in Codex?

Run `npx skills add lexler/skill-factory --skill nullables -a codex`. Or copy the skill folder (output_skills/testing/nullables in lexler/skill-factory) into .agents/skills/nullables in your project. Codex loads it when a task matches its description.

Can I use Nullables Testing Pattern in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add lexler/skill-factory --skill nullables -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/nullables, .gemini/skills/nullables, .github/skills/nullables and .opencode/skills/nullables in your project.

What does Nullables Testing Pattern need to run?

SKILL.md names no scripts, command-line tools or credentials: Nullables Testing Pattern is instructions for the agent only.

Does Nullables Testing Pattern access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Nullables Testing Pattern safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Nullables Testing Pattern use?

Nullables Testing Pattern is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Nullables Testing Pattern use?

About 2.2k tokens (SKILL.md is roughly 8.8k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 14k tokens, read only when the agent opens those files.

What are the alternatives to Nullables Testing Pattern?

Skills that share tags, products or a category with Nullables Testing Pattern: Write Vibe Tests (mistralai/mistral-vibe, 5.1k stars), cmux Testing Rules (disler/learning-cmux-with-agents, 115 stars), Nestjs Expert (davila7/claude-code-templates, 32k stars) and Testing OpenLogi UI (AprilNEA/OpenLogi, 23k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Nullables Testing Pattern?

lexler (a GitHub user) maintains it in lexler/skill-factory, which has 239 GitHub stars. The repository holds 25 skills in this directory. The repository was last updated on August 26, 2026.

Source: lexler/skill-factory on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.