이제 유닛테스트를 이용하여 회원가입 성공 기능을 검증해보자!
Mockito는 테스트 더블(실제 구현을 대체하는 객체,Mock, Stub, Spy 등)을 생성하고 동작을 검증하는 데 사용되는 목적 프레임워크이다.
@ExtendWith(MockitoExtension.class)를 테스트 클래스에 적용하면 Mockito의 기능을 사용할 수 있다.
@InjectMocks는 테스트 대상 객체를 주입받기 위해 사용했다. 여기서는 MemberController가 해당 대상이다. Mockito는 MemberController의 인스턴스를 생성하고, 필요한 의존성 해결을 위해
@Mock 어노테이션이 지정된 객체에 의존성을 주입한다.
이렇게 생성된 Mock 객체들은 테스트에서 사용될 때 원하는 동작을 강제로 수행할 수 있다.
@ExtendWith(MockitoExtension.class)
class MemberControllerTest {
@InjectMocks
private MemberController memberController;
@Mock
private MemberService memberService;
@Mock
private Rq rq;
private MockMvc mockMvc;
먼저 테스트 메서드 작성 전
@BeforeEach를 통해 테스트 메서드가 실행되기전에 초기화 작업을 정의한다. 여기서는 MockMvc 객체를 초기화해준다.
@BeforeEach
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(memberController).build();
}
standaloneSetup() 메서드를 통해 특정 컨트롤러를 기반으로 독립적인 환경에서 테스트를 실행할 수 있다.
이제, 회원가입 성공 테스트 메서드를 작성해보자! 대략적인 순서를 살펴보면,
1. 테스트에 사용할 테스트값을 넣은 객체를 생성한다.
2. doReturn().when()을 통해 Mock 객체의 동작을 설정한다.
3. ResultAction을 사용해 post(/member/join) 경로로 들어올 파라미터를 설정해준다. http 요청 메시지를 설정해주는 것!
4. resultAction을 이용해 예상된 http 응답 결과를 검증한다.
@Test
@DisplayName("일반 회원가입 성공")
void joinSuccess() throws Exception {
// Given
JoinRequestDto joinRequestDto = new JoinRequestDto();
joinRequestDto.setUsername("test");
joinRequestDto.setPassword("1234");
joinRequestDto.setConfirmPassword("1234");
joinRequestDto.setNickname("tester");
joinRequestDto.setEmail("test@naver.com");
Member member = Member.builder()
.username("test")
.password("1234")
.nickname("tester")
.email("test@naver.com")
.userRole("free")
.build();
RsData<Member> joinRsData = new RsData<>("S-1", "%s님의 회원가입이 완료되었습니다.".formatted(joinRequestDto.getNickname()), member);
doReturn(joinRsData).when(memberService).join(any(JoinRequestDto.class));
doReturn("redirect:/member/login").when(rq).redirectWithMsg(any(String.class), any(RsData.class));
// When
ResultActions resultActions = mockMvc
.perform(post("/member/join")
.contentType(MediaType.MULTIPART_FORM_DATA)
.param("username", "test")
.param("password", "1234")
.param("confirmPassword", "1234")
.param("nickname", "tester")
.param("email", "test@naver.com")
)
.andDo(MockMvcResultHandlers.print());
// Then
resultActions
.andExpect(status().is3xxRedirection())
.andExpect(handler().handlerType(MemberController.class))
.andExpect(handler().methodName("join"))
.andExpect(redirectedUrlPattern("/member/login/**"));
verify(memberService, times(1)).join(any(JoinRequestDto.class));
verify(rq, times(1)).redirectWithMsg(any(String.class), any(RsData.class));
}
}
joinRequestDto, member, joinRsData는 Mock 객체인 MemerService, Rq에 가짜 값을 넣어주기 위해 생성한 객체이다.
doReturn(joinRsData).when(memberService).join(any(JoinRequestDto.class));
doReturn("redirect:/member/login").when(rq).redirectWithMsg(any(String.class), any(RsData.class));
이렇게 하면 memberService.join() 메서드를 사용할 때 반환 값으로 무조건 joinRsData를 받게된다.
rq.redirectWithMsg()의 경우, 회원가입이 성공할 경우 실행되는 redirect 경로가 반환되도록 했다.
// When
ResultActions resultActions = mockMvc
.perform(post("/member/join")
.contentType(MediaType.MULTIPART_FORM_DATA)
.param("username", "test")
.param("password", "1234")
.param("confirmPassword", "1234")
.param("nickname", "tester")
.param("email", "test@naver.com")
)
.andDo(MockMvcResultHandlers.print());
mockMvc를 사용하면 Http요청, 응답을 테스트할 수 있다!
나는 회원가입 요청으로 받을 파라미터들을 설정해줬다. SSR 프로젝트기 때문에 formData를 사용했다. 따로 프론트엔드 프레임워크를 사용한다면 json 형태로 보내면 된다.
// Then
resultActions
.andExpect(status().is3xxRedirection())
.andExpect(handler().handlerType(MemberController.class))
.andExpect(handler().methodName("join"))
.andExpect(redirectedUrlPattern("/member/login/**"));
verify(memberService, times(1)).join(any(JoinRequestDto.class));
verify(rq, times(1)).redirectWithMsg(any(String.class), any(RsData.class));
}
}
.andExpect()를 통해 http 응답을 검증할 수 있다.
redirect 요청인지 확인하고, MemberController의 join 메서드에서 요청을 잘 처리했는가 확인하는 코드이다. 검증 완료 후
verify() 코드를 통해 mock 객체들이 호출된 횟수를 비교해서 회원가입 성공 유닛 테스트를 마쳤다!
참고:
https://mangkyu.tistory.com/143
https://ws-pace.tistory.com/92
https://thalals.tistory.com/273