환경: springboot3, java17
오늘의 시행착오..
아래와 같은 컨트롤러 코드가 있다.
@PostConstructor로 map을 채워야 한다.
public class EventRewardController {
private final List<EventMoneyUseCase> eventMoneyUseCases;
private EnumMap<GameType, EventMoneyUseCase> eventMoneyMap;
@PostConstruct
public void setEventMoneyMap() {
eventMoneyMap = eventMoneyUseCases.stream().filter(useCase -> useCase.getGameType() != null) // null 키 방지
.collect(Collectors.toMap(EventMoneyUseCase::getGameType, Function.identity(), (existing, replacement) -> existing,
() -> new EnumMap<>(GameType.class)));
}
@WebMvcTest는 Spring Boot에서 Web Layer 테스트를 위한 어노테이션이다.
주로 컨트롤러와 관련된 테스트를 작성할 때 사용된다. 이 어노테이션은 Spring MVC를 사용하여 HTTP 요청과 응답을 테스트할 수 있도록 해준다. @WebMvcTest는 웹 계층의 컴포넌트들만 로드하고, 데이터베이스나 서비스 계층과 같은 다른 빈들을 로드하지 않기 때문에 경량화된 테스트를 제공한다. @WebMvcTest는 기본적으로 컨트롤러와 관련된 빈들만 로드. 서비스, 리포지토리, 컴포넌트 등 다른 빈들은 자동으로 로드되지 않기 때문에 @MockBean을 사용하여 필요한 의존성을 모킹해야 한다.
그리하여 아래와 같이 컨트롤러 테스트를 작성했는데(클래스 부분 생략)
여러 방법으로 해도 다 실패하였다..
단순하게 생각할 수 있는 eventMoneyMap.get을 stubbing 해봤다가
list 자체를 stubbing 하고 싶다고 생각하면서도 이게 controller에 주입이 안 되는 것 같아
하면서도 말도 안 된다고 생각하였지만 injectMock을.. 사용해보기도 해 보고(절박하면 우선 해본다..ㅋㅋ)
PostConstruct 대신 직접 함수 호출을 해보기도 한다.
그러다가 webMvcTest ->@Autowired로 자동 빈 주입이잖아? 그럼 injectMock 말고 바꿔봐!
이래서 성공에 가까워졌다.
아래는 성공 코드
@Autowired로 컨트롤러 빈 가져와서 setter 함수로 mockBean을 주입한다.
private MockMvc mockMvc;
@MockBean
private EventMoneyUseCase mockUseCase;
@MockBean
private List<EventMoneyUseCase> eventMoneyUseCases;
@Autowired
private EventRewardController target;
@BeforeEach
protected void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) throws Exception {
this.mockMvc = mockMvc(webApplicationContext, restDocumentation);
}
@Test
@DisplayName("이벤트 머니 지급")
void giveEventReward() throws Exception {
// given
TestMsaMemberResolver.setUp(MEMBER_NO, MEMBER_ID);
given(mockUseCase.getGameType()).willReturn(GameType.S);
given(mockUseCase.giveEventMoney(any(EventMoneyRequest.class))).willReturn(MEMBER_NO);
eventMoneyUseCases = List.of(mockUseCase);
target.setEventMoneyMap(); // 이 부분은 `@PostConstruct`가 아닌 직접 호출
//when
...
}
}
성공하고 나면 별거 아닌데,, 이걸로 오늘 3시간은 날린 것 같다..ㅋㅋ 피곤하다 월요일
mockito를 사용할 경우
@Component
@RequiredArgsConstructor
public class ChangeMoneyUseCaseFactory {
private final List<ChangeMoneyUseCase> changeMoneyUseCases;
private EnumMap<GameType, ChangeMoneyUseCase> changeMoneyUseCaseMap;
@PostConstruct
private void init() {
changeMoneyUseCaseMap = changeMoneyUseCases.stream()
.collect(Collectors.toMap(ChangeMoneyUseCase::getGameType, Function.identity(), (existing, replacement) -> existing,
() -> new EnumMap<>(GameType.class)));
}
@ExtendWith(MockitoExtension.class)
class ChangeMoneyUseCaseFactoryTest {
@InjectMocks
private ChangeMoneyUseCaseFactory target;
@Mock
private SinChangeMoneyService sinChangeMoneyService; //list에 담길
@BeforeEach
void setUp() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
given(sinChangeMoneyService.getGameType()).willReturn(GameType.SIN);
List<ChangeMoneyUseCase> useCases = List.of(sinChangeMoneyService);
target = new ChangeMoneyUseCaseFactory(useCases); //주입
//private postconstruct reflection 주입
Method init = ChangeMoneyUseCaseFactory.class.getDeclaredMethod("init");
init.setAccessible(true);
init.invoke(target);
}
만약 public postconstruct 라면
@BeforeEach
void setUp() {
goodsWithdrawMap = new HashMap<>();
goodsWithdrawMap.put(ItemType.MONEY_CHIP, chipWithdrawer);
...
goodsWithdrawMap.put(null, defaultWithdrawer);
//public 함수 주입
ReflectionTestUtils.setField(target, "goodsWithdrawMap", goodsWithdrawMap);
}
'개발 > spring' 카테고리의 다른 글
[jpa] EntityManager를 타지 않는 작업 (1) | 2024.11.20 |
---|---|
[jpa] 프로시져와 트랜젝션 (0) | 2024.11.19 |
[test] @RestClientTest vs mockMvc (0) | 2024.11.16 |
스프링 빈 주입 시 우선 순위 (0) | 2024.11.12 |
[springboot] Test code: profile and configuration (0) | 2024.09.12 |