@MockBean을 사용한 통합(Controller)테스트 - 배종진

TEAM.새싹·2020년 5월 30일
2
post-thumbnail

이 글에서는 @MockBean을 사용한 통합테스트에 관한 이슈에 대해서 다루어 보도록 하겠다.

이 이슈는 Google OAuth를 사용하는 Service레이어를 의존하는 Controller를 테스트하는 과정에서 발생되었다.

Controller 코드는 다음과 같다.

@RestController
public class GoogleLoginController {

    GoogleLoginService googleLoginService;

    UserService userService;

    public GoogleLoginController(GoogleLoginService googleLoginService, UserService userService) {
        this.googleLoginService = googleLoginService;
        this.userService = userService;
    }

    @PostMapping("/login/google")
    public ResponseEntity loginByGoogleOAuth(@RequestBody String requestBody) {
        String accessToken = (String) JsonExtractor.getValueByKey(requestBody, "access_token");
        ResponseEntity responseEntity = googleLoginService.authenticate(accessToken);
        if (responseEntity.getStatusCode().is2xxSuccessful()) {
            userService.saveUser((GoogleUserinfoDTO) responseEntity.getBody());
        }
        return responseEntity;
    }
}

위 코드에 대해서 간략히 설명하자면

클라이언트가 /login/google 앤드포인트로 구글 인증서버에서 받은 Access_Token을 넘겨주면 핸들러 메소드가 GoogleLoginService를 사용해서 해당 Access_Token이 유효한지 아닌지를 체크하는 코드이다.(Access_Token이 유효한지 아닌지에 대한 검증은 GoogleLoginService의 RestTemplate이 구글 인증 서버에 요청을 보내 진행)

위의 컨트롤러를 테스트하기 위해 2가지의 경우의 수를 나누었다.
1. 유효하지 않은 Access_Token을 가진 요청을 보내 4xx 상태코드를 가진 응답 테스트
2. 유효한 Access_Token을 가진 요청을 보내 2xx 상태코드를 가진 응답 테스트


1. 유효하지 않은 Access_Token을 가진 요청을 보내 4xx 상태코드를 가진 응답 테스트

유효하지 않은 Access_Token을 테스트하는 작업은 매우 수월했다.

내가 임의로 만든 Access_Token(구글의 Access_Token 형식을 알기 때문에 절대 겹칠일이 없다는 가정하에 진행)을 가진 요청을 /login/google에게만 보내면 되었다.

@Test
public void GoogleLoginWithWrongAccessToken() throws Exception {
    // Given
    String GoogleAccessToken =  "basdhifbasduiofbasdohiufsaoi";

    // When & Then
    this.mockMvc.perform(post("/login/google")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"access_token\" : \"" + GoogleAccessToken + "\"}"))
            .andDo(print())
            .andExpect(status().isBadRequest());
}

위의 결과는 당연히 성공이었다!!!

하지만 문제는 두번째 경우 테스트 이었다....


2. 유효한 Access_Token을 가진 요청을 보내 2xx 상태코드를 가진 응답 테스트

유효한 Access_Token을 가진 요청을 테스트 하기 위해서는 유효한 Access_Token을 구글 인증 서버로부터 받아야 하는데 매번 테스트 할 때마다 유효한 Access_Token을 받아올 수는 없는 노릇이었다.

(또한 구글 인증 서버로부터 Access_Token을 가져오는 로직이 간단하지 않아 어쩌면 배보다 배꼽이 더 큰 격이다..)

그렇다면 이전에 사용해 보았던 Mock객체를 활용해 GoogleLoginService의 메소드 return 값을 내가 설정해 보면 어떨까?? 라는 생각이 들었다.

하지만 다시 생각해 보면 통합(Controller)테스트 인데 Spring Boot가 뜰 때 어떻게 GoogleLoginService Mock객체를 Controller에 주입해 줄 수 있을까??? 라는 또 다른 이슈가 생겼다!!!

여기 저기 참고해 보고, Sproutt스터디에 질문을 올려본 결과 으쌰으쌰 프로젝트에서 @MockBean을 사용하고 있는 걸 볼 수 있었고, 이에 대해서 찾아보니 나의 이슈를 해결할 수 있는 애노테이션이라는 것을 알게 되었다.

유효한 Access_Token 테스트 코드를 작성하기 이전에 @MockBean에 대해서 알아보자!

@MockBean

  • @MockBean은 Spring ApplicationContext에 Mock객체를 추가하게 해주는 주석이다.
  • @Configuration 또는 @RunWith(SpringRunner.class) 클래스내의 필드에서 사용할 수 있고, Class Level의 주석으로써도 사용할 수 있다.
  • Mock객체는 타입 또는 Bean name으로써 Context에 등록할 수 있고, Context내에서 정의된 동일한 유형의 단일 Bean이 존재한다면 어플리케이션 실행시 Mock Bean으로써 대체된다. 만약 동일한 타입의 Bean이 존재하지 않는다면 새로운 Mock Bean이 생성된다.
  • ApplicationContext에 알려져 있지만 Bean이 아닌 의존성은 발견되지 않을 것이며 Mock객체는 존재하는 의존성과 함께 Context에 추가되어 질 것이다.

위의 @MockBean의 Spec을 참고해 작성한 유효한 Access_Token을 가진 요청을 보내 2xx 상태코드를 가진 응답 테스트 코드는 다음과 같다.

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class GoogleLoginControllerTest {

    @Autowired
    MockMvc mockMvc;

    @MockBean
    GoogleLoginService googleLoginService;

    @Autowired
    UserService userService;

    @Test
    public void GoogleLoginWithRightAccessToken() throws Exception {
        // Given
        GoogleUserinfoDTO googleUserinfoDTO = new GoogleUserinfoDTO();
        googleUserinfoDTO.setName("frog");
        googleUserinfoDTO.setEmail("frog@email.com");
        ResponseEntity<GoogleUserinfoDTO> responseEntity = ResponseEntity.ok(googleUserinfoDTO);

        // When & Then
        when(googleLoginService.authenticate(eq("Right_Access_Token")))
                .thenReturn(responseEntity);

        this.mockMvc.perform(post("/login/google")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"access_token\" : \"Right_Access_Token\"}"))
                .andDo(print())
                .andExpect(status().isOk());

        assertThat(userService.findUserByEmail("frog@email.com").getName()).isEqualTo("frog");
    }

}

위의 결과는 다음과 같다.

@MockBean의 개념과 사용방법에 대해서 알아보았으니까 유효하지 않은 Access_Token을 테스트하는 코드를 리팩토링 하면 다음과 같다.

    @Test
    public void GoogleLoginWithWrongAccessToken() throws Exception {
        // When & Then
        when(googleLoginService.authenticate(eq("Wrong_Access_Token")))
                .thenReturn(ResponseEntity.badRequest().build());

        this.mockMvc.perform(post("/login/google")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"access_token\" : \"Wrong_Access_Token\"}"))
                .andDo(print())
                .andExpect(status().isBadRequest());
    }

@MockBean에 대해서 자세히 알아 보았고 이를 어떻게 사용하는지에 대해서도 알아 보았으니 테스트할 때 더 날아다닐 수 있을 것 같다!!! 역시 Sproutt스터디가 짱이다!! 화이팅

더 많은 정보를 얻고 싶은 독자는 다음을 참고하면 좋다.

profile
전남대 출신 개발 스터디 그룹 새싹팀 입니다. @ECONOVATION

1개의 댓글

comment-user-thumbnail
2021년 2월 23일

@AutoConfigureMockMvc 이용시 @MockBean이나 @SpyBean으로 GoogleLoginService googleLoginService을 주입받을 수는 있지만, controller에는 mock이 주입되지 않고, 원래의 방식대로 Bean이 주입되는 것 같은데 아닌가요?

@AutoConfigureMockMvc을 이용하는 경우 @MockBean이나 @SpyBean으로 Controller에 주입할 의존성을 테스트 시에만 조작하는 방식이 이루어지지 않는 것 같은데 제가 잘못하고 있는 것인지... ㅜㅜ.. @WebMvcTest를 이용하면 @Mock과 @Spy, @InjectMocks로 Controller에 의존성 주입은 조작 가능했습니다.

답글 달기