Backend Testing Rules
SkillDev toolsBackend Java tests, controller-level stable-boundary tests, JUnit, @Nested, MakeMe builders, @Transactional, parameterized tests. Use when writing or changing backend JUnit tests.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Backend Testing Rules skill
What this skill tells your AI
The instructions your AI receives, as published by nerds-odd-e/doughnut in .agents/skills/backend-testing/SKILL.md and read by ahel’s review.
Style ("small test" practice — stable boundary, data over mocks, focused assertions, concise makeMe): the unit-testing skill — follow that first.
Commands
Run backend verification from the repo root:
CURSOR_DEV=true nix develop -c pnpm backend:verify
When no database migration is involved, this is faster:
CURSOR_DEV=true nix develop -c pnpm backend:test_only
Always run all backend unit tests instead of a selected file or test case.
For dough-test-optimization, use backend:test_only as both the ordinary
feedback measurement and profiling run. Per-test timings are the time
attributes in backend/build/test-results/test/TEST-*.xml; keep any derived raw
profile outside committed files.
Core Principles
Backend application of the "small test" style (unit-testing skill):
- Prefer controller (or other HTTP-stable-boundary) tests for behavior users see through HTTP. These tests often do not reference the internal class you edited; cover services/repos via realistic
makeMepreconditions and the real DB. - Test services and algorithms directly only when they are an independent, intentional domain-stable contract (pure logic or algorithms).
- Keep tests small and focused: one behavior per test and descriptive names that explain the behavior.
Controller-style example:
@Test
void shouldBeAbleToSaveNoteWhenValid() throws UnexpectedNoAccessRightException {
Note note = makeMe.aNote().creatorAndOwner(userModel).please();
final NoteRealm noteRealm = controller.show(note);
assertThat(noteRealm.getId(), equalTo(note.getId()));
}
Prefer injecting the controller on ControllerTestBase (as above). Do not add @AutoConfigureMockMvc or extra @MockitoBean / @TestBean on a subclass unless that exact annotation mix already exists — each unique mix caches another ApplicationContext and Hikari pool; CI MySQL then fails with Too many connections while a local run still passes. To observe a package-private persistence table, query through EntityManager rather than opening a new MockMvc context.
Independent algorithm example:
@ParameterizedTest
@CsvSource({
"moon, partner of earth, partner of earth",
"Sedition, word sedition means this, word [...] means this"
})
void clozeDescription(String title, String markdown, String expectedClozeDescription) {
assertThat(
new ClozedString(clozeReplacement, markdown).hide(new NoteTitle(title)).maskedContentAsMarkdown(),
containsString(expectedClozeDescription));
}
Database Tests
- Tests use actual database interactions with
@Transactional. - This gives confidence in database operations and repository behavior.
- A
@Formulafield (for exampleNote.trashedInDatabase) is hydrated only when Hibernate loads the row from the database. An entity persisted or moved earlier in the same persistence context still carries the Java default, so a test that reads such a field through a loaded collection mustmakeMe.refresh(entity)first. Query-level predicates (Note.JPA_AVAILABLE) evaluate in SQL and need no refresh.
@SpringBootTest
@ActiveProfiles("test")
@Transactional
class RestNoteControllerTests {
// ...
}
MakeMe Builders
- Use the central
makeMefactory; chain methods; end withplease()(orplease(boolean)for persistence control). - Builders handle relationships and defaults — see
unit-testingskill for when to extend them vs set fields in the test. - Ownership: prefer
notebookOwnedBy(user)soaMemoryTrackerFor(note)inherits the owner.
Note note = makeMe.aNote()
.notebookOwnedBy(user)
.title("title")
.content("description")
.please();
Test Organization
- Group related tests with
@Nested. - Use
@BeforeEachfor common setup, keeping setup minimal and relevant to the group. - Mocking policy:
unit-testingskill. Backend exception: external services only — mockOpenAIClientstructured Responses output withOpenAiStructuredResponseMockin controller tests.
Assertions
- Use
assertThatwith descriptive matchers;assertThrowsfor exceptions;@ParameterizedTestwhen inputs vary but the assertion focus stays the same. - Assertion scope and avoiding cross-test redundancy:
unit-testingskill.
Signals
- GitHub stars
- 49
- Forks
- 72
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
backend-testing- Source
- github.com/nerds-odd-e/doughnut