Spartan-ai-toolkit testing-strategies
Testing patterns for Micronaut/Kotlin backend including repository tests, integration tests, and test data builders. Use when writing tests, setting up test infrastructure, or improving coverage.
install
source · Clone the upstream repo
git clone https://github.com/c0x12c/ai-toolkit
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/c0x12c/ai-toolkit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.codex/skills/testing-strategies" ~/.claude/skills/spartan-stratos-spartan-ai-toolkit-testing-strategies && rm -rf "$T"
manifest:
.codex/skills/testing-strategies/SKILL.mdsource content
Testing Strategies — Quick Reference
See
for @MicronautTest configuration, Retrofit client setup, and connection pool tuning.integration-test-setup.mdSee
for complete code patterns including MockK, test data builders, and assertions.examples.md
Integration Tests
Every endpoint needs an integration test. Extend
AbstractControllerTest, use Retrofit clients from module-client, and clean up with truncateAllTables() in @AfterEach.
Key rules:
- Always use Retrofit clients — never raw
HttpRequest - Generate real JWT tokens via
accessToken() - Test full HTTP stack end-to-end, no mocking managers
Test Naming
@Test fun `{action} - {expected outcome}`() = runBlocking { } // Examples: fun `getEmployee - returns employee by id`() = runBlocking { } fun `getEmployee - returns 404 when not found`() = runBlocking { } fun `createEmployee - fails with 401 when not authenticated`() = runBlocking { }
Required Test Coverage Per Endpoint
- Happy path — basic success case
- Not found — 404 for missing resource
- Auth failure — 401 without token
- Soft delete — deleted records not returned
@Test fun `getById - returns 404 when not found`() = runBlocking { val token = accessToken(prepareUser()) assertThrows<HttpException> { runBlocking { client.getById(authorization = token, id = UUID.randomUUID()) } }.also { assertThat(it.code()).isEqualTo(404) } }
Repository Tests
Extend
AbstractRepositoryTest. Every repository needs these tests at minimum:
class DefaultEmployeeRepositoryTest : AbstractRepositoryTest() { private val repository = DefaultEmployeeRepository(database) @BeforeEach fun cleanup() { database.primary.truncateAllTables() } @Test fun `insert - happy path`() { } @Test fun `byId - returns entity when exists`() { } @Test fun `byId - returns null when not exists`() { } @Test fun `byId - returns null when soft deleted`() { } @Test fun `deleteById - soft deletes entity`() { } @Test fun `update - updates selected fields`() { } }
MockK Rules
for creating mocksmockk<T>()
/coEvery
for suspend functions (notcoVerify
/every
)verify
+slot<T>()
to verify what arguments were passedcapture()
fromrunTest { }
— NOTkotlinx.coroutines.test
.runBlocking
handles virtual time and catches coroutine issues.runTest- AAA pattern with comments:
,// Given
,// When// Then - Use
+@Nested
to group related tests@DisplayName
See
for full MockK code examples,examples.mdpatterns, test data builders, and AssertJ assertions.@Nested
Gotchas
Connection pool exhaustion: If parallel tests fail with "cannot acquire connection", bump
maxPoolSize to 20. See integration-test-setup.md for the config snippet.
MockK + coroutines: Always use
coEvery/coVerify for suspend functions. Plain every/verify won't work and gives confusing errors.
Run Tests
./gradlew test # All tests ./gradlew test --tests "com.yourcompany.EmployeeControllerTest" # One class ./gradlew :app:module-repository:test # One module