스프링 프레임워크/kotlin

Java/Spring -> Kotlin/Spring 변환 - 2

blogger903 2024. 7. 27. 17:51
728x90

환경
IDE: intellij
SpringBootVersion: 2.7.18
Gradle: 8.5
Java: 17

해당 포스팅은
인프런 "코틀린 문법부터 실무까지 (자바 to 코틀린 실무)" 을 따라하면서 Java/Spring 프로젝트를 Kotlin/Spring 프로젝트로
점진적인으로 변환하는 내용을 담고 있습니다

다루는 내용

  • Mockito -> MockK

MockK

@MockK:

  • @Mock 대응
  • 더 엄격한 테스트 환경을 제공합니다. 모든 동작을 명시적으로 정의해야 합니다.
  • 모든 상호작용을 정확히 제어하고 싶을 때 사용합니다.
    @RelaxedMockK:
  • 더 유연한 테스트 환경을 제공합니다. 필요한 동작만 정의할 수 있습니다.
  • 일부 동작만 모킹하고 나머지는 무시하고 싶을 때 유용합니다.

@InjtectMockKs:

  • @InjectMocks 대응

의존성 추가

    val mockkVersion = "1.13.8"
    testImplementation("io.mockk:mockk:${mockkVersion}")

JUnit5 테스트 코드 수정

java -> kotlin

nullable한 객체를 참조할때는 safe call operator를 이용해서 참조하는 객체가 null이 아닐때만 호출하거나 프로퍼티에 접근하도록 합니다

typescript의 optional chainning과 매우 유사한 개념이고, 사용방법도 동일합니다

MockitoExtension -> MockKExtension 적용

테스트 코드를 작성할때 외부 의존성이 필요한경우 lateinit var를 사용합니다

MockKExtension이 테스트 인스턴스 생성할때 주입되는 필드들을 목 객체로 만들어주고 생성할때 주입해줍니다

// AS-IS
BDDMockito.given(somethingRepository.findById(ArgumentMatchers.anyString()))
                .willReturn(Optional.of(something))
// TO-BE
every { somethingRepository.findById(any())
            } returns Optional.of(something)
// AS-IS
        val captor = ArgumentCaptor.forClass(Something::class.java)
// TO-BE
        var slot = slot<Something>()
// AS-IS
        Mockito.verify(somethingRepository, Mockito.times(1))
            ?.save(captor.capture())
        val savedSomething = captor.value
// TO-BE
        verify(exactly = 1) { somethingRepository.save(capture(slot)) }
        val savedSomething = slot.captured

MockK는 호출 대상에 대한 스텁 정의를 하지 않으면 오류를 발생합니다

io.mockk.MockKException: no answer found for: ~~

@RelxedMockK를 통해 모든 메서드의 스텁을 하지 않도록 할 수 있습니다

참고: https://javacan.tistory.com/entry/kotlin-mock-framework-mockk-intro

java.lang.ClassCastException: class java.lang.Object cannot be cast to class 

스텁을 해줘야 테스트가 가능한경우에는 다음과 같이 스텁잉 해줍니다

        every { somethingRepository.save(any())} returns SomeThing(
            id = null,
            name = "Ismael Salas",
            age = 8727,
            createdAt = null,
            updatedAt = null
        )

capture

MockK에서 capture를 사용하기 위해선 slot을 생성합니다

var slot = slot<Prince>()

Capture 사용합니다
이 단계에서 save 메서드에 전달된 인자가 slot에 캡처됩니다.

verify { somethingRepository.save(capture(slot)) }

캡처된 값 접근

val savedPrince = slot.captured

여러 객체를 캡처할때

// 가정: Something 클래스가 있고, 이를 생성하는 SomethingService가 있습니다.
class Something(
    val id: String,
    val type: String,
    val value: Int
)

class CreateSomething {
    data class Request(val type: String, val value: Int)
}

@Test
fun createMultipleSomethingsTest() {
    // Given
    val request1 = CreateSomething.Request("typeA", 10)
    val request2 = CreateSomething.Request("typeB", 20)

    // 여러 객체를 캡처하기 위한 리스트 생성
    val capturedSomethings = mutableListOf<Something>()

    // 모의 객체 설정
    every { somethingRepository.save(any()) } returns mockk()

    // When
    somethingService.createSomething(request1)
    somethingService.createSomething(request2)

    // Then
    // save 메서드가 정확히 2번 호출되었는지 확인하고 각 호출의 인자를 캡처
    verify(exactly = 2) { somethingRepository.save(capture(capturedSomethings)) }

    // 첫 번째 캡처된 Something 객체 검증
    assertThat(capturedSomethings[0]).apply {
        prop(Something::type).isEqualTo("typeA")
        prop(Something::value).isEqualTo(10)
    }

    // 두 번째 캡처된 Something 객체 검증
    assertThat(capturedSomethings[1]).apply {
        prop(Something::type).isEqualTo("typeB")
        prop(Something::value).isEqualTo(20)
    }
}