Java

Mockito를 사용해서 자바 애플리케이션을 테스트 하는 방법

마닐라 2022. 3. 20. 15:16

백기선님의 인프런 강의를 듣고 정리하여 쓴 글입니다.

 

Mock은 진짜 객체와 비슷하게 동작하지만 프로그래머가 직접 그 객체의 행동을 관리하는 객체입니다.

Mockito는 Mock 객체를 쉽게 만들고 관리하고 검증할 수 있는 방법을 제공합니다.

테스트를 작성하는 자바 개발자 50% 이상이 사용하는 Mock 프레임워크입니다.

 

단위에 대한 범위는 규정하기 나름입니다. 하나하나의 객체를 단위로 볼 수도 있고 하나의 행위와 관련된 객체들의 집합을 단위로 볼 수도 있습니다.

 

단위 테스트에 대한 고찰

 

다음의 세 가지만 알면 Mock을 활용한 테스트를 쉽게 작성할 수 있습니다.

- Mock을 만드는 방법

- Mock이 어떻게 동작해야 하는지 관리하는 방법

- Mock의 행동을 검증하는 방법

 

Mockito 레퍼런스

 

Mock 객체 만들기

테스트 대상은 StudyService이고 StudyService가 사용하는 MemberService와 StudyRepository의 구현체가 준비 안되어 있는 상황을 가정한 테스트 상황입니다.구현체가 이미 있더라도 StudyService만 테스트하고 싶은 상황에서 사용할 수도 있습니다.

 

Mockito.mock() 메소드로 만드는 방법

MemberService memberService = mock(MemberService.class);
StudyRepository studyRepository = mock(StudyRepository.class);
 

@Mock 애노테이션으로 만드는 방법

  • JUnit 5 extension으로 MockitoExtension을 사용해야 합니다.
  • 필드
  • 메소드 매개변수
 
@ExtendWith(MockitoExtension.class)
class StudyServiceTest {
    @Mock MemberService memberService;
    @Mock StudyRepository studyRepository;

 

Mock 객체를 하나의 테스트에서만 사용할 때에는 아래와 같이 작성하면 됩니다.

@ExtendWith(MockitoExtension.class)
class StudyServiceTest {
    @Test
    void createStudyService(@Mock MemberService memberService,
                            @Mock StudyRepository studyRepository) {
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);
    }
}

 

Mock 객체 Stubbing

모든 Mock 객체의 행동

  • Null을 리턴한다. (Optional 타입은 Optional.empty 리턴)
    Optional<Member> optional = memberService.findById(1L); // Optional.empty
    
  • Primitive 타입은 기본 Primitive 값.
  • 콜렉션은 비어있는 콜렉션.
  • Void 메소드는 예외를 던지지 않고 아무런 일도 발생하지 않습니다.
    memberService.validate(2L); // 아무일도 발생 x
    

 

Mock 객체를 조작할 수 있습니다.

  • 특정한 매개변수를 받은 경우 특정한 값을 리턴하거나 예외를 던지도록 만들 수 있습니다.
    @Test
    void createStudyService(@Mock MemberService memberService,
                            @Mock StudyRepository studyRepository) {
        Member member = new Member();
        member.setId(1L);
        member.setEmail("inseongdev@gmail.com");
    
        //1이라는 매개변수로 호출하면 위의 멤버를 리턴한다.
        when(memberService.findById(1L)).thenReturn(Optional.of(member));
    
    
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);
    }
  • Void 메소드 특정 매개변수를 받거나 호출된 경우 예외를 발생 시킬 수 있습니다.
    doThrow(new IllegalArgumentException()).when(memberService).validate(1L);
    assertThrows(IllegalArgumentException.class, () -> {
        memberService.validate(1L);
    });
    memberService.validate(2L);

    Subbing void methods with exceptions
  • 메소드가 동일한 매개변수로 여러번 호출될 때 각기 다르게 행동하도록 조작할 수도 있습니다.
    when(memberService.findById(any()))
            .thenReturn(Optional.of(member)) // 정상적인 Member 객체리턴
            .thenThrow(new RuntimeException()) // 예외 발생
            .thenReturn(Optional.empty()); // 비어있는 객체
    
    Optional<Member> byId = memberService.findById(1L);
    assertEquals("inseongdev@gmail.com", byId.get().getEmail());
    assertThrows(RuntimeException.class, () -> {
        memberService.findById(2L);
    });
    assertEquals(Optional.empty(), memberService.findById(3L));
     
    Stubbing consecutive calls

 

Mock 객체 확인

Mock 객체가 어떻게 사용이 됐는지/어떤 일이 일어났는지 확인할 수 있습니다.

- 특정 메소드가 특정 매개변수로 몇번 호출 되었는지, 최소 한번은 호출 됐는지, 전혀 호출되지 않았는지

- 어떤 순서대로 호출했는지

- 특정 시간 이내에 호출됐는지

- 특정 시점 이후에 아무 일도 벌어지지 않았는지

 

Mockito BDD 스타일 API

BDD(Behavior driven development) : 애플리케이션이 어떻게 “행동”해야 하는지에 대한 공통된 이해를 구성하는 방법으로, TDD에서 창안했습니다.

행동에 대한 스팩

  • Title(이름)
  • Narrative(설명) - As a / I want / so that
  • Acceptance criteria - Given / When / Then

Mockito는 BddMockito라는 클래스를 통해 BDD 스타일의 API를 제공합니다.

 

When -> Given

given(memberService.findById(1L)).willReturn(Optional.of(member));
given(studyRepository.save(study)).willReturn(study);

 

Verify -> Then

then(memberService).should(times(1)).notify(study);
then(memberService).shouldHaveNoMoreInteractions();

 

참고