테스트/JUnit

JUnit5 - Java에서의 단위테스트5 Mock, Mockito, Stup

ysk(0soo) 2022. 8. 21. 21:55

Mock 객체 Stubbing

stub이란 토막,꽁초,남은부분,몽당연필.. 이라는 뜻으로 dummy객체가 마치 실제로 동작하는 것 처럼 보이도록 만들어놓은 것

  • 즉 Mock 객체의 행동을 조작하는 것

모든 Mock 객체의 행동

  • Null을 리턴한다. (Optional 타입은 Optional.empty 리턴. 즉 비어있는 옵셔널 객체)
  • Primitive 타입은 기본 Primitive 값.
  • 콜렉션은 비어있는 콜렉션.
  • Void 메소드는 예외를 던지지 않고 아무런 일도 발생하지 않는다.
  • 예제1
  • memberService.findById(1L) member 객체를 나오게 할라면 다음과 같이 쓰면 된다.
    • 만약 2L, 3L 등 다른 member 객체를 나오게 한다면 오류가 난다.
      • 1L로만 member를 나오게 했기 때문
    • 즉 when().thenReturn은 mock 객체를 직접 핸들링한다
    • org.mockito.ArgumentMatchers.any(); 메서드를 사용하면
      항상 같은 객체가 나온다 (아무 값이나 상관 없다).
  • @Test void createNewStudy(@Mock MemberService memberService, @Mock StudyRepository studyRepository) { Member member = new Member(); member.setId(1L); member.setEmail("youngsoo@naver.com"); Optional<Member> byId = memberService.findById(1L); when(memberService.findById(1L)) .thenReturn(Optional.of(member)); Study study = new Study(10, "java"); Optional<Member> findById = memberService.findById(1L); assertEquals("youngsoo@naver.com", findById.get().getEmail()); }
  • 예외를 던지는 doThrow() org.mockito.Mockito.doThrow;
    • doThrow(new IllegalArgumentException())
              .when(memberService).validate();
      
      assertThrows(IllegalArgumentException.class, () -> {
          memberService.validate();
          });
  • 순서대로 호출하기
  • when(memberService.findById(any()))
                  .thenReturn(Optional.of(member))
                  .thenThrow(new RuntimeException())
                  .thenReturn(Optional.empty());
    • 첫번째 호출 멤버
    • 두번째 호출 런타임 익셉션
    • 세번째 호출 빈 옵셔널 객체를 내보낸다

Mockito에서는 아래의 stub메서드들을 지원.
doReturn(): Mock 객체가 특정한 값을 반환해야 하는 경우 사용

    doReturn(3).when(p).add(anyInt(), anyInt());

doNothing(): Mock 객체가 아무 것도 반환하지 않는 경우 사용(void)

@Test
public void example(){
    Person p = mock(Person.class);
    doNothing().when(p).setAge(anyInt());
    p.setAge(20);
    verify(p).setAge(anyInt());
}

doThrow(): Mock 객체가 예외를 발생시키는 경우 사용

@Test(expected = IllegalArgumentException.class)
public void example(){
    Person p = mock(Person.class);
    doThrow(new IllegalArgumentException()).when(p).setName(eq("JDM"));
    String name = "JDM";
    p.setName(name);
}

Stub의 doReturn, thenReturn의 차이

Mockito를 사용하다보면 코드들에 doThrow, doReturn등의 형식이 아닌

thenReturn(), thenThrow()등의 형식도 볼 수 있을 것이다. 둘의 차이점은 다음과 같다.

doReturn

  • 실제로 메서드를 호출하고 리턴값을 임의로 정의할 수 있다.
  • 메서드를 실제로 수행하여 메서드 작업이 오래걸릴 경우 끝날 때 까지 기다려야한다.
  • 실제 메서드를 호출하기 때문에 대상 메서드에 문제점이 있을 경우 발견할 수 있다.
doReturn(6).when(cal).add(2, 4);

thenReturn

  • 메서드를 실제로 호출하지 않으며 리턴값을 임의로 정의할 수 있다.
  • 실제 메서드를 호출하지 않기 때문에 대상 메서드에 문제점이 있어도 알 수 없다.
when(cal.add(2, 4)).thenReturn(6); 

Mock 객체 확인

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

  • 특정 메소드가 특정 매개변수로 몇번 호출 되었는지,
  • 최소 한번은 호출 됐는지,
  • 전혀 호출되지 않았는지
    • Verifying exact number of invocations
    • org.mockito.Mockito.verify() 메서드
    • 예제 : 멤버 서비스에서 딱 1번 notify란 메서드가 study라는 매개변수를 가지고 호출 되어야 한다
    • verify(memberService, times(1)).notify(study);
    • any()도 가능하다.
  • 어떤 순서대로 호출했는지
    • Verification in order
    • org.mockito.Mockito.inOrder(), inOrder.verify() 메서드
    • 예제 : 멤버 서비스에서 notify(study) 메소드가 notify(member) 보다 먼저 호출되어야 한다.
    • verify(memberService, times(1)).notify(study);
      verify(memberService, times(1)).notify(member);
      
      InOrder inOrder = inOrder(memberService)
      inOrder.verify(memberService).notify(study);
      inOrder.verify(memberService).notify(member);
    • 이후에 더이상 아무 액션이 일어나면 안된다 할 때는
    • verifyNoMoreInteractions(Object mocks...) 메서드
  • 특정 시간 이내에 호출됐는지
    • Verification with timeout
  • 특정 시점 이후에 아무 일도 벌어지지 않았는지
    • Finding redundant invocations

참고