=================
== The Archive ==
=================

MockK 에서 Repository Entity 저장 테스트하기: Slot Capture 완벽 가이드

|

Introduction

기본 설정

1
2
3
4
5
dependencies {
    testImplementation("io.mockk:mockk:1.13.16")
    testImplementation("org.springframework.boot:spring-boot-starter-data-jpa:3.4.2")
    testImplementation("org.assertj:assertj-core:3.27.3")
}

테스트할 도메인 클래스 준비

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
data class UserEntity(
    val id: Long? = null,
    val name: String,
    val age: Int,
    val updatedAt: LocalDateTime? = null
)

interface UserRepository : JpaRepository<UserEntity, Long>

class UserService(
    private val userRepository: UserRepository
) {
    fun saveUser(user: UserEntity): UserEntity {
        return userRepository.save(user)
    }
}

기본적인 Entity 저장 테스트

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Test
fun `사용자 저장 테스트`() {
    // given
    val entitySlot = slot<UserEntity>()
    val repository = mockk<UserRepository>()
    every { repository.save(capture(entitySlot)) } answers { entitySlot.captured }

    val userService = UserService(repository)
    val user = UserEntity(name = "홍길동", age = 25)

    // when
    userService.saveUser(user)

    // then
    verify { repository.save(any()) }
    assertThat(entitySlot.captured).run {
        assertThat(actual().name).isEqualTo("홍길동")
        assertThat(actual().age).isEqualTo(25)
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Test
fun `사용자 저장 테스트`() {
    // given
    val entitySlot = slot<UserEntity>()
    val repository = mockk<UserRepository>()
    every { repository.save(capture(entitySlot)) } answers { entitySlot.captured }

    val userService = UserService(repository)
    val user = UserEntity(name = "홍길동", age = 25)

    // when
    val saved = userService.saveUser(user)

    // then
    verify { repository.save(any()) }
    assertThat(saved).run {
        assertThat(actual().name).isEqualTo("홍길동")
        assertThat(actual().age).isEqualTo(25)
    }
}

다중 Entity 저장 테스트

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
fun `여러 사용자 저장 테스트`() {
    // given
    val capturedEntities = mutableListOf<UserEntity>()
    val repository = mockk<UserRepository>()
    every { repository.save(capture(capturedEntities)) } answers { lastArg() }

    val userService = UserService(repository)

    // when
    val user1 = UserEntity(name = "홍길동", age = 25)
    val user2 = UserEntity(name = "김철수", age = 30)

    userService.saveUser(user1)
    userService.saveUser(user2)

    // then
    verify(exactly = 2) { repository.save(any()) }
    assertThat(capturedEntities).hasSize(2)
    assertThat(capturedEntities[0].name).isEqualTo("홍길동")
    assertThat(capturedEntities[1].name).isEqualTo("김철수")
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
fun `여러 사용자 저장 테스트`() {
    // given
    val capturedEntities = mutableListOf<UserEntity>()
    val repository = mockk<UserRepository>()
    every { repository.save(capture(capturedEntities)) } answers { lastArg() }

    val userService = UserService(repository)

    // when
    val user1 = UserEntity(name = "홍길동", age = 25)
    val user2 = UserEntity(name = "김철수", age = 30)

    val saved1 = userService.saveUser(user1)
    val saved2 = userService.saveUser(user2)

    // then
    verify(exactly = 2) { repository.save(any()) }
    assertThat(capturedEntities).hasSize(2)
    assertThat(capturedEntities[0].name).isEqualTo("홍길동")
    assertThat(capturedEntities[1].name).isEqualTo("김철수")

    assertThat(saved1.name).isEqualTo("홍길동")
    assertThat(saved2.name).isEqualTo("김철수")
}

Entity 수정이 포함된 저장 테스트

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
fun `저장 시 업데이트 시간이 설정되는지 테스트`() {
    // given
    val entitySlot = slot<UserEntity>()
    val now = LocalDateTime.now()

    val repository = mockk<UserRepository>()
    every { repository.save(capture(entitySlot)) } answers {
        entitySlot.captured.copy(
            updatedAt = now
        )
    }

    val userService = UserService(repository)

    // when
    val user = UserEntity(name = "홍길동", age = 25)
    val savedUser = userService.saveUser(user)

    // then
    verify { repository.save(any()) }
    assertThat(savedUser.updatedAt).isEqualTo(now)
}

예외 상황 테스트

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Test
fun `저장 실패 시 예외 발생 테스트`() {
    // given
    val entitySlot = slot<UserEntity>()
    val repository = mockk<UserRepository>()
    every { repository.save(capture(entitySlot)) } throws
        RuntimeException("데이터베이스 저장 실패")

    val userService = UserService(repository)
    val user = UserEntity(name = "홍길동", age = 25)

    // when & then
    assertThrows<RuntimeException> {
        userService.saveUser(user)
    }

    verify { repository.save(any()) }
}

테스트 작성 시 주의사항

  1. Slot 재사용: slot 은 한 번만 캡처할 수 있으므로, 여러 호출을 캡처하려면 mutableListOf 를 사용해야 한다.
  2. 테스트 격리: 각 테스트마다 새로운 slot 을 생성하여 테스트 간 격리를 보장
  3. 검증 순서: verify 는 일반적으로 테스트의 마지막에 수행한다.
  4. 명확한 given-when-then: 테스트 구조를 명확히 하여 가독성을 높인다.

결론

Categories:

Tags: