Mockito

이연중·2021년 2월 5일
0

JAVA

목록 보기
16/20

Mockito 소개

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

  • Mockito: Mock 객체를 쉽게 만들고 관리하고 검증할 수 있는 방법 제공

  • 테스트를 작성하는 자바 개발자 50%+ 사용하는 Mock 프레임워크

Mockito 시작하기

스프링 부터 2.2+ 프로젝트 생성시 spring-boot-starter-test에서 자동으로 Mockito 추가해줌

if, 스프링 부트를 쓰지 않는다면 의존성을 직접 추가

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.1.0</version>
    <scope>test</scope>
</dependency>


<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>3.1.0</version>
    <scope>test</scope>
</dependency>

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

  • Mock을 만드는 방법
  • Mock이 어떻게 동작해야 하는지 관리하는 방법
  • Mock의 행동을 검증하는 방법

Mock 객체 만들기

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

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

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

  • JUnit5 extension으로 MockitoExtension을 사용해야 함
  • 필드
  • 메소드 매개변수
//mock 객체를 여러 테스트에 걸쳐 사용하는 경우
@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 리턴)
  • Primitive 타입은 기본 Primitive 값
  • 콜렉션은 비어있는 콜렉션
  • Void 메소드는 예외를 던지지 않고 아무런 일도 발생하지 않음

Mock 객체 조작

  • 조작을 통해 특정 매개변수를 받은 경우 특정한 값을 리턴하거나 예외를 던지도록 만들 수 있음

    when(memberService.findById(any())).thenReturn(Optional.of(member));
    when(memberService.findById(1L)).thenThrow(new RuntimeException());
  • Void 메소드가 특정 매개변수를 받거나 호출된 경우 예외를 발생 시킬 수 있음

    doThrow(new IllegalArgumentException()).when(memberService).validate(1L);
  • 메소드가 동일한 매개변수로 여러번 호출될 때 각기 다른 행동을 하도록 조작할 수 있음

    when(memberService.findById(any()))
        .thenReturn(Optional.of(member))
        .thenThrow(new RuntimeException())
        .thenReturn(Optional.empty());

연습

Study study= new Study(10,"테스트");

// TODO memberService 객체에 findById 메소드를 1L 값으로 호출하면 Optional.of(member) 객체를 리턴하도록 Stubbing
when(memberService.findById(1L)).thenReturn(Optional.of(member));

// TODO studyRepository 객체에 save 메소드를 study 객체로 호출하면 study 객체 그대로 리턴하도록 Stubbing
when(studyRepository.save(study)).thenReturn(study);
    
studyService.createNewStudy(1L,study);

assertNotNull(study.getOwner());
assertEquals(member,study.getOwner());

Mock 객체 확인

Mock 객체가 어떻게 사용이 됐는지 확인할 수 있음

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

    verify(memberService,times(1)).notify(study); //memberService에서 한번 study를 매개변수로 notify()가 호출이 됐어야 한다.
    verify(memberService, never()).validate(any()); //validate는 어떠한 argument를 가지더라도 호출이 되지 않는다.
    // 그렇지 않을 시 에러 발생!!
  • 어떤 순서로 호출했는지

    Inorder inOrder=inOrder(memberService);
    inOrder.verify(memberService).notify(study);
    inOrder.verify(memberService).notify(member);
    //해당 매개변수를 순서로 메소드가 호출되어야 함 
  • 특정 시간 이내에 호출됐는지

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

    verifyNoMoreInteractions(memberService);

Mockito BDD 스타일 API

BDD: 어플리케이션이 어떻게 행동해야 하는지에 대한 공통된 이해를 구성하는 방법으로 TDD에서 창안

행동에 대한 스팩

  • Title
  • Narrative
    • As a / I want/ so that
  • Acceptance criteria
    • Given/ When/ Then(~상황이 주어졌을때 -> ~하면 -> ~할 것이다)

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

@Test
void createNewStudy(){
	//Given
    StudyService studyService= new StudyService(memberService,studyRepositody);
    assertNotNull(studyService);
    
    Member member=new Member();
    member.setId(1L);
    member.serEmail("dd@gmail.com");
    
    Study study= new Study(10,"테스트");
    //Given안에 When -> Given로 
    when(memberService.findById(1L)).thenReturn(Optional.of(member));
    when(studyRepository.save(study)).thenReturn(study);
    
    //When
    studyService.createNewStudy(1L,study); 
    
    //Then
    assertEquals(member,study.getOwner());
    //Then안에 Verify -> Then로
    verify(memberService,times(1)).notify(study);
    verifyNoMoreInteraction(memberService);
}

위 코드를 토대로 아래처럼 알맞게 바꾸면 된다.

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();

연습

StudyService.java

public Study openStudy(Study study){
    study.open();
    Study openedStudy = repository.save(study);
    memberService.notify(openedStudy);
    return openedStudy;
}

StudyServiceTest.java

@DisplayName("다른 사용자가 볼 수 있도록 스터디를 공개한다.")
@Test
void openStudy() {
    // Given
    StudyService studyService = new StudyService(memberService, studyRepository);
    Study study = new Study(10, "더 자바, 테스트");
    // TODO studyRepository Mock 객체의 save 메소드를호출 시 study를 리턴하도록 만들기.
    given(studyREpository.save(study)).willReturn(study);

    // When
    studyService.openStudy(study);

    // Then
    // TODO study의 status가 OPENED로 변경됐는지 확인
    assertEquals(StudyStatus.OPENED,study.getStatus());
    // TODO study의 openedDataTime이 null이 아닌지 확인
    assertNotNull(study.getOpenedDateTime());
    // TODO memberService의 notify(study)가 호출 됐는지 확인.
    then(memberService).should().notify(study);
}

참고

https://www.inflearn.com/course/the-java-application-test

profile
Always's Archives

0개의 댓글