더 자바, 애플리케이션을 테스트하는 다양한 방법

Mockito

Mockito

Mockito 소개

Mockito 시작하기

  • 스프링 부트 2.2+ 프로젝트 생성시 spring-boot-starter-test 에서 자동으로 Mockito 추가해 준다.
  • 스프링 부트를 쓰지 않는다면, 의존성 직접 추가해야한다.

<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() 메소드로 만드는 방법


        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;

}


@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 객체를 조작해서


@ExtendWith(MockitoExtension.class)
class StudyServiceTest {

    @Test
    void createNewStudy(@Mock MemberService memberService,
                        @Mock StudyRepository studyRepository) {
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);

        Member member = new Member();
        member.setId(1L);
        member.setEmail("test@email.com");

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

        Optional<Member> byId = memberService.findById(1L);
        assertEquals("test@email.com", byId.get().getEmail());

        assertThrows(RuntimeException.class, () -> {
            memberService.findById(2L);
        });

        assertEquals(Optional.empty(), memberService.findById(3L));
    }

}


@ExtendWith(MockitoExtension.class)
class StudyServiceTest {

    @Test
    void createNewStudy(@Mock MemberService memberService,
                        @Mock StudyRepository studyRepository) {
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);

        Member member = new Member();
        member.setId(1L);
        member.setEmail("test@email.com");

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

        // TODO memberService 객체에 findById 메소드를 1L 값으로 호출하면 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 객체가 어떻게 사용이 됐는지 확인할 수 있다.


@ExtendWith(MockitoExtension.class)
class StudyServiceTest {

    @Mock MemberService memberService;
    @Mock StudyRepository studyRepository;

    @Test
    void createNewStudy() {
        StudyService studyService = new StudyService(memberService, studyRepository);
        assertNotNull(studyService);

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

        studyService.createNewStudy(1L, study);

        
        verify(memberService, times(1)).notify(study);
        verify(memberService, never()).validate(any());
        InOrder inOrder = inOrder(memberService);
        inOrder.verify(memberService).notify(study);
        inOrder.verify(memberService).notify(new 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를 제공한다.
  • 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();


    @DisplayName("다른 사용자가 볼 수 있도록 스터디를 공개한다.")
    @Test
    void openStudy() {
        // Given
        StudyService studyService = new StudyService(memberService, studyRepository);
        Study study = new Study(10, "더 자바, 테스트");

        assertNull(study.getOpenedDateTime());

        // 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);
    }


© 2020. All rights reserved.

SIKSIK