프로젝트/크루트

처음 테스트 코드를 작성하며 겪었던 문제들

발전생 2022. 2. 10. 20:42

spring boot의 controller 클래스의 테스트 코드를 만들어봤다. 이게 내 첫 테스트 코드 경험이다.

mockito 쓰는 게 아직 낯설지만 테스트 코드를 작성하다 만났던 에러들을 기록한다.

 

 

@WebMvcTest(UserApiController.class)
class UserApiControllerTest {
    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserService userService;

    private Gson gson = new Gson();
    ...
}

테스트 케이스 위에 @WebMvcTest 를 써주는 것만으로도 무사히 테스트가 통과됐다. 처음에는 검색 시 너무 많은 애노테이션이 나와서 이것저것 다 써봤는데 이거 하나면 충분했다. 주의할 점이라면 userService를 주입해주지 않으면 applicationContext에 문제가 있다고 에러가 뜬다. 분명 컨트롤러에서 생성자 의존관계 주입을 통해 userService 빈을 받지만 테스트 코드에서는 스프링의 정상적인 flow대로 빈 주입이 이루어지지 않는 것으로 보인다.

 

@MockBean이라는 신기한 개념의 애노테이션도 있었다.  아래 코드를 위해서 userService에 mockito가 관리해줄 수 있는 빈을 주입했다. controller가 수행하다가 userService의 join을 호출 시 Error를 던지게 해준다. doThrow를 사용하지 않으면 데이터베이스에 새로운 User를 등록하게 되고 정상적으로 수행된 뒤 200 코드를 보내준다. 

doThrow(new NameExistsException()).when(userService).join(payload.toUser());

 

doThrow 사용 시 주의해야 할 점 2가지가 있었다. 에러의 원인이 되었던 점들이다.

첫째로, when().method()에서 method의 매개변수에 신경 써야 한다.

controller에서 UserService의 join을 호출할 때 넘겨준 매개변수가 when 뒤에 있는 메소드에 넘겨준 매개변수와 같아야지만 doThrow가 실행된다. 즉, 에러를 던진다. 만약 다른 객체로 인식되면 doThrow는 발생하지 않아 200 성공 응답을 보낸다. 그래서 비교 대상인 User 클래스에 equals()와 hashCode()를 작성해줬다. 이 때 적당한 필드 몇개를 가지고 작성해야 한다. 설계상 고유해야 하는 email과 name을 가지고 equals()와 hashCode()를 만들었다.

 

둘째로, doThrow()에 XXX.class를 넘겨줄 수 있지만 이렇게 되면 합당하게도(?) 에러 객체를 만드는 게 아니라 에러를 던지기만 한다. 에러 상황에서도 에러 원인을 message에 담아 body로 넘겨줘야 하는 로직 상 에러 객체가 생성이 돼야 했다. 이 body 내용 또한 테스트를 해야한다. 그래서 new를 사용해 에러 클래스의 생성자를 만들고 던져줬다. 

 

이것이 정상적으로 통과되는 테스트 코드이다.

@Test
    public void create_existedName_shouldFailAndReturn400() throws  Exception {
        UserApiController.CreateUserRequest payload = new UserApiController.CreateUserRequest();
        payload.setEmail("already@gmail.com");
        payload.setPassword("12345678");
        payload.setName("na");
        payload.setPosition("frontend");

        doThrow(new NameExistsException()).when(userService).join(payload.toUser());

        mvc.perform(post("/api/v1/users").contentType(MediaType.APPLICATION_JSON).content(gson.toJson(payload)))
                .andExpect(status().is(400))
                .andExpect(jsonPath("$.message").value("이미 존재하는 이름입니다"));
    }