뭐요

스프링 : 테스트 코드 작성 가이드 본문

Spring

스프링 : 테스트 코드 작성 가이드

욕심만 많은 사람 2023. 5. 16. 22:11

단위 테스트 작성 기본 구조

1개의 단위 테스트를 3단계로 나눈다.

  • Given : 어떠한 데이터가 주어졌을 때
  • When : 어떠한 함수를 실행하면
  • Then : 어떠한 결과가 나와야 함

기본 개념 요약

Mockito

  • 개발자가 동작을 직접 제어할 수 있는 가짜 객체를 지원하는 테스트 프레임워크
  • 가짜 객체를 주입시켜 객체 간의 의존성을 제거

Mock 객체 의존성 주입 Annotation

  • @Mock: 가짜 객체를 만들어 반환해주는 어노테이션
  • @Spy: Stub하지 않은 메소드들은 원본 메소드 그대로 사용하는 어노테이션
  • @InjectMocks: @Mock 또는 @Spy로 생성된 가짜 객체를 자동으로 주입시켜주는 어노테이션

Example)

UserController에 대한 단위 테스트를 작성할 때 @Mock을 통해 가짜 UserService 객체를 만들고 @InjectMocks를 통해 컨트롤러 내에 주입한다.

stub 메소드

의존성 있는 객체는 가짜 객체를 주입해서 어떤 결과를 반환하라고 정해진 답변을 준비시켜야 한다.

  • doReturn(): 가짜 객체가 특정한 값을 반환해야 하는 경우
  • doNothing(): 가짜 객체가 아무 것도 반환하지 않는 경우(void)
  • doThrow(): 가짜 객체가 예외를 발생시키는 경우

Mockito와 Junit 결합

@ExtendWith(MockitoExtension.class)

MockMVC

  • 스프링 내에서 HTTP 호출

Layer별 단위 테스트 예제

Repository 단위 테스트

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryTest {
    @Autowired
    private UserRepository userRepository;
    
    @DisplayName("사용자 추가")
    @Test
    void 사용자추가(){
        // given
        final User user = SignUpRequest.builder()
                .name("최규현")
                .email("rbgus200@@naver.com")
                .nickName("규규")
                .phoneModel("")
                .picture("")
                .build().toEntity();

        // when
        final User userSaved = userRepository.save(user);

        // then
        assertThat(userSaved.getUserId()).isNotNull();
        assertThat(userSaved.getProfile().getName()).isEqualTo("최규현");
        assertThat(userSaved.getProfile().getEmail()).isEqualTo("rbgus200@@naver.com");
    }
}

@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)

Default로 설정되는 h2 memory DB가 아닌 현재 로컬에서 설정된 DB를 사용하겠다!

@DataJpaTest

Jpa Test 에 포커싱이 맞춰진 테스트 Annotation

Summary

  • JPARepository에서 기본적으로 제공하는 method는 테스트 하지 않습니다. 주로 @Query로 작성된 JPQL 등으로 커스텀하게 추가된 메소드만이 테스트 대상입니다.

Service 단위 테스트

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @InjectMocks
    private UserService userService;

    @Mock
    private UserRepository userRepository;

    private SignUpRequest getSignUpRequest() {
        return SignUpRequest.builder()
                .name("최규현")
                .email("rbgus200@@naver.com")
                .nickName("규규")
                .phoneModel("")
                .picture("")
                .build();
    }
    private User getUser() {
        return getSignUpRequest().toEntity();
    }


    @Test
    @DisplayName("회원가입_실패_이메일중복")
    void UserServiceTest(){
        // given
        doReturn(true).when(userRepository).existsByProfileEmail(any(String.class));

        // when
        EmailExistsException exception = assertThrows(EmailExistsException.class, () -> userService.signUp(getSignUpRequest()));

        // then
        assertThat(exception.getResultCode()).isEqualTo(ResultCode.DUPLICATE_EMAIL);
    }

    @Test
    @DisplayName("회원가입_성공")
    void 회원가입_성공() {
        // given
        doReturn(false).when(userRepository).existsByProfileEmail(any(String.class));
        doReturn(getUser()).when(userRepository).save(any(User.class));

        // when
        final User newUser = userService.signUp(getSignUpRequest());

        // then
        assertThat(newUser).isNotNull();
        assertThat(newUser.getProfile().getName()).isEqualTo("최규현");
    }
}

@ExtendWith

Mockito와 Junit 결합

@InjectMocks

Mocking한 객체들을 주입해주겠다!

@Mock

Mocking해서 가짜 객체를 만들겠다!

doReturn().when().~

Mocking한 객체가 ~메소드 사용했을 때 return 해줄 값을 명시적으로 정해줌

any(A.class)

A 타입에 해당하는 객체를 만들어 임의로 넣어주겠다!

Controller 단위 테스트

@ExtendWith(MockitoExtension.class)
class UserApiTest {
    @InjectMocks
    private UserApi userApi;

    @Mock
    private UserService userService;

    private MockMvc mockMvc;

    private Gson gson;

    @BeforeEach
    public void init() {
        mockMvc = MockMvcBuilders.standaloneSetup(userApi)
                .setControllerAdvice(new GlobalExceptionHandler())
                .build();
        gson = new Gson();
    }

    private SignUpRequest getSignUpRequest() {
        return SignUpRequest.builder()
                .name("최규현")
                .email("rbgus2002@naver.com")
                .nickName("규규")
                .phoneModel("")
                .picture("")
                .build();
    }

    @Test
    @DisplayName("회원가입_실패_이메일형식X")
    void 회원가입_실패_이메일형식X() throws Exception {
        // given
        final String url = "/user";

        // when
        final ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.post(url)
                .content(gson.toJson(SignUpRequest.builder()
                        .name("최규현")
                        .email("rbgus2002")
                        .nickName("규규")
                        .phoneModel("")
                        .picture("")
                        .build()))
                .contentType(MediaType.APPLICATION_JSON)
        );

        // then
        resultActions.andExpect(status().isBadRequest());
    }

    @Test
    @DisplayName("")
    void 회원가입_실패_이메일중복존재() throws Exception {
        // given
        final String url = "/user";
        doThrow(new EmailExistsException(ResultCode.DUPLICATE_EMAIL)).when(userService).signUp(any(SignUpRequest.class));

        // when
        final ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.post(url)
                        .content(gson.toJson(getSignUpRequest()))
                        .contentType(MediaType.APPLICATION_JSON)
        );

        // then
        resultActions.andExpect(status().isBadRequest());
    }

    @DisplayName("회원가입_성공")
    @Test
    void 회원가입_성공() throws Exception {
        // given
        final String url = "/user";
        doReturn(getSignUpRequest().toEntity()).when(userService).signUp(any(SignUpRequest.class));

        // when
        final ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.post(url)
                .content(gson.toJson(getSignUpRequest()))
                .contentType(MediaType.APPLICATION_JSON)
        );

        // then
        resultActions.andExpect(status().isOk());


    }
}
MockMvc

컨트롤러 테스트를 위한 HTTP 호출 객체. 생성할 때 별도의 Exception을 handling 해주는 Controller Advice가 존재하면 set 해준다.

Gson

HTTP 호출 시에 json format으로 변환을 도와주는 객체 (별도의 dependency 추가 필요)

@BeforeEach

테스트 전에 항상 실행할 메소드. 테스트 전에 MockMvc와 Gson 객체를 생생함!


Reference

https://spring.io/guides/gs/testing-web/

https://mangkyu.tistory.com/145

https://mangkyu.tistory.com/182