티스토리 뷰

1. Mock 객체 만들기

내, 외부 API를 사용할 때 인터페이스를 통해 객체를 생성하려면 선언되어 있는 추상 메소드를 모두 구현한

클래스 혹은 익명 클래스를 만들어야 한다.

해당 작업을 Mockito가 해주고 만들어진 Mock 객체를 사용할 수 있다.

 

위와 같은 구조에서 MemberService, StudyRepository 가 인터페이스이다.

1) MemberService는 규약만 정의해 놓은 상태라고 가정하고,

StudyRepository 는 JpaRepository를 상속받는 인터페이스이다.

public interface MemberService {
    Optional<Member> findById(Long memberId);
    void validate(Long memberId);
    void notify(Study newstudy);
    void notify(Member member);
}

public interface StudyRepository extends JpaRepository<Study, Long> {

}

2) StudyService 는 생성자에서 위 두 개의 인터페이스의 참조변수를 파라미터로 갖는다.

public class StudyService {

    private final MemberService memberService;

    private final StudyRepository repository;

    public StudyService(MemberService memberService, StudyRepository repository) {
        assert memberService != null;
        assert repository != null;
        this.memberService = memberService;
        this.repository = repository;
    }

    public Study createNewStudy(Long memberId, Study study) {
        Optional<Member> member = memberService.findById(memberId);
//        if (member.isPresent()) {
//            study.setOwnerId(memberId);
//        } else {
//            throw new IllegalArgumentException("Member doesn't exist for id: '" + memberId + "'");
//        }
        study.setOwnerId(member.orElseThrow(() -> new IllegalArgumentException("Member doesn't exist for id: '" + memberId + "'")).getId());
        Study newstudy = repository.save(study);
        memberService.notify(newstudy);
        return newstudy;
    }

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

 

3) StudyService 인스턴스를 생성하기 위해 MemberService, StudyRepository 의 Mock 객체를 만든다.

  (1) createStudyService

    - mock 메소드에 클래스 정보를 넘겨주면서 Mock 객체를 생성할 수 있다.

  (2) createStudyService2

    - @Mock 어노테이션을 통해 파라미터로 Mock 객체를 받을 수 있다.

    - @ExtendWith(MockitoExtension.class) 필요

  (3) createStudyService3

    - 클래스의 멤버로 선언된 참조변수에 @Mock 어노테이션을 통해 Mock 객체를 만들어 할당할 수 있다.

    - @ExtendWith(MockitoExtension.class) 필요

  (4) @ExtendWith(MockitoExtension.class)

    - JUnit 애서 extention 을 등록할 때 사용하는 @ExtendWith 어노테이션을 통해

    - @Mock 어노테이션을 통해 Mock 객체를 만들어 주입해주는 MockitoExtension을 등록한다.

@ExtendWith(MockitoExtension.class)
class StudyServiceTest {

    @Mock
    MemberService memberService;

    @Mock
    StudyRepository studyRepository;

    @Test
    void createStudyService() {
        MemberService memberService = mock(MemberService.class);
        StudyRepository studyRepository = mock(StudyRepository.class);

        StudyService studyService = new StudyService(memberService, studyRepository);

        assertNotNull(studyService);
    }

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

        assertNotNull(studyService);
    }

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

        assertNotNull(studyService);
    }
}

 

 

 

2. Mock 객체 Stubbing

1) 모든 Mock 객체의 행동

  • Null을 리턴한다. (Optional 타입은 Optional.empty 리턴)
  • Primitive 타입은 기본 Primitive 값.
  • 콜렉션은 비어있는 콜렉션.
  • Void 메소드는 예외를 던지지 않고 아무런 일도 발생하지 않는다.

2) Mock 객체를 Stubbing

(1) 특정한 매개변수를 받은 경우 특정한 값을 리턴하도록 설정

   - when(), thenReturn() 메소드를 활용

   - memberService.findById(1L) 이 호출되면 Optional.of(member) 를 리턴하도록 설정

   - memberService.findById(2L) 과 같이 매개변수가 다른 경우 기본적인 Mock 객체의 동작을 따른다.

   - Argument matchers 를 참고해 매개변수를 여러 형식으로 설정할 수 있다.

     any(), anyInt(), ...

     ( 예, when(memberService.findById(any())).thenReturn(Optional.of(member)); )

   - 테스트 마지막 라인 studyService.createNewStudy() 메소드에서

     memberService.findById(1L) 을 호출할 때 Optional.of(member) 를 리턴

@ExtendWith(MockitoExtension.class)
class StudyServiceTest {

    @Mock
    MemberService memberService;

    @Mock
    StudyRepository studyRepository;

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

        System.out.println(memberService.findById(1L)); // Optional.empty

        Member member = new Member();
        member.setId(1L);
        member.setEmail("mandy@gmail.com");
        when(memberService.findById(1L)).thenReturn(Optional.of(member));

        System.out.println(memberService.findById(1L)); // not empty
        System.out.println(memberService.findById(2L)); // Optional.empty

        Study study = new Study(10, "java");
        Study newStudy = studyService.createNewStudy(1L, study);// createNewStudy 에서 memberService.findById(1L) 호출

    }
}

 

(2) 특정한 매개변수를 받은 경우 특정한 예외를 던지도록 설정

   - when(), thenThrow() 활용

@ExtendWith(MockitoExtension.class)
class StudyServiceTest {

    @Mock
    MemberService memberService;

    @Mock
    StudyRepository studyRepository;

    @Test
    void createStudy() {
        when(memberService.findById(1L)).thenThrow(new RuntimeException());
    }
}

>> 실행 결과

 

(3) void 메소드가 특정 매개변수를 받거나 호출된 경우 예외를 발생시키도록 설정

   - doThrow(), when() 메소드 활용

@ExtendWith(MockitoExtension.class)
class StudyServiceTest {

    @Mock
    MemberService memberService;

    @Mock
    StudyRepository studyRepository;

    @Test
    void createStudy() {
//        doThrow(new IllegalArgumentException()).when(memberService).validate(1L);
        doThrow(new IllegalArgumentException()).when(memberService).validate(anyLong());
        assertThrows(IllegalArgumentException.class, () -> {
            memberService.validate(1L);
        });
    }
}

 

(4) 메소드가 동일한 매개변수로 여러번 호출될 때 각기 다르게 행동하도록 설정

@ExtendWith(MockitoExtension.class)
class StudyServiceTest {

    @Mock
    MemberService memberService;

    @Mock
    StudyRepository studyRepository;

    @Test
    void createStudy() {

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

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

        assertEquals("mandy@gmail.com", memberService.findById(1L).get().getEmail());
        assertThrows(IllegalArgumentException.class, () -> {
            memberService.findById(1L);
        });
        assertEquals(Optional.empty(), memberService.findById(1L));
    }
}

 

 

 

 

출처

https://www.inflearn.com/course/the-java-application-test 더 자바, 애플리케이션을 테스트하는 다양한 방법(백기선)

728x90

'Test > Mockito' 카테고리의 다른 글

[Mockito] 4. BDD Mockito 스타일 API  (0) 2021.12.10
[Mockito] 3. Mock 객체 확인  (0) 2021.12.10
[Mockito] 1. Mockito 시작하기  (0) 2021.12.09