backend-testing · diff
git:20260805.73917ee to git:20260829.17a3fcc
0 added, 2 removed. Audit A to A.
---
description: Backend Java tests, controller-level stable-boundary tests, JUnit, @Nested, MakeMe builders, @Transactional, parameterized tests
globs: backend/src/test/**/*.java
alwaysApply: false
---
# Backend Testing Rules
- Use this rule when creating or updating backend Java tests.
-
**Style ("small test" practice — stable boundary, data over mocks, focused assertions, concise makeMe):** always-applied `unit-testing.mdc` — follow that first.
## Commands
Run backend verification from the repo root:
```bash
CURSOR_DEV=true nix develop -c pnpm backend:verify
```
When no database migration is involved, this is faster:
```bash
CURSOR_DEV=true nix develop -c pnpm backend:test_only
```
Always run all backend unit tests instead of a selected file or test case.
## Core Principles
Backend application of the "small test" style (`unit-testing.mdc`):
1. 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 `makeMe` preconditions and the real DB.
2. Test services and algorithms directly only when they are an independent, intentional domain-stable contract (pure logic or algorithms).
3. Keep tests small and focused: one behavior per test and descriptive names that explain the behavior.
Controller-style example:
```java
@Test
void shouldBeAbleToSaveNoteWhenValid() throws UnexpectedNoAccessRightException {
Note note = makeMe.aNote().creatorAndOwner(userModel).please();
final NoteRealm noteRealm = controller.show(note);
assertThat(noteRealm.getId(), equalTo(note.getId()));
}
```
Independent algorithm example:
```java
@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.
```java
@SpringBootTest
@ActiveProfiles("test")
@Transactional
class RestNoteControllerTests {
// ...
}
```
## MakeMe Builders
- Use the central `makeMe` factory; chain methods; end with `please()` (or `please(boolean)` for persistence control).
- Builders handle relationships and defaults — see `unit-testing.mdc` for when to extend them vs set fields in the test.
- Ownership: prefer `notebookOwnedBy(user)` so `aMemoryTrackerFor(note)` inherits the owner.
```java
Note note = makeMe.aNote()
.notebookOwnedBy(user)
.title("title")
.content("description")
.please();
```
## Test Organization
- Group related tests with `@Nested`.
- Use `@BeforeEach` for common setup, keeping setup minimal and relevant to the group.
- Mocking policy: `unit-testing.mdc`. Backend exception: external services only — mock `OpenAIClient` structured Responses output with `OpenAiStructuredResponseMock` in controller tests.
## Assertions
- Use `assertThat` with descriptive matchers; `assertThrows` for exceptions; `@ParameterizedTest` when inputs vary but the assertion focus stays the same.
- Assertion **scope** and avoiding cross-test redundancy: `unit-testing.mdc`.