본문 바로가기

[Android] Unit Test - Mockito란?

Unit Test - Mockito란?

Mockito란?

  • Unit Test를 위한 Java Mocking Framework
  • 유닛 테스트에서 가짜 객체(Mock)를 지원해주는 Framework
  • 구성
    • Mock 객체 생성 - Mockito.mock() 메서드 (Static 메서드로 클래스명 생략가능)
    • Mock 객체 동작 지정 - Stubbing이라고 하며 when().thenReturn() 방식
    • Mock 객체 동작 수행확인 - verify()
  • 공식홈페이지에서 자세히 확인가능 - [보러가기]

 

build.gradle 설정

2020.06.28 기준 최신버전 '1.9.5' - [최신버전 보러가기]

dependencies {
    ...
    testImplementation group: 'org.mockito', name: 'mockito-all', version: '1.9.5'
    
}

 

Mock 객체 생성하기

mock() & @Mock & @InjectMocks 방법으로 Mock객체 생성 

  • @Mock
    • mock 객체를 만들어서 반환
  • @InjectMocs
    • @Mock이나 @Spy 객체를 자신의 멤버 클래스와 일치하면 주입시켜서 mock객체 생성
// create mock Object
@Mock
Person person_annotation;			// @Mock Annotation
Person person_method = mock(Person.class);	// mock() Method


// using mock Object
person_annotation.setName("홍길동");
person_annotation.setAge(25);


// 검증(Verify) - Success
verfiy(person_annotation).setName("홍길동");	// setName("홍길동") 호출되었는지 검증
verfiy(person_annotation).setAge(25);		// setAge(25) 호출되었는지 검증

// 검증(Verify) - Fail
verfiy(person_annotation).setAge(30);		// setAge(30)은 호출한 적 없으므로 Fail

 

Stub

when() 메서드는 지정한 메서드를 호출할 경우 특정 값을 반환하도록 설정하는 메서드

// create Mock Object
@Mock
Person person;

// Stubbing
when(person.getName()).thenReturn("홍길동");	// getName() 호출 시 "홍길동" 반환하도록 설정
when(person.getAge()).thenReturn(25);		// getAge() 호출 시 25 반환하도록 설정
when(person.getAdderss()).thenThrow(new RuntimeException());	// getAdderss() 호출 시 RuntimeException 예외 설정

System.out.println(person.getName());		// "홍길동"
System.out.println(person.getAge());		// 25
System.out.println(person.getAdderss());	// RuntimeException()

 

연속적인 Stubbing

when(mock.someMethod("some arg"))	// 호출 대상 메서드
     .thenThrow(new RuntimeException())	// 첫번째 반환 값 설정
     .thenReturn("Test");		// 두번째 반환 값 설정


// 첫번째 호출 : throws runtime exception 발생
mock.someMethod("some arg");


// 두번째 호출 : "Test" 반환
System.out.println(mock.someMethod("some arg"));


// 호출한 순서대로 결과값을 Return : One, Two, Three
when(mock.someMethod("some arg"))
    .thenReturn("One", "Two", "Three");

 

 

doThrow

doThrow() 메서드를 사용하면 해당 구문 호출 시 예외를 발생하도록 설정이 가능

@Test
public void mockTest() {
    // mock() 메서드로 mock 객체 생성
    Person person = mock(Person.class);

    // doThrow() 설정, person.setAge(28) 호출 시 RuntimeException 예외 발생
    doThrow(new RuntimeException())
    	.when(person)		// 협력객체(대상) 설정
        .setAge(eq(28));	// eq는 정확한 값을 의미, eq(28)은 정확한 28을 의미

    person.setAge(30);	// success
    person.setAge(25);	// success
    person.setAge(27);	// success
    person.setAge(28);	// fail, 예외(RuntimeException) 발생
}

doThrow() - 발생시킬 예외 설정

when() - 협력객체(대상) 설정

setAge(eq(28)) - 예외 발생시킬 메서드 설정, 해당 메서드에 28값을 선언 시 예외발생

 

verify

verify()는 해당 구문이 호출 되었는지를 검증하는 메서드

단순하게 해당 구문 호출 확인뿐만 아니라 호출 횟수 / 타임아웃 시간까지 지정해서 검증이 가능한 메서드

@Test
public void mockTest() {
    Person person = mock(Person.class);
    person.setName("홍길동");

    person.setAge(20);
    person.setAge(21);

    person.setAddress("서울");
    person.setAddress("경기도");
    person.setAddress("인천");

    // times() - n번 호출했는지 체크, times(1)은 Default값
    verfiy(person, times(1)).setName(any(String.class));
    verfiy(person).setName(any(String.class));

    // 2번, 3번횟수도 검증가능
    verify(person. times(2)).setAge(anyInt());
    verfiy(person, times(3)).setAddress(any(String.class));

    // never() - 한번도 호출하지 않았는지 체크, ::= times(0) 의미
    verify(person, never()).getName();				// Success, getName() 0번 호출 확인
    verify(person, never()).setName(eq("홍길동"));		// Success, eq()는 정확하게 해당값을 의미
    verify(person, never()).setName(eq("최루피"));		// Fail, setName("최루피") 호출했으므로 Fail

    // 적어도 한번, 적어도 x번, 최대 x번
    verify(person, atLeastOnce()).setName(any(String.class));	// Success, 적어도 한번
    verify(person, atLeast(2)).setName(any(String.class));	// Fail, 적어도 2번
    verify(person, atMost(2)).setName(any(String.class));	// Success, 최대 2번


    // 지정된 시간(Millis) 안으로 메소드가 종료 했는지?
    verify(person, timeOut(100)).setName(any(String.class));	// Success

    // 지정된 시간(Millis) 안으로 1번 이상 메서드 호출 했는지?
    verify(person, timeOut(100).atLeast(1)).setName(any(String.class));	// Success
}

 

아무일이 일어나지 않는 mock 검증

List mock_one = mock(List.class);
List mock_two = mock(List.class);
List mock_three = mock(List.class);

mock_one.add("One");

// mock_one에 대한 호출 메서드 검증
verify(mock_one).add("One");
verify(mock_one, never()).add("Two");

// 나머지 Mock들이 아무 일도 발생하지 않았는지 검증
verifyZeroInteractions(mock_two, mock_three);

 

Mockito 메서드 정리

  • mock() - Mock 객체(가짜 객체) 생성 메서드
  • when() - 협력객체 메서드 반환 값을 지정해주는 역할 stub)
  • verify() - Stub 내부 협력객체 메서드가 호출 되었는지 여부 확인
  • times() - 지정한 횟수 만큼 협력 객체 메서드가 호출 되었는지 확인
  • never() - 한번도 호출되지 않았는지 여부 검증
  • atLeastOnce() - 최소 1번은 호출되었는지 확인
  • atLeast() - 최소 지정한 횟수만큼 호출되었는지 확인
  • atMost() - 최대 지정한 횟수만큼 호출되었는지 확인
  • clear() - Stub 초기화
  • timeOut() - 지정된 시간안에 호출되었는지 확인

 

Annotation 정리

  • @Test
        : 테스트 대상 메서드임을 의미, @Before가 완료되면 실제 코드 테스트를 진행
  • @Before
        : @Test 시작하기 전에 호출 되어서 사전작업을 수행, @Test 시작 전 항상 호출
  • @After
        : @Test가 종료되면 호출, 메모리에서 resource를 release 작업, @Before/@After는 @Test 실행전, 후에 실행 
  • @Rule
        : 해당 Test 클래스에서 사용하게 될 ActvitiyTestRule과 ServiceTestRule에 대해서 정의
  • @BeforeClass / @AfterClass
        : public static method로 정의해야 하며, @Before와 @After와 달리 테스트 클래스에서 딱 한번씩만 수
         @Before / @After는 각각의 @Test 실행 전, 후마다 실행된다는 점이 다름
  • @Test(timeout=2000)
        : @Test 룰에 대한 Timeout 지정, 설정한 timeout 시간 내에 테스트가 완료하지 않으면 Fail (Milliseconds 단위)

 

Test Flow