Kotlin testing
Skill Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack/plugins/devtools-pack/skills/kotlin-testing
When to activate: Kotlin testing, JUnit 5, Kotest, MockK, Testcontainers, coroutine testing, Spring test slices, runTestFrom its SKILL.md
npx -y skills add Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack --skill kotlin-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.9 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Kotlin Testing Patterns
JUnit 5 + MockK
@ExtendWith(MockKExtension::class)
class UserServiceTest {
@MockK lateinit var userRepository: UserRepository
@MockK lateinit var emailService: EmailService
@InjectMockKs lateinit var userService: UserService
@Test
fun `findById returns user when exists`() {
val user = User(id = 1L, name = "Alice", email = "[email protected]")
every { userRepository.findById(1L) } returns Optional.of(user)
val result = userService.findById(1L)
assertThat(result).isEqualTo(user.toDto())
verify(exactly = 1) { userRepository.findById(1L) }
}
@Test
fun `findById throws when not found`() {
every { userRepository.findById(any()) } returns Optional.empty()
assertThrows<ResourceNotFoundException> { userService.findById(99L) }
}
@Test
fun `create sends welcome email`() {
val request = CreateUserRequest("Bob", "[email protected]")
every { userRepository.existsByEmail(any()) } returns false
every { userRepository.save(any()) } answers { firstArg() }
every { emailService.sendWelcome(any()) } just Runs
userService.create(request)
verify { emailService.sendWelcome(match { it.email == "[email protected]" }) }
}
}
Kotest
class UserServiceSpec : BehaviorSpec({
val repository = mockk<UserRepository>()
val service = UserService(repository)
Given("a user exists") {
val user = User(1L, "Alice", "[email protected]")
every { repository.findById(1L) } returns Optional.of(user)
When("findById is called") {
val result = service.findById(1L)
Then("returns the user DTO") {
result shouldBe user.toDto()
}
}
}
})
// Data-driven tests
class EmailValidationSpec : FunSpec({
forAll(
row("[email protected]", true),
row("invalid-email", false),
row("", false),
row("missing@domain", false),
) { email, expected ->
test("$email is ${if (expected) "valid" else "invalid"}") {
email.isValidEmail() shouldBe expected
}
}
})
Coroutine Testing
class FlowViewModelTest {
@get:Rule val mainDispatcherRule = MainDispatcherRule()
@Test
fun `search results update after debounce`() = runTest {
val fakeRepo = FakeSearchRepository()
val vm = SearchViewModel(fakeRepo)
vm.onQueryChange("kotlin")
advanceTimeBy(400) // past 300ms debounce
assertEquals(fakeRepo.expectedResults, vm.results.value)
}
@Test
fun `stateflow collects values`() = runTest {
val flow = MutableStateFlow(0)
val collected = mutableListOf<Int>()
val job = launch { flow.collect { collected.add(it) } }
flow.value = 1
flow.value = 2
job.cancel()
assertThat(collected).containsExactly(0, 1, 2)
}
}
Testcontainers
@SpringBootTest
@Testcontainers
class UserRepositoryIntegrationTest {
companion object {
@Container @JvmStatic
val postgres = PostgreSQLContainer("postgres:16-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test")
@DynamicPropertySource @JvmStatic
fun configureProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl)
registry.add("spring.datasource.username", postgres::getUsername)
registry.add("spring.datasource.password", postgres::getPassword)
}
}
@Autowired lateinit var repository: UserRepository
@Test
@Transactional
fun `save and find user`() {
val user = User(name = "Test", email = "[email protected]")
val saved = repository.save(user)
val found = repository.findById(saved.id!!)
assertThat(found).isPresent.get().extracting("email").isEqualTo("[email protected]")
}
}
Spring Test Slices
// Only loads web layer
@WebMvcTest(UserController::class)
class UserControllerTest {
@Autowired lateinit var mvc: MockMvc
@MockBean lateinit var userService: UserService
@Test
fun `GET user returns 200`() {
every { userService.findById(1L) } returns UserResponse(1L, "Alice")
mvc.get("/api/v1/users/1")
.andExpect { status { isOk() } }
.andExpect { jsonPath("$.name") { value("Alice") } }
}
}
// Only loads data layer
@DataJpaTest
class OrderRepositoryTest {
@Autowired lateinit var repository: OrderRepository
@Autowired lateinit var entityManager: TestEntityManager
// ...
}
AssertJ Custom Assertions
fun assertThatUser(user: User) = UserAssert(user)
class UserAssert(actual: User) : AbstractAssert<UserAssert, User>(actual, UserAssert::class.java) {
fun isActive() = apply { check("status") { actual.status == UserStatus.ACTIVE } }
fun hasEmail(email: String) = apply { check("email") { actual.email == email } }
}
// Usage
assertThatUser(user).isActive().hasEmail("[email protected]")
Key Rules
- Use
mockkover Mockito — idiomatic Kotlin, noany()casting issues, supportsobjectmocking - Use
runTestfor coroutine tests — it usesTestCoroutineSchedulerand auto-advances virtual time @Testcontainerswith@Containerstatic field — reuse containers across tests with companion object- Spring slices (
@WebMvcTest,@DataJpaTest) are faster than full@SpringBootTest— use them for focused tests - Prefer
every { } returnsoverwhenever { } thenReturn— cleaner in Kotlin
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most test skills give in ~1.3k tokens
Counted across 964 of the 1,571 authors here whose files we hold, read 2026-08-07
- Close the browser when donein 55 of 964, across 12 files
- Wait for network idle statein 51 of 964, across 6 files
- Launch Chromium in headless modein 49 of 964, across 6 files
- Use descriptive selectors for elementsin 49 of 964, across 6 files
- Run provided scripts with help flag firstin 49 of 964, across 6 files
- Add appropriate explicit waitsin 48 of 964, across 5 files
- Use bundled scripts as black boxesin 46 of 964, across 3 files
- Do not read script source codein 46 of 964, across 3 files
- Use sync playwright for scriptsin 46 of 964, across 3 files
- Inspect dom before executing actionsin 46 of 964, across 3 files
- Run the full test suitein 37 of 964
- Write the failing test firstin 29 of 964, across 23 files
Said here and by no other author read
- use MockK over Mockito
- use runTest for coroutine tests
- use Spring test slices for focused tests
- prefer every returns over whenever thenReturn
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.