프로그래밍 언어/Kotlin

Context 없이 테스트 하기 : mockito 사용 예시

Julie825 2025. 2. 21. 15:37

테스트가 어려웠던 이유 - 종속성 관리

별로 어렵지 않은 경우 바로 아래로 가시면 됩니다.

더보기
더보기

나의 경우 테스트를 선뜻 시작하기 어려웠던게 mocking이 가능하다는걸 몰랐기 때문이었다.

 

아래처럼 HiService라는 복잡한 클래스가 있다고 해보자.

유저 이름을 넣으면 그 이름을 넣은 인사카드 gif 이미지를 반환하는 그런 서비스다.

class HiService(
    // 특징 : DB에서 UserEntity를 불러온다.
    private val userRepository: UserRepository, 
    // 특징 : UtilBean에 method가 백개쯤 있음 곧 분리될 예정
    private val utilBean: UtilBean, 
    // 특징 : API 호출 건수대로 돈받는 유료 서비스
    private val assetRepository: AssetRepository, 
    private val animateRepository: AnimateRepository,
) {
	fun hi(userName: Sring): Response<HiAsset> {
            val region = utilBean.verifyRegion(userName)
            val user: UserEntity? = userRepository(userName)

            if (user == null) { throw CustomExeption("...") }
            val image = assetRepository.getHiImage()
            val animateType = animateRepository.jump()
            return Response.ok(
                utilBean.combinate(image, animateType)
            )
    }
}

 

JUnit 가이드만 좀 읽어보고 @Autowired를 사용해서 이 서비스를 테스트해보려고 하면 해야할게 너무나도 많다.

- userRepository는 DB랑 연결되어야하니 별도 테스트 DB를 만들어야 기존 데이터 영향 없이 테스트를 수행할 수 있다.

- assetRepository에서 신규자원을 가져오면 요금이 징수되서 선뜻 호출하기가 어렵다

- 사실 HiService를 사용하는 상위 모듈의 테스트에서도 다 검증하고있는 작업이다. ex) HiController 등

 

이런저런 생각을 하다보면 시간 없으니 일단 QA랑 통합테스트로 퉁치자는 생각이 든다.

시간이 지나도 단위테스트는 영영 작성되지 않고 기능 하나 바꿀 때마다 야근을 하게된다...

 

이런 고민을 해결해줄 수 있는게 바로 모킹 프레임워크이다.

파이썬이나 자바스크립트에서 수행하는 테스트랑 느낌이 비슷하다.

모듈에서 사용할 메서드의 리턴값만 정의해주면 테스트 준비가 끝난다.

테스트 DB, 외부 API 연결 다 필요 없다. 하위 모듈의 로직 에러로부터 영향을 받을 이유도 없다.

 

모든 레이어에 대해 단위 테스트가 작성되었다는 전제하에, 적은 비용과 짧은 시간으로 기능을 검증할 수 있어서 통합테스트보다 유리하다.

 

Mockito-kotlin 종속성 설정

Spring + Kotlin을 사용하는 경우 아래 종속성에 mockito가 포함되어있다.

// Kotlin
testImplementation("org.springframework.boot:spring-boot-starter-test")
// Groovy
testImplementation "org.springframework.boot:spring-boot-starter-test"

 

mockito-kotlin 라이브러리가 특별히 필요한 경우 아래처럼 받아오면 된다.

주로 null 관련 오류때문에 해당 라이브러리가 필요하게된다.

// Kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
// Groovy
testImplementation "org.mockito.kotlin:mockito-kotlin:5.4.0"

 

Service 테스트 작성 예시

repository 하나를 주입받는 아래와 같은 Service 클래스를 테스트한다고 생각해보자.

class HiService(private val userRepository: UserRepository) { 
    fun welcome(userName: String): String { ... } 
}

 

단위테스트이므로 클래스 단위에 주석을 달아줄 필요는 없다. 먼저 테스트 내용을 함수명으로 표현해주자.

class HiServiceTest {
    @Test
    fun `유저가 없으면 가입 안내 메세지를 반환한다`()
    
    @Test
    fun `유저가 있으면 환영 메세지를 반환한다`()
}

이대로 돌려보면 테스트가 실패하질 않으니 둘다 통과한다고 나온다.

 

이제 HiService에서 사용할 가짜 UserRepository를 만들어주자.

import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`
// ...
val userRepository = mock(UserRepository::class.java)
`when`(userRepository.findUser("김아무개")).thenReturn(False)

// Mock 객체 반환값 비교
println(userRepository.findUser("김아무개")) // False (설정값)
println(userRepository.findUser("박아무개")) // True (기본값)
println(userRepository.getInt()) // 출력 : 0
println(userRepository.getList()) // 출력 : []

`when` & thenReturn 으로 설정하지 않은 메서드에 대해서는 타입만 맞춘 기본값들이 나온다.

따라서 정확히 원하는 값이 있는 경우에만 메서드 리턴값을 정의해주면 된다.

 

이제 이러한 기능을 활용해서 테스트 DB 없이 Service 로직을 검증할 수 있다.

class HiServiceTest {
    private val userRepository = mock(UserRepository::class.java)
    private val hiService = HiService(userRepository)
    // ...
    @Test
    fun `유저가 있으면 환영 메세지를 반환한다`() {
    	val givenUser = "김아무개"
        `when`(userRepository.findUser("김아무개")).thenReturn(False)
        assert(hiService.welcome("김아무개") == "김아무개님 환영합니다.")
    }
}

@BeforeEach 같은 주석으로 예전 모킹 정보를 지워야하지 않나 싶겠지만,

mockito에서 똑똑하게 예전 테스트 코드에서 등록된 동작은 다음 테스트에서 지워버린다. 

 

 

그리고 단순 assert에 더해서, Mock 객체의 어떤 메서드가 호출되었는지 확인할 때 verify 메서드를 사용할 수 있다.

import org.mockito.Mockito.verify
import org.mockito.kotlin.any as kotlinAny

verify(`mock객체`).`확인 원하는 메서드`(`원하는 인풋`) // times(1)이 기본 적용됨

verify(`mock객체`, atMost(3)).`최대 3번 호출`(`원하는 인풋`)
verify(`mock객체`, atLeast(3)).`최소 3번 호출`(`원하는 인풋`)
verify(`mock객체`, times(3)).`정확히 3번 호출`(`원하는 인풋`)
verify(`mock객체`, never()).`호출 안함`(kotlinAny<`인풋타입`>())

 

HiService같은 경우 유저 이름을 여러번 검색할 필요가 전혀 없으니, 유저 검색이 한번만 이루어졌는지 검증하는 테스트 코드를 추가해보자. 아래처럼 검사할 수 있다.

@Test
fun `유저 검색은 한번만 수행한다`() {
    val givenUser = "김아무개"
    `when`(userRepository.findUser("김아무개")).thenReturn(False)
    assert(hiService.welcome("김아무개") == "김아무개님 환영합니다.")

    verify(userRepository, times(1)).findUser("김아무개")
}

 

이처럼 `when`, thenReturn 및 verify만 사용할 줄 알아도 이제 조금의 검색으로 단위테스트를 작성할 수 있게 될 것이다.

 

 

참고자료

- mockito-kotlin 깃헙

Baeldung | Mockito + Kotlin 가이드