Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
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
Archives
Today
Total
관리 메뉴

코딩블로그

[Spring]Junit5 Controller 단위 테스트 @AutoConfigureMockMvc, @MockBean, @MockMvc 트러블슈팅 본문

PopcornMate

[Spring]Junit5 Controller 단위 테스트 @AutoConfigureMockMvc, @MockBean, @MockMvc 트러블슈팅

_hanbxx_ 2024. 4. 6. 15:13
728x90

상영회 생성하는 API가 잘 호출이 되는지 확인하기 위한 테스트를 구성해보아서

간단하게 200뜨는 것 보고 

처음에는 "아~ 잘되네~~"하고 넘어갔는데 다른 사람들 코드를 참고해보니까 실제로 API를 호출하고 Body안에 원하는 Response까지 받아와야 되는 것을 깨닫게 되었다. 그래서 Body안에 원하는 값을 받아오기 위해서 트러블슈팅을 해보았다.

 

나는 Controller를 테스트하기 위해서

@AutoConfigureMockMvc

어노테이션을 사용한다

웹 환경에서는 반드시 서블릿 컨테이너가 구동되고 DispatchServlet 객체가 메모리에 올라가야 한다.

그래서 저 @AutoConfigureMockMvc 어노테이션을 사용해서 서블릿 컨테이너를 Mocking하여 실제로 테스트용 모형 컨테이너를 사용할 수 있기 때문에 Controller를 테스트할 수 있는 환경을 구성하였다. 

단순히 Controller 호출을 하는 경우를 테스트 하는 것이면 @WebMvcTest를 사용하는 것을 추천한다.

(+ @WebMvcTest를 사용하려면 @SpringBootTest랑 같이 사용하면 안된다. 각자 서로의 MockMvc를 모킹하기 때문에 충돌이 발생하여 실행이 안될 것이다)

@ApiIntegrateSpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ScreeningControllerTest {
    @LocalServerPort
    protected int port;

    @Autowired
    MockMvc mvc;
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
    @Autowired
    private ObjectMapper objectMapper;
    @MockBean
    private ScreeningUploadUseCase screeningUploadUseCase;

    @DisplayName("상영회 만들기 시나리오")
    @Test
    @WithCustomMockUser
    void createScreening() throws Exception {
        PostScreeningRequest request = PostScreeningRequest.builder()
                .posterImgUrl("https://jgjfhdjghsdkjhgkjd")
                .screeningTitle("홍익대학교 졸업전시회")
                .hostName(null)
                .category(Category.GRADUATE)
                .screeningStartDate(LocalDateTime.parse("2024-03-20T07:52:06", formatter))
                .screeningEndDate(LocalDateTime.parse("2024-03-20T07:52:06", formatter))
                .screeningStartTime(LocalDateTime.parse("2024-03-20T07:52:06", formatter))
                .location("홍익대학교")
                .information("졸업 작품보러오세요")
                .formUrl("https://sdhgfhsdjkfsjjgsh.com")
                .hostPhoneNumber(null)
                .hostEmail(null)
                .hasAgreed(true)
                .build();

        Screening response = Screening.of(
                "홍익대학교 졸업전시회",
                "https://jgjfhdjghsdkjhgkjd",
                null,
                "홍익대학교",
                "https://sdhgfhsdjkfsjjgsh.com",
                "졸업 작품보러오세요",
                LocalDateTime.parse("2024-04-05T08:13:36.663"),
                LocalDateTime.parse("2024-04-05T08:13:36.663"),
                LocalDateTime.parse("2024-04-05T08:13:36.663"),
                true,
                Category.GRADUATE
        );
        given(screeningUploadUseCase.execute(request)).willReturn(response);

        mvc.perform(MockMvcRequestBuilders.post("/screening/upload-screening")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                //Then
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(jsonPath("$.data.hasAgreed").isBoolean());

    }
}

 

이렇게 위 코드 처럼

    @MockBean
    private ScreeningUploadUseCase screeningUploadUseCase;
    
    given(screeningUploadUseCase.execute(request)).willReturn(response);

이걸 추가를 하면 ScreeningUploadUseCase안에 있는 메소드들을 아예 호출하고 있지 않는다.

 

그래서 저 @MockBean을 사용하지 않고 given 코드도 밑에 코드 처럼 빼보았는데, 

@ApiIntegrateSpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ScreeningControllerTest {
    @LocalServerPort
    protected int port;

    @Autowired
    MockMvc mvc;
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
    @Autowired
    private ObjectMapper objectMapper;

    @DisplayName("상영회 만들기 시나리오")
    @Test
    @WithCustomMockUser
    void createScreening() throws Exception {
        PostScreeningRequest request = PostScreeningRequest.builder()
                .posterImgUrl("https://jgjfhdjghsdkjhgkjd")
                .screeningTitle("홍익대학교 졸업전시회")
                .hostName(null)
                .category(Category.GRADUATE)
                .screeningStartDate(LocalDateTime.parse("2024-03-20T07:52:06", formatter))
                .screeningEndDate(LocalDateTime.parse("2024-03-20T07:52:06", formatter))
                .screeningStartTime(LocalDateTime.parse("2024-03-20T07:52:06", formatter))
                .location("홍익대학교")
                .information("졸업 작품보러오세요")
                .formUrl("https://sdhgfhsdjkfsjjgsh.com")
                .hostPhoneNumber(null)
                .hostEmail(null)
                .hasAgreed(true)
                .build();

        Screening response = Screening.of(
                "홍익대학교 졸업전시회",
                "https://jgjfhdjghsdkjhgkjd",
                null,
                "홍익대학교",
                "https://sdhgfhsdjkfsjjgsh.com",
                "졸업 작품보러오세요",
                LocalDateTime.parse("2024-04-05T08:13:36.663"),
                LocalDateTime.parse("2024-04-05T08:13:36.663"),
                LocalDateTime.parse("2024-04-05T08:13:36.663"),
                true,
                Category.GRADUATE
        );

        mvc.perform(MockMvcRequestBuilders.post("/screening/upload-screening")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                //Then
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(jsonPath("$.data.hasAgreed").isBoolean());

    }
}

다른 클래스에 있는 메소드들도 호출을 하면서 제대로 동작을 한다!

 

공식문서와 구글링을 통해 알게된 원인은 바로 @AutoConfigureMockMvc를 사용하면 @Service나 @Repository가 붙은 객체들을 모두 메모리에 올려 사용할 수 있게 되는데 무엇보다 @MockBean 어노테이션을 사용하게 되면, Spring Application Context에 들어있는 Bean을 Mock으로 만든 객체(가짜 객체)로 교체한다. 모든 @Test마다 자동으로 리셋된다! 그래서 나는 처음에  @AutoConfiigureMockMvc로 메모리에 필요한 객체들을 다 올렸는데 @MockBean을 또 선언해서 필요한 것 까지 다 가짜 객체로 교체를 해서 제대로 수행이 안됐던 것 이었다.

이런 이유로, ScreeningUploadUseCase에서 ScreeningAdapter을 호출하는 메서드를 제대로 수행하지 못했던 것이다.

 

그래서 결국 MockBean과 given을 없애서

Body안에 제대로 된 response를 받아올 수 있었다