askill
kotlin-testing

kotlin-testingSafety 90Repository

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.

0 stars
1.2k downloads
Updated 2/5/2026

Package Files

Loading files...
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

# 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

SliceUse WhenLoads
@SpringBootTestFull integrationEverything
@WebMvcTestController onlyWeb layer
@DataJpaTestRepository onlyJPA + DB
@MockMvcTestREST endpointsMockMvc
None (unit test)Service/domain logicNothing

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

MockK Patterns

Basic Mocking

@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)

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

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

Capturing Arguments

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

service.create(command)

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

Coroutines (coEvery/coVerify)

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

Spring Test Patterns

@DataJpaTest (Repository Testing)

@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)

@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

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

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

2. GREEN: Implement

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

PatternProblemSolution
@SpringBootTest for unit testsSlow, loads everythingRemove annotation
every { } returns without verifyMock unusedAdd verify or use relaxed
Testing private methodsBrittle testsTest through public API
Thread.sleep() in testsFlakyUse awaitility or latch
Mocking data classesUnnecessaryUse 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

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

95/100Analyzed 2/12/2026

An exceptional technical reference for Kotlin and Spring Boot testing, providing comprehensive patterns for MockK, Testcontainers, and Spring slices with clear actionable examples.

90
100
90
98
95

Metadata

Licenseunknown
Version-
Updated2/5/2026
Publishermajiayu000

Tags

apici-cddatabasegithub-actionstesting