Skip to content
cccskills
FreePublished on GitHub

unit-testing

Small-test style for unit tests — stable boundary, data over mocks, focused assertions, concise makeMe (all packages). Use when writing or changing unit tests in any package.

The English summary is being prepared. Showing the original description.

nerds-odd-e/doughnut50 stars(GitHub 50 + cccskills 0)

Run score Not tested

See how to install

Files included (1)

  • SKILL.md7.6 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

"Small test" style

This repo’s automated tests are E2E or unit tests — nothing in between. Write unit tests in the "small test" style below.

A "small test" means: a fast, in-process unit test (JUnit, Vitest, Bach, …) that drives a stable boundary with crafted data/makeMe, exercises real lower-level production code, and mocks only external dependencies. Stack commands, entry points, and tooling stay in the package skills (frontend / frontend-testing, backend / backend-testing, cli, mcp-server).

Stable boundary

  • Drive the outermost (or domain-stable) interface. Prefer the public surface under test — e.g. a controller, mounted component, CLI run / runInteractive, MCP tool — or an interface that is itself a deliberate, domain-stable contract (pure algorithm, validation API). Do not add a test class per internal production class, and do not widen exports only so tests can reach helpers.
  • Cover lower layers with data, not with more test surfaces. Craft realistic, scenario-specific preconditions and fixtures so one boundary test exercises the real lower-level production routines. Prefer that over isolating each collaborator behind mocks or thin wrapper tests.
  • Build those fixtures with makeMe — keep construction concise (see below).
  • Do not mock unless external or exceptional. Mock only true external dependencies (third-party APIs, network services, and the like) or extremely rare special cases. Package rules name the allowed exceptions (OpenAIClient, frontend mockSdkService for the backend HTTP API, CLI spies on donut-api, etc.). Prefer the real database, real in-process collaborators, and real browser rendering inside the system under test.
  • Loud failures — Code that is allowed to fail loudly is not a unit-test subject for that failure. Policy: ADR 0006 Usage.

Focused assertions

  • One behavior per test. Assert only what this scenario uniquely establishes.
  • Canonical shape once. Shared post-conditions belong in one test. Sibling scenarios assert only their delta.
  • Different precondition ≠ re-assert unrelated post-condition. If outcome X is clearly independent of what changed in the setup, do not repeat X.
  • Prefer asserting the positive signal over “not the other message/alert” when the positive already distinguishes the case.
  • When a change removes something, the tests are those of the behavior that survives or replaces it. Write no test, assertion, fixture, or mock that observes the removed thing (not called, absent, ignored). Apply removal proof to each proof item. Rule: principle 7 in AGENTS.md / CLAUDE.md.
  • In loops (thresholds, retries, frame polls), assert the loop result, not the same intermediate outcome every iteration.
// ❌ every related case repeats the full payload
assertFalse(result.getAnswer().getCorrect());
assertThat(result.getAnswer().getOutcome(), is(ACCIDENTAL_MATCH));
assertThat(result.getMatchedNotes(), hasSize(1));

// ✅ canonical case asserts the shape; this case only the delta
assertThat(result.getAnswer().getOutcome(), is(ACCIDENTAL_MATCH));
// ❌ re-check section when the unique claim is “no link CTAs”
expect(wrapper.find('[data-testid="matched-notes-section"]').exists()).toBe(true)
expect(wrapper.findAll('[data-testid^="wiki-link-or-relationship-to-matched-note-"]')).toHaveLength(0)

// ✅
expect(wrapper.findAll('[data-testid^="wiki-link-or-relationship-to-matched-note-"]')).toHaveLength(0)

Removal proof

For a mixed change, inspect the individual assertions and observations in the proof table, slice Proof, and implementation brief. A scope statement about removal does not make a contradictory proof item acceptable.

  • Surviving or replacement behavior: retain the proof of button states, error feedback, and other promised outcomes, and carry it through all three handoff forms. A negative assertion can prove a surviving business outcome, such as silent completion; classify by what it establishes, not its syntax.
  • Actively enforced refusal: retain its rejection proof, including a read-only user's unavailable editing control. This establishes authorization behavior the product still promises.
  • Removed behavior or interface: delete its assertions and the fixtures, helpers, and mocks used only by them. Do not replace them with assertions that it is absent, not called, or ignored.
  • Sweep: record the one-time acceptance search and classified result as a reading in the plan, separately from surviving-behavior proof. Create no permanent absence test or checker.

The coordinator corrects inherited contradictions before delegation. An implementer or refactorer encountering one skips and reports that item while preserving valid proof; accepting a removal must not discard its surviving behavior's tests.

Destructive operations (hard deletes, delete migrations)

The concise-fixture guidance below is correct for behavior tests. For hard deletes and delete migrations, fixture completeness beats minimality — a bare target row with no children only proves row selection, not referential fan-out.

  • Hard-delete endpoints: build a row in every table that holds an FK into the deleted entity (directly or via CASCADE). Example: MemoryTrackerDeleteControllerTest uses makeMe.aConversation().forARecallPrompt(...) so deleting a tracker exercises the conversation → recall_prompt edge, not just the tracker row.
  • Delete migrations: same rule — seed every child table in the FK closure, not only rows the WHERE clause selects. While a gated migration is pending, pair it with a focused temporary test for the placeholder and row selection; remove that migration-only harness after confirmed production application.
  • Schema guard: DeletableEntityFkClosureTest catches restricting FKs without fixtures; it does not replace completeness tests for runtime delete paths.

Concise makeMe setup

  • Build fixtures with makeMe (donut-test-fixtures/makeMe on TS; backend MakeMe on Java). Rely on builder defaults when a value is unused in assertions and does not affect the logic under test. Exception: destructive operations above — do not minimize away FK children.
  • Do not create users, notebooks, or owners only to satisfy wiring if the builder already defaults them and the test never refers to them.
  • Prefer ownership helpers so dependents inherit the owner (e.g. backend notebookOwnedBy → tracker .by default).
  • Prefer builder APIs over post-construction mutation (e.g. .id(10), not note.id = 10).
  • Prefer domain-shaped helpers over field soup when the same shape repeats (e.g. .accidentalMatch(...), .overlap(...), .aliases(...)).
  • If a mandatory dependency or repeated fixture cannot be expressed concisely, extend the makeMe builder — do not leave verbose setup in every test.
// ❌
Notebook nb = makeMe.aNotebook().creatorAndOwner(currentUser.getUser()).please();
Note n = makeMe.aNote().notebook(nb).title("X").please();
makeMe.aMemoryTrackerFor(n).by(currentUser.getUser()).spelling().please();

// ✅
Note n = makeMe.aNote().notebookOwnedBy(currentUser.getUser()).title("X").please();
makeMe.aMemoryTrackerFor(n).spelling().please();
// ❌
const note = makeMe.aNote.please()
note.id = 10
note.noteTopology.id = 10

// ✅
const note = makeMe.aNote.id(10).please()

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Backend Java — Spring Boot controllers/services, JUnit controller tests, MakeMe. Read this first for any backend work. Use when working on the Java Spring backend.

The English summary is being prepared. Showing the original description.

nerds-odd-e/doughnut50Updated Oct 26, 2026

Backend Java production code, Spring Boot controllers/services/repositories, controller return values, imports, schema ERD pointer. Use when editing backend production Java.

The English summary is being prepared. Showing the original description.

nerds-odd-e/doughnut50Updated Oct 26, 2026

Backend Java tests, controller-level stable-boundary tests, JUnit, @Nested, MakeMe builders, @Transactional, parameterized tests. Use when writing or changing backend JUnit tests.

The English summary is being prepared. Showing the original description.

nerds-odd-e/doughnut50Updated Oct 26, 2026

cli

Free

related to donut CLI. Use when working on donut-cli.

The English summary is being prepared. Showing the original description.

nerds-odd-e/doughnut50Updated Oct 26, 2026

Set up and run the Donut dev environment on Cursor Cloud VM (no Nix). Use when working on Cloud VM, running tests on Cloud VM, or encountering command errors related to missing nix. Triggers on: cloud VM, cloud agent, no nix available, /workspace path.

The English summary is being prepared. Showing the original description.

nerds-odd-e/doughnut50Updated Oct 26, 2026

Regenerate docs/database-erd.md from MySQL information_schema (Mermaid ERD: foreign keys and key columns). Use after Flyway schema changes, new migrations, or when the diagram is stale. Triggers on: database ERD, database-erd.md, export_database_erd, schema diagram, ER diagram after migration.

The English summary is being prepared. Showing the original description.

nerds-odd-e/doughnut50Updated Oct 26, 2026

See all skills by nerds-odd-e

Report a problem with this skill