@SpringBootTest
@SpringBootTest
는 해당 어플리케이션의 모든 빈을 IoC Container에 등록하고 테스트를 진행하기 때문에 테스트가 느려질 수 밖에 없음assertEquals()
와 같은 메소드는 AssertJ가 주는 메소드에 비해 가독성이 떨어짐@ExtendWith(MockitoExtension.class)
: SpringBoot 2.2.0부터 공식적으로 JUnit5를 지원함에 따라 해당 어노테이션을 통해 Mockito와 JUnit 연동. 가짜 환경 세팅@Mock
: 스텁 처리되는 가짜 객체를 반환@Spy
: 원본 메소드 그대로 사용하기 위해 진짜 객체를 반환@InjectMocks
: @Mock
또는 @Spy
로 생성된 객체를 자동으로 주입doReturn()
: 가짜 객체가 특정한 값을 반환해야 하는 경우doNothing()
: 가짜 객체가 아무 것도 반환하지 않는 경우(void)doThrow()
: 가짜 객체가 예외를 발생시키는 경우
then~()
- 메소드를 실제 호출 > 메소드 작업이 오래 걸릴 경우 끝날때까지 기다림. 대상 메소드에 문제점이 있을 경우 발견 가능.
do~()
- 메소드를 실제 호출 X > 컴파일 시 리턴 타입 체크가 안 됨. 실제 메소드를 호출하지 않기 때문에 대상 메소드에 문제점이 있어도 알수가 없음
doNothing()
을 통해 void 메서드 stubbing 가능- spy는 mock처럼 위임된 메서드를 호출하는 것이 아니라 실제 인스턴스의 메서드를 호출하기 때문에 doReturn 권장
MockMvc
: 컨트롤러를 테스트에 HTTP 호출을 제공
@ExtendWith(MockitoExtension.class)
class UserControllerTest {
@InjectMocks
private UserController userController;
@Mock
private UserService userService;
private MockMvc mockMvc;
@BeforeEach
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
}
}
꿀팁!!!!!!!!!
doThrow(new CustomException()).when().save());
@ControllerAdvice
에서 처리되는 커스텀 Exception을 테스트 할 때는 MockMvc에@ControllerAdvice
정보도 설정해줍니다.@BeforeEach public void init() { mockMvc = MockMvcBuilders.standaloneSetup(registrationController) .setControllerAdvice(new YourControllerAdvice()) .build(); }
그렇지 않으면 아래와 같은 테스트 실패를 겪을 것입니다!!!!!
jakarta.servlet.ServletException: Request processing failed: com.*.*.exception.*Exception
수고~ ><
@WebMvcTest
: MockMvc 객체가 자동으로 생성되며 ControllerAdvice나 Filter, Interceptor 등 웹 계층 테스트에 필요한 요소들을 모두 빈으로 등록해 스프링 컨텍스트 환경을 구성. 스프링부트가 제공하는 테스트 환경이므로 @Mock
과 @Spy
대신 각각 @MockBean
과 @SpyBean
을 사용.
스프링은 내부적으로 스프링 컨텍스트를 캐싱해두고 동일한 테스트 환경이라면 재사용한하는데 특정 컨트롤러만을 빈으로 만들고 @MockBean
과 @SpyBean
으로 빈을 모킹하는 @WebMvcTest
는 캐싱의 효과를 거의 얻지 못하고 새로운 컨텍스트의 생성 필요. 빠른 테스트를 원한다면 직접 MockMvc를 생성하는 방법을 사용하는 것이 좋을 수 있음.
@WebMVcTest(UserController.class)
class UserControllerTest {
@MockBean
private UserService userService;
@Autowired
private MockMvc mockMvc;
}
@ExtendWith(MockitoExtension.class)
class UserControllerTest {
@InjectMocks
private UserController userController;
@Mock
private UserService userService;
private MockMvc mockMvc;
@BeforeEach
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
}
@DisplayName("회원 가입 성공")
@Test
void signUpSuccess() throws Exception {
// given
SignUpRequest request = signUpRequest();
UserResponse response = userResponse();
doReturn(response).when(userService)
.signUp(any(SignUpRequest.class));
// when
ResultActions resultActions = mockMvc.perform(
MockMvcRequestBuilders.post("/users/signUp")
.contentType(MediaType.APPLICATION_JSON)
.content(new Gson().toJson(request))
);
// then
MvcResult mvcResult = resultActions.andExpect(status().isOk())
.andExpect(jsonPath("email", response.getEmail()).exists())
.andExpect(jsonPath("pw", response.getPw()).exists())
.andExpect(jsonPath("role", response.getRole()).exists())
}
}
@Test
: 테스트 인식@DisplayName
: default로 저장되는 테스트 이름을 커스텀any()
any()
를 사용할 때에는 특정 클래스의 타입을 지정해주는 것이 좋음MockMvcRequestBuilders
@DisplayName("회원 가입")
@Test
void signUp() {
// given
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
SignUpRequest request = signUpRequest();
String encryptedPw = encoder.encode(request.getPw());
doReturn(new User(request.getEmail(), encryptedPw, UserRole.ROLE_USER)).when(userRepository)
.save(any(User.class));
// when
UserResponse user = userService.signUp(request);
// then
assertThat(user.getEmail()).isEqualTo(request.getEmail());
assertThat(encoder.matches(signUpDTO.getPw(), user.getPw())).isTrue();
// verify
verify(userRepository, times(1)).save(any(User.class));
verify(passwordEncoder, times(1)).encode(any(String.class));
}
verify()
: Mockito 라이브러리를 통해 만들어진 가짜 객체의 특정 메소드가 호출된 횟수를 검증 가능@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@DisplayName("사용자 추가")
@Test
void addUser() {
// given
User user = user();
// when
User savedUser = userRepository.save(user);
// then
assertThat(savedUser.getEmail()).isEqualTo(user.getEmail());
assertThat(savedUser.getPw()).isEqualTo(user.getPw());
assertThat(savedUser.getRole()).isEqualTo(user.getRole());
}
private User user() {
return User.builder()
.email("email")
.pw("pw")
.role(UserRole.ROLE_USER).build();
}
}
@DataJpaTest
@Entity
가 있는 클래스들을 스캔@DataJpaTest
를 사용참고