Agent skill

kotlin-testing

Kotlin testing with JUnit 5, MockK, Spring test slices, and Testcontainers. Provides mocking patterns, test slice selection, and TDD workflow for Spring Boot + Kotlin. Use when writing tests for Kotlin/Spring code, setting up test infrastructure, or debugging test failures.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/testing/kotlin-testing

SKILL.md

Kotlin Testing

Testing patterns for Spring Boot 4 + Kotlin 2 projects.

When to Use

  • Writing unit tests for services/repositories
  • Setting up integration tests with Testcontainers
  • Mocking dependencies with MockK
  • Selecting appropriate Spring test slices
  • Debugging flaky or failing tests

MCP Workflow

kotlin
# 1. Find existing test patterns
serena.search_for_pattern("@Test|@MockK|@SpringBootTest", relative_path="src/test/")

# 2. Check test utilities
serena.get_symbols_overview(relative_path="src/test/kotlin/.../support/")

# 3. Find test slice usage
jetbrains.search_in_files_by_text("@DataJpaTest|@WebMvcTest", fileMask="*Test.kt")

# 4. Spring testing docs
context7.get-library-docs("/spring/spring-boot", "testing")

Test Slice Selection

Slice Use When Loads
@SpringBootTest Full integration Everything
@WebMvcTest Controller only Web layer
@DataJpaTest Repository only JPA + DB
@MockMvcTest REST endpoints MockMvc
None (unit test) Service/domain logic Nothing

Rule: Start with unit tests (no slice), add slices only when testing integration points.

MockK Patterns

Basic Mocking

kotlin
@ExtendWith(MockKExtension::class)
class PipelineServiceTest {
    @MockK
    private lateinit var repository: PipelineRepositoryJpa

    @InjectMockKs
    private lateinit var service: PipelineService

    @Test
    fun `should create pipeline`() {
        // Arrange
        val command = CreatePipelineCommand(name = "test")
        val entity = PipelineEntity(id = 1, name = "test")
        every { repository.save(any()) } returns entity

        // Act
        val result = service.create(command)

        // Assert
        assertThat(result.name).isEqualTo("test")
        verify(exactly = 1) { repository.save(any()) }
    }
}

Relaxed Mocks (Stubs)

kotlin
@MockK(relaxed = true)  // Returns sensible defaults
private lateinit var logger: Logger

// Or inline
val mockRepo = mockk<Repository>(relaxed = true)

Capturing Arguments

kotlin
val slot = slot<PipelineEntity>()
every { repository.save(capture(slot)) } returns mockEntity

service.create(command)

assertThat(slot.captured.name).isEqualTo("test")

Coroutines (coEvery/coVerify)

kotlin
coEvery { asyncService.process(any()) } returns Result.success()
coVerify { asyncService.process(match { it.id == 1L }) }

Spring Test Patterns

@DataJpaTest (Repository Testing)

kotlin
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
@Testcontainers
class UserRepositoryTest {
    companion object {
        @Container
        val mysql = MySQLContainer("mysql:8.0")
            .withDatabaseName("test")

        @DynamicPropertySource
        @JvmStatic
        fun properties(registry: DynamicPropertyRegistry) {
            registry.add("spring.datasource.url", mysql::getJdbcUrl)
            registry.add("spring.datasource.username", mysql::getUsername)
            registry.add("spring.datasource.password", mysql::getPassword)
        }
    }

    @Autowired
    private lateinit var repository: UserRepositoryJpaSpringData

    @Test
    fun `should find by email`() {
        val user = UserEntity(email = "test@example.com")
        repository.save(user)

        val found = repository.findByEmail("test@example.com")

        assertThat(found?.email).isEqualTo("test@example.com")
    }
}

@WebMvcTest (Controller Testing)

kotlin
@WebMvcTest(PipelineController::class)
class PipelineControllerTest {
    @Autowired
    private lateinit var mockMvc: MockMvc

    @MockkBean
    private lateinit var pipelineService: PipelineService

    @Test
    fun `should return pipeline by id`() {
        every { pipelineService.findById(1L) } returns PipelineDto(id = 1, name = "test")

        mockMvc.get("/api/pipelines/1")
            .andExpect {
                status { isOk() }
                jsonPath("$.name") { value("test") }
            }
    }
}

TDD Workflow for Kotlin

1. RED: Write Failing Test

kotlin
@Test
fun `should reject duplicate pipeline names`() {
    every { repository.existsByName("existing") } returns true

    assertThrows<DuplicateNameException> {
        service.create(CreatePipelineCommand(name = "existing"))
    }
}

2. GREEN: Implement

kotlin
fun create(command: CreatePipelineCommand): PipelineDto {
    if (repository.existsByName(command.name)) {
        throw DuplicateNameException("Pipeline '${command.name}' exists")
    }
    return repository.save(command.toEntity()).toDto()
}

3. REFACTOR: Improve

  • Extract validation to domain
  • Add more edge case tests
  • Verify exception messages

Anti-Patterns

Pattern Problem Solution
@SpringBootTest for unit tests Slow, loads everything Remove annotation
every { } returns without verify Mock unused Add verify or use relaxed
Testing private methods Brittle tests Test through public API
Thread.sleep() in tests Flaky Use awaitility or latch
Mocking data classes Unnecessary Use real instances

Quality Checklist

  • Unit tests use no Spring context (fast)
  • Integration tests use appropriate slice
  • Mocks verified with verify blocks
  • Testcontainers for database tests
  • No Thread.sleep() (use awaitility)
  • Tests follow AAA pattern (Arrange-Act-Assert)
  • Test names describe behavior: should X when Y

Didn't find tool you were looking for?

Be as detailed as possible for better results