Mockito는 Java에서 인기있는 Mocking framework입니다. Mockito로 객체를 mocking하여 Unit Test를 작성할 수 있습니다.
직접 Mock 객체를 만들 수 있지만 Mockito와 같은 Mocking framework을 사용하면 번거로운 코드를 작성하지 않아도 됩니다.
이 글에서는 Mockito로 어떻게 테스트 코드를 작성하는지 알아보겠습니다.
- Mocking
- Verify
- ArgumentCaptor
- Spying
- Exception 발생
의존성 설정
gradle 프로젝트에서 다음과 같이 의존성을 설정하면 JUnit과 Mockito 라이브러리를 사용할 수 있습니다.
dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.7.22'
}
Mocking
다음과 같이 mock(ClassToMock.class)
으로 Mock 객체를 만들 수 있습니다.
Mock 객체를 만들면 내부의 코드는 모두 동작하지 않고, 리턴 값은 0, false, null 등으로 대체됩니다.
@Test
public void mockingList() {
List mockList = mock(ArrayList.class);
mockList.add("apple");
assertEquals("apple", mockList.get(0));
}
위 코드를 실행해보면 null이 리턴되어 테스트가 실패합니다.
java.lang.AssertionError:
Expected :apple
Actual :null
When
when
을 사용하면 어떤 상황에서 Mock 객체가 어떤 값을 리턴하도록 만들 수 있습니다.
이것을 Stubbing이라고 합니다.
다음 코드를 해석하면 mockList
객체가 get(0)
을 호출할 때, "appple"
을 리턴하라는 것입니다.
when(mockList.get(0)).thenReturn("apple");
다음과 같이 get()
, size()
가 호출될 때 특정 값이 설정되도록 하였습니다.
@Test
public void mockingList_when() {
List mockList = mock(ArrayList.class);
when(mockList.get(0)).thenReturn("apple");
when(mockList.get(1)).thenReturn("kiwi");
when(mockList.size()).thenReturn(10);
assertEquals("apple", mockList.get(0));
assertEquals("kiwi", mockList.get(1));
assertEquals(10, mockList.size());
}
입력되는 인자와 무관하게 특정 값을 리턴하고 싶다면 anyInt()
를 사용하면 됩니다.
이 외에 anyString()
, anyLong()
등 모든 타입에 대한 메소드들이 제공됩니다.
@Test
public void mockingList_when2() {
List mockList = mock(ArrayList.class);
when(mockList.get(anyInt())).thenReturn("apple");
assertEquals("apple", mockList.get(0));
assertEquals("apple", mockList.get(1));
}
Verify
verify
를 이용하면 mock 객체에 어떤 API가 호출되었는지, 몇번 호출되었는지 확인할 수 있습니다.
다음 코드는 mockList.add("apple")
이 호출되었는지 확인합니다. 만약 호출되지 않았다면 테스트는 실패합니다.
verify(mockList).add("apple");
다음 코드는 mockList.get(0)
이 두번 호출되었는지 확인합니다. times()
의 인자에 예상되는 호출 횟수를 전달합니다.
verify(mockList, times(2)).get(0);
그래서 다음과 같이 메소드가 호출되었는지 확인할 수 있습니다.
atLeastOnce()
, atLeast()
, times()
등의 메소드를 제공해주어 몇번 호출되었는지도 확인할 수 있습니다.
@Test
public void mockingList_verify() {
List mockList = mock(ArrayList.class);
mockList.add("apple");
verify(mockList).add("apple");
mockList.get(0);
mockList.get(0);
verify(mockList, times(2)).get(0);
verify(mockList, atLeast(2)).get(0);
}
ArgumentCaptor
ArgumentCaptor는 Mock 객체에 전달된 인자를 확인하는 용도로 사용합니다.
다음 코드는 mockList.add()
의 인자를 읽어오는 코드입니다. verify에 capture()
를 전달하고 getValue()
로 어떤 값이 전달되었는지 확인할 수 있습니다.
ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);
verify(mockList).add(arg.capture());
assertEquals("apple", arg.getValue());
다음과 같이 ArgumentCaptor를 이용하여 add()
에 어떤 인자가 전달되었는지 확인할 수 있습니다.
@Test
public void captorExample() {
ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);
mockList.add("apple");
verify(mockList).add(arg.capture());
assertEquals("apple", arg.getValue());
}
Spying
Mock 객체는 내부 구현이 동작하지 않고 0, false, null 등이 리턴된다고 했는데요. Spy 객체는 실제 객체처럼 동작하고, verify로 어떤 메소드가 호출되었는지 확인할 수 있습니다. 또한, when 키워드로 일부 메소드를 stubbing할 수 있습니다.
다음과 같이 spy 객체를 만들 수 있습니다. 실행해보면 테스트는 pass되며 실제 객체처럼 동작한다는 것을 알 수 있습니다.
@Test
public void spyingList() {
List spyList = spy(ArrayList.class);
spyList.add("apple");
assertEquals("apple", spyList.get(0));
}
다음과 같이 verify
로 메소드가 호출되었는지 검증할 수 있습니다.
@Test
public void spyingList_verify() {
List spyList = spy(ArrayList.class);
spyList.add("apple");
verify(spyList).add("apple");
spyList.get(0);
verify(spyList).get(0);
}
또한, 다음과 같이 when
으로 일부 메소드를 stubbing할 수 있습니다. 하지만 아래 코드는 문제가 있습니다.
@Test
public void spyingList_when_error() {
List spyList = spy(ArrayList.class);
when(spyList.get(0)).thenReturn("apple");
when(spyList.get(1)).thenReturn("kiwi");
when(spyList.size()).thenReturn(10);
assertEquals("apple", spyList.get(0));
assertEquals("kiwi", spyList.get(1));
assertEquals(10, spyList.size());
}
위 코드를 실행하면 IndexOutOfBoundsException
예외가 발생하면서 테스트는 실패합니다.
spy 객체는 실제 객체처럼 동작하기 때문에 when
코드에서 spyList.get(0)
을 호출할 때 Item이 없어 Exception이 발생할 수 있습니다.
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:659)
at java.util.ArrayList.get(ArrayList.java:435)
이럴 때는 doReturn().when()
패턴으로 변경해주면 성공적으로 stubbing할 수 있습니다.
@Test
public void spyingList_when_ok() {
List spyList = spy(ArrayList.class);
doReturn("apple").when(spyList).get(0);
doReturn("kiwi").when(spyList).get(1);
doReturn(10).when(spyList).size();
assertEquals("apple", spyList.get(0));
assertEquals("kiwi", spyList.get(1));
assertEquals(10, spyList.size());
}
따라서, spy 객체를 stubbing할 때는 when().thenReturn()
패턴을 사용하지 말고
doReturn().when()
패턴을 사용하는 것이 좋습니다.
Exception 발생
특정 상황에서 Exception이 발생하도록 할 수도 있습니다.
when().thenThrow(ExceptionClass)
패턴을 사용하면 됩니다.
@Test
public void whenGetUserInfo_throwException() {
Contacts contactsMock= mock(Contacts.class);
when(contactsMock.getUserInfo(anyInt()))
.thenThrow(NullPointerException.class);
contactsMock.getUserInfo(0);
}
thenThrow()
에 인자로 클래스 이름을 전달하지 않고 new NullPointerException(msg)
처럼 Exception 객체를 생성하여 전달할 수도 있습니다.
@Test
public void whenGetUserInfo_throwExceptionWithMsg() {
Contacts contactsMock= mock(Contacts.class);
when(contactsMock.getUserInfo(anyInt()))
.thenThrow(new NullPointerException("Null pointer exception happened"));
contactsMock.getUserInfo(0);
}
위의 코드들을 실행하면 Exception이 발생하며 테스트는 실패합니다. Exception이 발생되는 것을 확인한 것이기 때문에 테스트는 패스되어야 합니다.
다음과 같이 NullPointerException이 발생하지 않으면 테스트는 실패하고, Exception이 발생하면 성공하도록 구현할 수 있습니다.
@Test
public void whenGetUserInfo_throwException2() {
Contacts contactsMock= mock(Contacts.class);
when(contactsMock.getUserInfo(anyInt()))
.thenThrow(NullPointerException.class);
try {
contactsMock.getUserInfo(0);
fail();
} catch (NullPointerException e) {
// pass
}
}
더 간단하고 가독성이 좋은 방법은 @Test
에 다음과 같이 예외가 발생할 것이라고 명시하는 것입니다.
만약 Exception이 발생하면 테스트는 성공하며, 그렇지 않으면 테스트는 실패합니다.
@Test(expected = NullPointerException.class)
public void whenGetUserInfo_throwException3() {
Contacts contactsMock= mock(Contacts.class);
when(contactsMock.getUserInfo(anyInt()))
.thenThrow(NullPointerException.class);
contactsMock.getUserInfo(0);
}
Void-Return Type 메소드에서 예외 발생
Void로 설정된 메소드는 다음과 같이 doThrow().when()
패턴으로 Exception이 발생하도록 구현해야 합니다.
@Test(expected = NullPointerException.class)
public void whenCallsVoidReturnTypeMethod_throwException() {
Contacts contactsMock= mock(Contacts.class);
doThrow(NullPointerException.class)
.when(contactsMock)
.addUserInfo(anyString(), anyString());
contactsMock.addUserInfo("AAA", "123-456");
}
spy 객체에서 예외 발생
다음과 같이 Spy 객체도 Mock과 동일한 방식으로 Exception을 발생시킬 수 있습니다.
@Test(expected = NullPointerException.class)
public void whenGetUserInfo_throwException4() {
Contacts contactsMock= spy(Contacts.class);
when(contactsMock.getUserInfo(anyInt()))
.thenThrow(NullPointerException.class);
contactsMock.getUserInfo(0);
}
정리
Mockito 라이브러리를 사용하여 Mock과 Spy 객체를 만들고 verify로 검증하는 방법을 알아보았습니다. 또한, when으로 stubbing하여 특정 값을 리턴하거나 예외를 발생시키도록 만드는 방법을 알아보았습니다.
Related Posts
- Java - Unsupported class file major version 61 에러
- Java - String.matches()로 문자열 패턴 확인 및 다양한 예제 소개
- Java - 문자열 공백제거 (trim, replace)
- Java - replace()와 replaceAll()의 차이점
- Java - ArrayList 초기화, 4가지 방법
- Java - 배열 정렬(Sorting) (오름차순, 내림차순)
- Java - 문자열(String)을 비교하는 방법 (==, equals, compare)
- Java - StringBuilder 사용 방법, 예제
- Java - 로그 출력, 파일 저장 방법 (Logger 라이브러리)
- Java IllegalArgumentException 의미, 발생 이유
- Java - NullPointerException 원인, 해결 방법
- Seleninum의 ConnectionFailedException: Unable to establish websocket connection 해결
- Java - compareTo(), 객체 크기 비교
- Java - BufferedWriter로 파일 쓰기
- Java - BufferedReader로 파일 읽기
- Java charAt() 함수 알아보기
- Java - BigInteger 범위, 비교, 연산, 형변환
- Java contains()로 문자(대소문자 X) 포함 확인
- Java - Set(HashSet)를 배열로 변환
- Java - 문자열 첫번째 문자, 마지막 문자 확인
- Java - 문자열 한글자씩 자르기
- Java - 문자열 단어 개수 가져오기
- Java - 1초마다 반복 실행
- Java - 배열을 Set(HashSet)로 변환
- Java - 여러 Set(HashSet) 합치기
- Java - 명령행 인자 입력 받기
- Java - 리스트 역순으로 순회, 3가지 방법
- Java - 특정 조건으로 리스트 필터링, 3가지 방법
- Java - HashMap 모든 요소들의 합계, 평균 계산
- Java - 특정 조건으로 HashMap 필터링
- Java - 싱글톤(Singleton) 패턴 구현
- Java - 숫자 왼쪽에 0으로 채우기
- Java - String 배열 초기화 방법
- Java - 정렬된 순서로 Map(HashMap) 순회
- Java - HashMap에서 key, value 가져오기