일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 26 | 27 | 28 |
29 | 30 | 31 |
- git
- ECS
- QueryDSL
- CI
- Kotlin
- Spring Data JPA
- centos7
- bean
- K8s
- mysql
- producer
- API
- kafka
- cd
- Kubernetes
- offsetdatetime
- mirror maker2
- topic생성
- Streams
- Entity
- consumer
- AWS
- JPA
- entity graph
- CodePipeline
- spring kafka
- spring
- PAGING
- transactionaleventlistener
- Spring JPA
- Today
- Total
Yebali
[Spring] 테스트에서 Static 메소드 Mocking하기 본문
회사에서 테스트 코드를 병렬 처리 설정을 넣고 Codebuild를 돌렸더니 아래처럼 에러가 발생했다.
심지어 매번 돌릴 때마다 결과가 달라져 동시성 문제인 것을 예상할 수 있었다.
문제의 테스트 코드
@ExtendWith(MockKExtension::class)
class TestServiceTest {
...
@InjectMockKs
private lateinit var service: TestService
@BeforeEach
fun initTest() {
// for LocalDate.now() Mocking
mockkStatic(LocalDate::class)
...
}
@Test
fun Test_A() {
...
every { LocalDate.now(ZoneOffset.ofHours(9)) } returns LocalDate.of(2022, 7, 11)
...
val expectedDates = listOf(
LocalDate.of(2022, 7, 11),
LocalDate.of(2022, 7, 12),
LocalDate.of(2022, 7, 13),
LocalDate.of(2022, 7, 14)
)
assertThat(availableDates).containsExactlyElementsOf(expectedDates)
}
@Test
fun Test_B() {
...
every { LocalDate.now(ZoneOffset.ofHours(9)) } returns LocalDate.of(2022, 7, 30)
...
// then
val expectedDates = listOf(
LocalDate.of(2022, 7, 30),
LocalDate.of(2022, 7, 31),
)
assertThat(availableDates).containsExactlyElementsOf(expectedDates)
}
@Test
fun Test_C() {
...
every { LocalDate.now(ZoneOffset.ofHours(9)) } returns LocalDate.of(2022, 7, 11)
...
val expectedDates = listOf(
LocalDate.of(2022, 7, 11),
LocalDate.of(2022, 7, 13),
LocalDate.of(2022, 7, 14)
)
assertThat(availableDates).containsExactlyElementsOf(expectedDates)
}
@Test
fun Test_D() {
...
every { LocalDate.now(ZoneOffset.ofHours(9)) } returns LocalDate.of(2022, 7, 11)
...
val expectedDates = listOf(
LocalDate.of(2022, 7, 11),
LocalDate.of(2022, 7, 14)
)
assertThat(availableDates).containsExactlyElementsOf(expectedDates)
}
}
테스트 코드에서 `@BeforeEach`내부에 `mockkStatic()`을 사용하여 `LocaDate`를 mocking 하고,
각 테스트에서는 `LocalDate.now()` 매서드 값을 mocking 하고 있다.
다른 테스트들은 `LocalDate.now()`의 값을 `LocalDate.of(2022, 7, 11)`로 mocking 하지만,
`Test_B`는 `LocalDate.of(2022, 7, 30)`으로 mocking한다. 여기서 문제는 `LocalDate.now()`는 static 메서드로 mocking하면 다른 테스트에 영향을 미친다.
public static LocalDate now(ZoneId zone) {
return now(Clock.system(zone));
}
해결 방법
결론부터 말하면 `LocalDate.now()`를 `@Service` 내부에서 Static 메서드를 사용하지 말고 외부에서 만들도록 하는 것이다.
이를 테면 아래처럼 `LocalDate.now()`를 통해 현재 날짜를 생성하는 기능을 가진 @Bean을 주입받아 사용하는 것이다.
@Service
class TestService(
...
private val localDateGenerator: LocalDateGenerator,
) {
...
private fun List<LocalDate>.excludeDatesPastBoundary(window: Window): List<LocalDate> {
val now = localDateGenerator.now(window.zoneOffset)
...
}
}
위에 코드 처럼 `TestService`에 `LocalDateGenerator`를 주입받아 해당 서비스의 `.now()` 메서드를 사용해서 현재 날짜를 받도록 수정했다.
위의 수정으로 테스트 코드에서 현재 날짜를 mocking하는 부분을 아래처럼 수정했다.
every { localDateGenerator.now(any()) } returns LocalDate.of(2022, 7, 11)
위의 수정으로 동시성이슈는 해결했다.
하지만 테스트 코드를 위해 `@Service`코드를 수정하는 게 맞는 행위일까?
결론부터 말하면 말하면 맞다.
토비의 스프링에서는 테스트할 대상이 의존하고 있는 Bean을 DI를 통해 바꿔치기 하는 것 을 서비스 추상화라고 하며,
테스트를 원할하게 하기 위해서만이라도 서비스 추상화를 하는 것은 바람직하다고 한다.
글 쓰는데 참조한 블로그 글에 자세한 내용이 있다.
참조
'Spring' 카테고리의 다른 글
WebSocket 구현하기 feat.Spring (0) | 2023.02.10 |
---|---|
Spring Collection 조회 성능 비교 (0) | 2022.12.25 |
Spring의 Filter와 Interceptor (0) | 2022.01.23 |
Join의 종류와 QueryDsl (0) | 2021.11.01 |
QueryDSL in Kotlin (0) | 2021.10.31 |