Mockito와 Stubbing

안승섭·2023년 2월 12일
1

경험들

목록 보기
2/7
post-thumbnail

단위 테스트에서 Mock을 얼마나 사용해야 할까?

지난번 포스팅에서 테스트 시 외부 API 또는 DB와 통신해야할 때 테스트 환경이 구축되어있지 않아도 Mock 사용해 테스트할 수 있다고 작성하였는데, 그렇다면 얼마나 Mock을 사용해야하는지 판단해보자.

예제

@InjectMocks
    StudyService studyService;

    @Mock MemberService memberService;
    @Mock StudyRepository studyRepository;
    @Mock StudyMapper studyMapper;

    @Test
    @DisplayName("createStudy 성공 케이스")
    void createStudy() {
        // ...생략

        when(memberService.findById(reqDTO.getOwner()))
                .thenReturn(Optional.of(member));

        when(studyMapper.toStudy(reqDTO))
                .thenReturn(study);

        when(studyRepository.save(study))
                .thenReturn(study);

        when(studyMapper.toStudyResDTO(study))
                .thenReturn(resDTO);
        // when
        StudyResDTO result = studyService.createStudy(reqDTO);

        // then
        assertAll(
                () -> assertThat(result.getLimit()).as("limit가 일치하지 않습니다").isEqualTo(resDTO.getLimit()),
                () -> assertThat(result.getName()).as("name이 일치하지 않습니다").isEqualTo(resDTO.getName()),
                () -> assertThat(result.getStatus()).as("status가 일치하지 않습니다").isEqualTo(resDTO.getStatus())
        );
    }

이전에 작성한 예제 코드를 보면 StudyService의 createStudy 메소드에서 사용되는 모든 객체(studyRepository, memberRepository, studyMapper)들을 mocking하고 있다.

단위 테스트에서 단위를 어떻게 생각하는지에 따라 다른 테스트 코드가 작성되는데 예제 코드는 createStudy 메소드에서 사용되는 모든 객체들을 엄격하게 mocking하고 있다.

그렇다면 상황을 바꿔서 생각해보자.
StudyMapper

public class StudyMapper {

    public StudyResDTO toStudyResDTO(Study study) {
        if (study == null) {
            return null;
        } else {
            StudyResDTO.StudyResDTOBuilder studyResDTO = StudyResDTO.builder();
            studyResDTO.limit(study.getChapter());
            studyResDTO.idx(study.getIdx());
            studyResDTO.status(study.getStatus());
            studyResDTO.name(study.getName());
            return studyResDTO.build();
        }
    }

    public Study toStudy(StudyReqDTO dto) {
        if (dto == null) {
            return null;
        } else {
            Study.StudyBuilder study = Study.builder();
            study.chapter(dto.getLimit());
            study.status(dto.getStatus());
            study.name(dto.getName());
            study.owner(dto.getOwner());
            return study.build();
        }
    }
}

studySevice

	@Spy
    StudyMapper studyMapper;
    
    @Test
    @DisplayName("createStudy 성공 케이스")
    void createStudy() {
        // given
        ...생략

        when(memberRepository.findById(reqDTO.getOwner()))
                .thenReturn(Optional.of(member));
        // when
        StudyResDTO result = studyService.createStudy(reqDTO);

        // then
        assertAll(
                () -> assertThat(result.getLimit()).as("limit가 일치하지 않습니다").isEqualTo(resDTO.getLimit()),
                () -> assertThat(result.getName()).as("name이 일치하지 않습니다").isEqualTo(resDTO.getName()),
                () -> assertThat(result.getStatus()).as("status가 일치하지 않습니다").isEqualTo(resDTO.getStatus())
        );
    }

studyMapper가 인터페이스로 작성되어 있었던 이전 포스팅과 달리 위의 코드로 실제로 구현이 되어있는 상태라면 @Spy 어노테이션을 사용해 따로 stubbing 하지 않고 studyMapper의 실제 코드로 단위 테스트를 작성할 수 있다.

여기서 우리는 실제로 구현되어 있어도 연관되어 있는 모든 객체들에 대해 mock을 사용할지 우리가 테스트하고자 하는 영역에 대해서만 mock을 사용할지 결정해야하는데, 뭐가 더 좋다고 할 수 없지만 개인적으로는 시키지만 않으면 후자로 진행할 것 같다.

Stubbing

Mock 객체의 행동을 지정해주기 위해 stubbing을 해주어야한다. Stubbing을 하기 위해 when을 사용해 응답 값을 정해주거나 예외를 던질 수 있다.

예제

when(method())
	.thenReturn(resDTO);
    
when(method())
	.thenThrow(NullPointerException.class);	

추가로 mockito에서는 mock 객체의 상태를 검증하기 위한 verify 메소드도 지원한다.
예제

verify(mock 객체).method(); // mock 객체 안의 method가 한번 호출되었는지 검증한다
verify(mock 객체, never()).method(); // mock 객체 안의 method가 한번도 호출되지 않았는지 검증한다

참고 - 마틴파울러
Mockito docs
더 자바, 애플리케이션을 테스트하는 다양한 방법

profile
Just Do It!

0개의 댓글