Controller 테스트를 하기위해서 @Mock말고 @MockBean을 써야하는 이유

박준수·2023년 9월 26일
0

이것저것

목록 보기
5/9
post-custom-banner

처음으로 Controller 테스트 코드를 작성해보았다.

다음은 일기를 생성하는 Controller에 대한 테스트 코드이다.

@Tag(name = "Diary", description = "일기 API")
@RestController
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@RequestMapping("/diaries")
public class DiaryController {

    private final DiaryService diaryService;
    private final S3UploadService s3UploadService;

    @Operation(summary = "일기 등록")
    @PostMapping
    public ResponseEntity<ResultResponse> createDiary(@AuthenticationPrincipal UserDetail user,
                                                      @RequestPart("file") MultipartFile multipartFile,
                                                      @Valid @RequestPart(value="createRequest") DiaryCreateReq createRequest) throws IOException {
        String drawingUrl = s3UploadService.saveFile(multipartFile, user.getUsername());
        diaryService.createDiary(user, createRequest, drawingUrl);
        return ResponseEntity.ok(ResultResponse.of(ResultCode.DIARY_CREATE_SUCCESS));
    }
 }
@WebMvcTest(DiaryController.class)
@ExtendWith(RestDocumentationExtension.class)
@DisplayName("Diary 컨트롤러의 ")
class DiaryControllerTest extends MockApiTest {

    @MockBean
    private DiaryService diaryService;
    @MockBean
    private S3UploadService s3UploadService;

    @Test
    @WithCustomMockUser
    @DisplayName("일기가 등록되는지 확인한다.")
    void createDiary() throws Exception {

        MockMultipartFile file = new MockMultipartFile(
                "file",      // 파라미터 이름
                "file_name", // 파일 이름
                "image/jpeg",// 파일 타입
                "test file".getBytes(StandardCharsets.UTF_8) // 파일 내용
        );
        given(s3UploadService.saveFile(any(), any())).willReturn(String.valueOf(file));

        DiaryCreateReq createReq = DiaryControllerFixture.CREATE_REQ;
        String jsonByCreateReq = objectMapper.writeValueAsString(createReq);
        MockMultipartFile request = new MockMultipartFile(
                "createRequest",
                "createRequest",
                "application/json",
                jsonByCreateReq.getBytes(StandardCharsets.UTF_8)
        );
        String token = "accessToken";
        diaryService.createDiary(any(), any(), any());

        ResultActions perform =
                mockMvc.perform(
                        multipart("/diaries")
                                .file(file)
                                .file(request)
                                .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
                );

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

        // docs
        perform.andDo(print())
                .andDo(document("register diary",
                        getDocumentRequest(),
                        getDocumentResponse()));

    }
}

문제 상황

  • 다음은 DiaryController에서 일기를 등록하는 createDiary를 테스트하는 코드를 작성한 것이다.
  • Controller 테스트 예제에서 Service에 MockBean으로 설정을 해놓길레 따라했지만 @Mock으로 해놓으니깐

이런 에러가 발생했다.

문제 해결 과정

  • @WebMvcTest의 특징

@WebMvcTest 같은 경우는 스프링 MVC를 위한 테스트로 컨트롤러와 관련된 애들만 빈으로 등록하고, Service, Repository같은 레이어는 빈으로 등록하지 않아 의존성을 끊습니다.

즉, 스프링 컨텍스트에서 컨트롤러와 관련된 애들만 빈으로 등록하고 테스트를 합니다.

(@WebMvcTest는 빈으로 등록해서 사용하는 통합 테스트의 일종)

  • 그렇다면 @Mock으로는 DiaryServiceS3UploadService 를 Bean으로 등록해주지 않은것인다.

@Mock

  • 단위 테스트에서 사용된다.
  • Mock 객체를 직접 생성하여 사용하므로, 스프링 컨텍스트와 관련이 없다.

@MockBean

  • 통합 테스트를 수행할 때 사용된다.
  • Mock 객체를 생성하고, 스프링 컨텍스트에 등록을 한다.
    • 스프링 컨텍스트에 직접 생성하므로, 관련된 객체와 연결하여 통합 테스트를 수행할 수 있다.

결론

통합 테스트에서 컨테이너가 필요하고, 빈을 등록해야한다면 @MockBean을 통해 등록해주면 되고,

단위 테스트 같이 컨테이너가 필요 없다면 @Mock을 통해 객체를 만들어주면 됩니다.

잠깐

통합 테스트에서 컨테이너가 필요하다는 무슨의미지?

  • @WebMvcTest 은 통합테스트의 일종
  • Controller와 관련된 애들만 빈으로 등록하기에 @MockBean으로 Service등을 등록시켜줘야 함
  • 왜 @MockBean이냐? @MockBean은 mock 객체를 스프링 컨텍스트에 등록하고, @Mock은 @InjectMocks에 대해서만 해당 클래스안에서 정의된 객체를 찾아서 의존성을 해결하기 때문
  • 따라서 통합 테스트에서 컨테이너가 필요하고, 빈을 등록해야한다면 @MockBean을 통해 등록해줘야함

단위 테스트 같이 컨테이너가 필요 없다면?

  • @Mock을 통해 객체를 만들어주면 됨
  • 단위 테스트는 개별 컴포넌트의 동작을 격리된 환경에서 테스트하므로 전체 스프링 컨테이너를 로드하지 않아도 됨
  • 이때는 외부 의존성을 모의(Mock) 빈으로 대체하여 테스트

참고

[Spring] SpringBoot 테스트 시 @WebMvcTest와 @SpringBootTest의 차이
[Spring] @Mock과 @MockBean의 차이점은 무엇일까?

profile
방구석개발자
post-custom-banner

0개의 댓글