👩🏻💻 Programming/SpringBoot
@PostConsturct와 테스트 코드
한국의 메타몽
2024. 11. 23. 19:51
@Component
@RequiredArgsConstructor
public class ChangeMoneyUseCaseFactory {
private final List<ChangeMoneyUseCase> changeMoneyUseCases;
private EnumMap<LevelType, ChangeMoneyUseCase> changeMoneyUseCaseMap;
@PostConstruct
private void init() {
changeMoneyUseCaseMap = changeMoneyUseCases.stream()
.collect(Collectors.toMap(ChangeMoneyUseCase::getLevelType, Function.identity(), (existing, replacement) -> existing,
() -> new EnumMap<>(LevelType.class)));
}
public ChangeMoneyUseCase changeMoneyUseCaseOf(LevelType levelType) {
return changeMoneyUseCaseMap.getOrDefault(levelType, null);
}
}
위와 같은 factory 클래스가 있었다.
보다시피 해당 factory 클래스의 init()
메서드는 @PostConstruct
을 통해 초기화가 이루어지고 있다.
그러다보니 테스트 코드 작성 중 아래의 이슈들과 맞닥뜨렸다.
@PostConstruct
은 Spring Context에서만 동작- 따라서
Mockito
를 사용하여@InjectMocks
로 의존성을 주입하고 순수 자바 객체로 테스트를 진행할 때@PostConstruct
으로 선언된 로직은 자동으로 실행되지 못함
그래서 일반적인 @InjectMocks
<-> @Mock
을 활용한 테스트 코드를 작성했을때 changeMoneyUseCaseOf
메서드를 호출해도 changeMoneyUseCaseMap
자체가 제대로 초기화되지 못한 현상이 발생했다.
그래도 테스트 코드는 작성해야 성에 찰 것 같다.
방법은 크게 2가지가 떠올랐다.
init()
초기화 메서드의 접근제어자를public
으로 변경하여 테스트 코드에서init()
을 호출하기Reflection
으로init()
메서드에 접근하기
아무라봐도 테스트 코드를 위해 비즈니스 로직의 접근제어자를 private
-> public
으로 바꾸는건 아닌 것 같다.
한편으로는 private
으로 선언된 메서드로 어떻게든 테스트 코드를 작성하는게 맞는지 의문이 들기도 하지만,
그래도 테스트 코드는 작성해야 마음 놓고 PR을 올릴 수 있을것 같으니, 우선 비즈니스 로직을 변경하지 않는 2안으로 진행하기로 결정했다.
@BeforeEach
void setUp() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// ChangeMoneyUseCase를 상속받는 각 service 레이어로 값 초기화
given(vipChangeMoneyService.getLevelType()).willReturn(LevelType.VIP);
// .. 다른 멤버십 서비스도 동일하게 작성
List<ChangeMoneyUseCase> useCases = List.of(vipChangeMoneyService, ...);
target = new ChangeMoneyUseCaseFactory(useCases);
// Reflection으로 private 메서드 접근
Method init = ChangeMoneyUseCaseFactory.class.getDeclaredMethod("init");
init.setAccessible(true);
init.invoke(target);
}