[Error] Mockito를 사용한 단위테스트(form데이터 전송)
SpringBootTest를 이용한 통합테스트 위주의 테스트코드를 작성하다가 단위테스트도 해봐야겠다는 마음으로 컨트롤러는 단위테스트로 작성하기로 했다.
아직 한 번도 해본적이 없어서 인터넷으로 찾아보고 다른 사람들 코드 찾아보면서 작성해보기로 했다.
Junit5를 사용하였고 아래의 코드와 같이 사용하였다.
@ExtendWith(MockitoExtension.class)
class MemberControllerTest {
@InjectMocks
MemberController memberController;
@Mock
MemberService memberService;
MockMvc mockMvc;
private final String baseUrl = "/member";
@BeforeEach
void beforeEach() {
mockMvc = MockMvcBuilders.standaloneSetup(memberController).build();
}
@Test
void 회원가입_성공() throws Exception {
// when, then
mockMvc.perform(multipart(baseUrl)
.file("image", new byte[]{})
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("username", "username")
.param("username_validation", "1")
.param("password", "password123!")
.param("password_check", "password123!")
.param("email", "email@naver.com"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/login"));
verify(memberService).sign(new MemberSaveRequestDto());
}
}
html에서 form형식으로 데이터를 입력받아서 서버로 전송하도록 되어있고 컨트롤로에서는 바로 @ModelAttribute를 이용해 dto로 받아서 처리하는 방식이다. 아래는 해당 테스트의 컨트롤러 부분 코드이다.
@PostMapping
public String sign(@Valid @ModelAttribute("member") MemberSaveRequestDto dto,
BindingResult bindingResult) throws Exception {
if(passwordValidation(dto.getPassword(), dto.getPassword_check())) {
bindingResult.rejectValue("password_check", "passwordValidation", "");
}
if(dto.getUsername_validation() == 0) {
bindingResult.reject("usernameValidation", null);
}
if (bindingResult.hasErrors()) {
return "members/sign";
}
memberService.sign(dto);
return "redirect:/login";
}
테스트코드를 form데이터에서 넘기듯이 파라미터들을 모두 지정해주고 내부에서 회원가입 메소드가 잘 동작하는지 확인하기 위해서 마지막에 verify를 사용해서 점검하는 방식이었다. 그런데 이거 그대로 실행시키면 에러가 발생하는 것이었다..!
해당 에러의 내용인데 아무래도 파라미터를 넘겨서 컨트롤러에서 만드는 dto와 verify를 이용해서 테스트하는 dto의 주소값이 다르기 때문에 나타나는 에러인것 같았다.
상식적으로 당연한 이야기였다. 테스트코드와 컨트롤러에서 각각 만들어내고 있으니 같을 수가 없다.
이게 무슨일이지 싶어서 바로 인터넷을 찾아봤는데 인터넷에서의 코드들은 대부분 @RestController로 사용해서 @RequestBody를 이용해서 데이터를 받아와서 처리하는 방식이었다. 그렇다보니 테스트코드에서 넘기는 값도 dto를 생성하고 json으로 변환해서 넘기고 있는 코드들이 대부분이었다. 그리고 verify에서 메소드의 결과값으로도 앞에서 생성했던 dto를 사용하고 있었다. 하지만 내 코드는 form데이터로 보내고 받기 때문에 적용 할 수 없는 예시들이었다.
원하는 내용을 쉽게 찾지 못하고 몇 일을 걸려서 검색하고 Mockito와 verify의 사용법에 대한 내용도 찾아보던 중 발견한 예시코드가 답을 주었다.
verify에서 리턴되는 값을 실제 dto를 생성해서 주입하는 것이 아니라 any()를 이용해서 dto클래스라는 것만 알려주는 방식으로 사용하는 것이었다.
바로 코드에 적용했을 때 아래와 같이 적용된다.
@Test
void 회원가입_성공() throws Exception {
// when, then
mockMvc.perform(multipart(baseUrl)
.file("image", new byte[]{})
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("username", "username")
.param("username_validation", "1")
.param("password", "password123!")
.param("password_check", "password123!")
.param("email", "email@naver.com"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/login"));
// verify(memberService).sign(new MemberSaveRequestDto()); // 기존 사용방식(에러)
verify(memberService).sign(any(MemberSaveRequestDto.class)); // 새롭게 사용한 방식
}
새롭게 적용한 코드를 주석으로 표시했다.
이렇게 적용하니 테스트코드가 깔끔하게 통과되었다..!!
아무래도 인터넷에서 form데이터를 넘기는 방식의 테스트예시를 찾기가 쉽지않아서 이렇게 글을 남겼다.
'에러모음' 카테고리의 다른 글
JPA필수코스 N+1문제 해결하기 (0) | 2022.06.25 |
---|---|
스프링부트 gradle build 실패 (0) | 2022.06.15 |
[Error] JPQL에서 엔티티조인 후 내부의 엔티티 조회 (0) | 2022.05.03 |
[Error] TransientPropertyValueException에러.. (0) | 2022.05.02 |
[Error] Form태그 안에 버튼이 있다면 버튼의 역할은? (0) | 2022.04.21 |
댓글
이 글 공유하기
다른 글
-
JPA필수코스 N+1문제 해결하기
JPA필수코스 N+1문제 해결하기
2022.06.25 -
스프링부트 gradle build 실패
스프링부트 gradle build 실패
2022.06.15 -
[Error] JPQL에서 엔티티조인 후 내부의 엔티티 조회
[Error] JPQL에서 엔티티조인 후 내부의 엔티티 조회
2022.05.03 -
[Error] TransientPropertyValueException에러..
[Error] TransientPropertyValueException에러..
2022.05.02