코딩블로그
[Spring]Junit5 Controller 단위 테스트 @AutoConfigureMockMvc, @MockBean, @MockMvc 트러블슈팅 본문
[Spring]Junit5 Controller 단위 테스트 @AutoConfigureMockMvc, @MockBean, @MockMvc 트러블슈팅
_hanbxx_ 2024. 4. 6. 15:13상영회 생성하는 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를 받아올 수 있었다
'PopcornMate' 카테고리의 다른 글
[리팩토링] 멀티모듈에 Spotless 적용하면서 테스트 코드 컨벤션 유지하기 (0) | 2024.04.16 |
---|---|
[Spring] 프로젝트 협업 툴 디스코드 연동해서 에러 메세지 받아보기 (Feat. feign, logback.xml) (1) | 2024.04.03 |
멀티 모듈 환경에서 내가 만든 api 테스트해보기 Feat. MockMvc, profileResolver, WithCustomUser (0) | 2024.03.24 |
[Spring] FCM 구축하기 & FCM과 @Scheduled을 이용하여 특정 시간대에 알림 보내기 (4) | 2024.03.11 |
[Spring] Amazons S3 Presigned Url 구현하기 (0) | 2024.03.11 |