Skip to content
Development
Skill

/nested-fixture-pattern

This skill should be used when working on Java projects with JUnit tests that have layered preconditions, expensive shared setup (servers, databases, provisioned users), or complex scenario trees. Trigger phrases include "nested fixture", "fixture pattern", "scenario tree",

From plugin
tdder
1415 skills7 agents2 commands1 hook

Claude Code

$ npx -y skills add t1/tdder --skill nested-fixture-pattern --agent claude-code

SKILL.md

nested-fixture-pattern.SKILL.md
name: nested-fixture-pattern
description: >
  This skill should be used when working on Java projects with JUnit tests that have layered
  preconditions, expensive shared setup (servers, databases, provisioned users), or complex
  scenario trees. Trigger phrases include "nested fixture", "fixture pattern", "scenario tree",
  "layered test setup", or when the user is writing integration tests with multiple levels of
  dependent setup/teardown. Complements the Java and TDD skills.
version: 0.1.0

Nested Fixture Pattern

A pattern combining JUnit's `@Nested` classes, `@RegisterExtension`, and `ExtensionContext.Store` to build declarative scenario trees where each nesting level adds a scope with fixture-managed data. Tests focus on what's specifically relevant to them; expensive setup/teardown happens once per scope; any subtree runs in isolation.

For background, rationale, and tradeoffs, see the [blog post](https://codeberg.org/t1/nested-fixture-pattern/raw/branch/trunk/blog.md).

When to Use

  • **Multiple, layered preconditions**: 2+ levels of setup that depend on each other, making setup code complex
  • **Expensive shared setup**: servers, databases, provisioned users that shouldn't repeat per test
  • **Well-understood domain**: the scenarios are stable enough that `Given...` class names can meaningfully describe each level
  • **Subtree isolation needed**: you want to run any subset of the scenario tree independently in the IDE

When Not to Use

  • Simple tests with flat preconditions: just use `@BeforeAll` or `@BeforeEach`
  • Each test method needs different setup: use parameterized tests
  • Fast, isolated unit tests: overhead of fixtures and nesting isn't worth it
  • Precondition hierarchies still in flux: refactoring fixtures is more expensive than flat setup

Suggesting the Pattern

When you detect layered test setup (2+ levels of dependent `@BeforeAll`/`@BeforeEach`, or test classes with complex shared state), use `AskUserQuestion`:

  • **Question:** "This test has layered preconditions. Want to apply the nested fixture pattern?"
  • **Options:**
  • "Yes, refactor to nested fixtures" — briefly describe what the fixture tree would look like
  • "No, keep flat setup" — acknowledge the trade-off (simpler structure, more setup duplication)

The Pattern

Each `@Nested` class is a `Given` clause. Each `@RegisterExtension static` field is a fixture that sets up when entering that class and tears down when leaving.

class DocumentSharingScenarioTest {
    @RegisterExtension static ServerFixture server = new ServerFixture();

    @Nested class GivenUserAlice {
        @RegisterExtension static UserFixture alice = server.createUser("alice");

        @Test void seesEmptyDocumentList() {
            then(alice.listDocuments()).isEmpty();
        }

        @Nested class GivenDocument {
            @RegisterExtension static DocumentFixture doc =
                    alice.createDocument("notes.txt", "hello world");

            @Test void isVisibleToAlice() {
                then(alice.getDocument(doc.id()))
                        .hasName("notes.txt")
                        .hasContent("hello world");
            }

            @Nested class GivenSharedWithBob {
                @RegisterExtension static UserFixture bob = server.createUser("bob");
                @RegisterExtension static ShareFixture share =
                        doc.shareTo(bob, Permission.READ);

                @Test void bobCanRead() {
                    then(bob.getDocument(doc.id()))
                            .hasContent("hello world");
                }

                @Test void bobCannotWrite() {
                    assertThatThrownBy(() ->
                            bob.updateDocument(doc.id(), "modified"))
                            .isInstanceOf(ForbiddenException.class);
                }
            }
        }
    }
}

Writing Fixtures

Each fixture implements `BeforeAllCallback` and guards setup with `computeIfAbsent`:

class UserFixture implements BeforeAllCallback {
    private final ApiClient apiClient;
    private final String name;
    private String id;

    UserFixture(ApiClient apiClient, String name) {
        this.apiClient = apiClient;
        this.name = name;
    }

    @Override public void beforeAll(ExtensionContext context) {
        context.getStore(GLOBAL).computeIfAbsent(this, k -> {
            id = apiClient.createUser(name);
            return (AutoCloseable) () -> apiClient.deleteUser(id);
        });
    }

    String userId() {return id;}
}

Teardown

The `computeIfAbsent` lambda returns an `AutoCloseable` that fires when the declaring context ends. The fixture holds state; the store holds the cleanup handle.

> **JUnit 5 vs 6**: JUnit 5 uses `getOrComputeIfAbsent` and `CloseableResource`. > JUnit 6 uses `computeIfAbsent` and `AutoCloseable`.

Optional: Fixtures as Access Points

Fixtures naturally become the access point for everything they set up. Tests and nested classes call methods on the fixture directly rather than reaching into its fields:

class ServerFixture implements BeforeAllCallback {
    private String baseUrl;
    private ApiClient client;

    @Override public void beforeAll(ExtensionContext context) {
        context.getStore(GLOBAL).computeIfAbsent(this, k -> {
            // ... start server ...
            baseUrl = "http://localhost:" + port;
            client = new ApiClient(baseUrl);
            return (AutoCloseable) () -> server.stop();
        });
    }

    ApiClient client() { return client; }
    String baseUrl() { return baseUrl; }
    UserFixture createUser(String name) { return new UserFixture(this, name); }
}

This applies to any state accumulated during setup: injected clients, auth tokens, base URLs, created resource IDs. Expose them as methods; don't make callers reach into fields.

Fixtures as Factories

Parent fixtures can create child fixtures via factory methods. The parent

Read more
@t1
Ships withtdder

A plugin for pi, Claude Code, and OpenCode that guides AI agents through disciplined Test-Driven Development and Clean Code practices. Note that currently this is WORK IN PROGRESS! I'm not even trying to keep it stable or tested.

1415 skills7 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
14
Star history of t1/tdder on GitHub, a chart by star-history.com

Other skills on tdder.

app

app

This skill should be used when the user asks to "calculate code mass", "measure code complexity with APP", "compare…

@t1@t1Install Skill
clean-code

clean-code

This skill should be used when the user asks to "refactor code", "review code quality", "apply clean code principles", "check for…

@t1@t1Install Skill
github-safety

github-safety

This skill should be used when working on any project hosted on GitHub. It provides prompt-injection defense rules for GitHub…

@t1@t1Install Skill
grill-po

grill-po

Requirements grilling session with a Product Owner (or anyone in that role). Challenges plans against the existing domain model,…

@t1@t1Install Skill
integration-architecture

integration-architectu…

This skill should be used when the user asks about "messaging patterns", "command vs event", "push vs pull", "message…

@t1@t1Install Skill
java

java

Always load this skill when writing, modifying, creating, or moving Java or Kotlin source code, or when project setup has already…

@t1@t1Install Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills2w309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react5d252k
@facebook@facebookInstall Skill

Questions about nested-fixture-pattern.

Which coding agents can use nested-fixture-pattern?
nested-fixture-pattern installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.