Dev 달팽이 @_''

Java - Mockito 본문

Java - Mockito

Mock

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

Mockito

  • 단위 테스트를 위한 Java mocking framework

Mockito Maven

<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 객체 만들기

  • Mockito.mock() 메소드로 만드는 방법
    @Test
    void createStudyService(){

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

        StudyService studyService = new StudyService(memberService, studyRepository);

        assertNotNull(studyService);
    }
  • @Mock 애노테이션으로 만드는 방법
    • JUnit 5 extension으로 MockitoExtension을 사용해야 한다.(@ExtendWith)
    • 필드
    • 메소드 매개변수

- 필드

@ExtendWith(MockitoExtension.class)
public class StudyServiceTest {

    @Mock MemberService memberService;

    @Mock StudyRepository studyRepository;

    @Test
    void createStudyService(){

        StudyService studyService = new StudyService(memberService, studyRepository);

        assertNotNull(studyService);
    }
 }

- 메소드 매개변수

@ExtendWith(MockitoExtension.class)
public class StudyServiceTest {

    @Test
    void createStudyService(@Mock MemberService memberService, @Mock StudyRepository studyRepository){

        StudyService studyService = new StudyService(memberService, studyRepository);

        assertNotNull(studyService);
    }
 }

 

Mock 객체 Stubbing

  • Stubbing
    • 모의 객체 생성 및 모의 객체의 동작을 지정하는 것
  • 모든 Mock 객체의 행동
    • Null을 리턴(Optional 타입은 Optional.empty 리턴)
    • Primitive 타입은 기본 Primitive 값
    • 콜렉션은 비어있는 콜렉션
    • void 메소드는 예외를 던지지 않고 아무런 일도 발생하지 않음
  • Mock 객체 조작
    • 특정한 매개변수를 받은 경우 특정한 값을 리턴하거나 예외를 던지도록 만들 수 있다.
    • void 메소드 특정 매개변수를 받거나 호출된 경우 예외를 발생 시킬 수 있다.
    • 메소드가 동일한 매개변수로 여러번 호출될 때 각기 다르게 행동하도록 조작할 수 있다.
  @Test
    void createStudyServiceWithAnno(@Mock MemberService memberService, @Mock StudyRepository studyRepository){

        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);

        Member member = new Member();
        member.setId(1L);
        member.setEmail("jdb4497@gmail.com");

        //파라미터가 중요함
        when(memberService.findById(1L))
                .thenReturn(Optional.of(member));


        //파라미터가 아무거나여도 됨
//        when(memberService.findById(any()))
//                .thenReturn(Optional.of(member));


        Study study = new Study(10,"java");

        Optional<Member> findById = memberService.findById(1L);
        assertEquals("jdb4497@gmail.com",findById.get().getEmail());
//        studyService.createNewStudy(1L,study);

        doThrow(new IllegalArgumentException()).when(memberService).validate(1L);

        assertThrows(IllegalArgumentException.class, ()->{
            memberService.validate(1L);
        });

        memberService.validate(2L);

    }
  • when(memberService.findById(1L)).thenReturn(Optional.of(member));
    • memberService 객체의 findById(1L)이 실행되면 member를 리턴한다.
    • 매개변수가 중요함.(매개변수가 1L일 때만 체크)
  • when(memberService.findById(any())).thenReturn(Optional.of(member));
    • memberService 객체의 findById가 실행되면 member를 리턴한다.
    • 매개변수가 아무거나여도 됨
  • doThrow(new IllegalArgumentException()).when(memberService).validate(1L);
    • memberService 객체의 validate(1L)이 실행되면 IlleagalArgumentException을 던진다.

Mock 객체 확인

  • Mock 객체가 어떻게 사용 됐는지 확인
    • 특정 메소드가 특정 매개변수로 몇번 호출 되었는지, 최소 한번은 호출 됐는지, 전혀 호출되지 않았는지 확인
    • 어떤 순서대로 호출했는지 확인
    • 특정 시간 이내에 호출됐는지 확인
    • 특정 시점 이후에 아무 일도 벌어지지 않았는지 확인

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

    @Test
    void createNewStudy(@Mock MemberService memberService,@Mock StudyRepository studyRepository){

        StudyService studyService = new StudyService(memberService,studyRepository);

        Member member = new Member();
        member.setEmail("jdb4497@gmail.com");
        member.setId(1L);

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

        when(memberService.findById(1L))
                .thenReturn(Optional.of(member));

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


        studyService.createNewStudy(1L,study);

        assertNotNull(study.getOwnerId());
        assertEquals(member.getId(),study.getOwnerId());

        verify(memberService,times(1)).notify(study);
        verify(memberService,times(1)).notify(member);
        verify(memberService, never()).validate(any());
}
  • verify(memberService, times(1)).notify(study);
    • memberService의 notify(study) 함수가 1번 호출됐는지 확인한다.
  • verify(memberService, never()).validate(any());
    • memberService의 validate 함수가 한번도 호출되지 않았는지 확인한다.

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

    @Test
    void createNewStudy(@Mock MemberService memberService,@Mock StudyRepository studyRepository){

        StudyService studyService = new StudyService(memberService,studyRepository);

        Member member = new Member();
        member.setEmail("jdb4497@gmail.com");
        member.setId(1L);

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

        when(memberService.findById(1L))
                .thenReturn(Optional.of(member));

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


        studyService.createNewStudy(1L,study);

        assertNotNull(study.getOwnerId());
        assertEquals(member.getId(),study.getOwnerId());

        InOrder inOrder = inOrder(memberService);
        inOrder.verify(memberService).notify(study);
        inOrder.verify(memberService).notify(member);
  }
  • InOrder(memberService);
    • memberService에서 실행되는 함수의 순서를 확인한다.
    • notify(study)가 notify(member) 보다 먼저 실행되는지 확인한다.

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

    @Test
    void createNewStudy(@Mock MemberService memberService,@Mock StudyRepository studyRepository){

        StudyService studyService = new StudyService(memberService,studyRepository);

        Member member = new Member();
        member.setEmail("jdb4497@gmail.com");
        member.setId(1L);

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

        when(memberService.findById(1L))
                .thenReturn(Optional.of(member));

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


        studyService.createNewStudy(1L,study);

        assertNotNull(study.getOwnerId());
        assertEquals(member.getId(),study.getOwnerId());

        verify(memberService,times(1)).notify(study);
	verify(memberService,times(1)).notify(member);
        verifyNoMoreInteractions(memberService);
  }
  • verifyNoMoreInteractions(memberService);
    • memberService에서 더 이상 아무 일도 벌어지지 않았는지 확인한다.

Mockito BDD 스타일

  • BDD(Behavior-Driven Development)
    • TDD에서 파생된 개발 방법
    • "이 클래스가 어떤 행위를 해야한다(should do something)"라는 식의 문장으로 작성
    • 행위에 대한 테스트에 집중
  • 행동에 대한 스펙
    • Title
    • Narrative
      • As a / I want / so that
    • Acceptance critera
      • Given / When / Then
  • Mockito는 BddMockito라는 클래스를 통하여 BDD 스타일의 API를 제공

- 기존 Mockito

    @Test
    void createNewStudy(@Mock MemberService memberService,@Mock StudyRepository studyRepository){

        // Given
        StudyService studyService = new StudyService(memberService,studyRepository);

        Member member = new Member();
        member.setEmail("jdb4497@gmail.com");
        member.setId(1L);

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

	when(memberService.findById(1L)).thenReturn(Optional.of(member));
        when(studyRepository.save(study)).thenReturn(study);

        // When
        studyService.createNewStudy(1L,study);

        // Then
        assertEquals(member.getId(),study.getOwnerId());
	verify(memberService,times(1)).notify(member);
        verify(memberService,times(1)).notify(study);
        verifyNoMoreInteractions(memberService);
    }
  • 기존 Mockito에서는 Given에 when이 들어가있어 혼동할 가능성이 있음.
  • 기존 Mockito에서 when을 given으로 verify를 then으로 바꾸면 됨.(given과 then은 BDDMockito에 포함)

- BDDMockito

    @Test
    void createNewStudyBDD(@Mock MemberService memberService,@Mock StudyRepository studyRepository){

        // Given
        StudyService studyService = new StudyService(memberService,studyRepository);

        Member member = new Member();
        member.setEmail("jdb4497@gmail.com");
        member.setId(1L);

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

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

        // When
        studyService.createNewStudy(1L,study);

        // Then
        assertEquals(member.getId(),study.getOwnerId());
        then(memberService).should(times(1)).notify(member);
        then(memberService).should(times(1)).notify(study);
        then(memberService).shouldHaveNoMoreInteractions();

    }
  • when이 given으로 thenReturn이 willReturn으로 변함.
  • verify가 then으로 바뀌고 should이 추가.