반응형

환경: 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);
  }
728x90
반응형

+ Recent posts