Mockito 란?
- 자바에서 단위 테스트를 위한 Mocking framework
- tastes really good(?!)
- (개인적으론) 간편하게 사용할 수 있어서 테스트코드 작성할 때 즐겨 사용하는 편입니다.
1. 의존성 추가
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>
- 버전에 대해
- mockito 의 버전은 1.x, 2.x, 3.x 로 총 3개가 있습니다. 공식 wiki 에 따르면 1과 2는 맞지 않는(Incompatible) 것도 있다고 합니다. 2와 3은 API 로는 변경된 것이 없으나, Java8 에 맞춰졌다고 합니다.
2. 사용 방법
2-1. verify
// mock 등의 코드를 깔끔하게 하기 위해 static import
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
public class MockitoTest {
@Test
public void verify_테스트() {
//mock 객체 생성
List mockedList = mock(List.class);
mockedList.add("one");
mockedList.clear();
// 검증
verify(mockedList).clear();
verify(mockedList).add("one");
verify(mockedList).add("two"); // 검증 실패 (호출된 적이 없기 때문에)
}
}
- 생성되면 mock 은 모든 상호작용을 기억합니다. 어떤 메소드가 실행되었는지
verify
로 검증할 수 있으며, 검증에는 순서를 따지지 않는 것을 확인할 수 있습니다.
2-2. stubbing
public void stubbing_테스트() {
LinkedList mockedList = mock(LinkedList.class);
// stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException("not bound index"));
// 첫번째 first 호출
System.out.println(mockedList.get(0)); // first
// stub 해주지 않았으므로 null 호출
System.out.println(mockedList.get(999)); // null
// runtimeException 발생
System.out.println(mockedList.get(1)); // java.lang.RuntimeException: not bound index
}
stub 라는 단어는 한글로 해석하면
그루터기
라는 뜻인데, 모르겠싶어 관련내용을 wiki 에서 찾아보면 Test Stub 이 있는데요. 내용을 보면- Test stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.
여기서 나오는 canned answer 이라는 건
정해진 답
으로 stubbing 이란 정해진 응답을 주도록 하는 것이라 볼 수 있겠습니다.(자꼬치기?)
2-3. Argument matchers
@Test
public void argument_matchers_테스트() {
LinkedList mockedList = mock(LinkedList.class);
when(mockedList.get(anyInt())).thenReturn("element");
System.out.println(mockedList.get(999));
}
- stubbing 을 할 때 입력 파라미터 인자를 유연하게 하고 싶을 때 사용합니다.
- argument matchers 의 anyX() 메소드들은 테스트를 편하게 도와주긴 하지만, 경함상 Int 파라미터가 필요한 자리에 any() 를 쓰면 에러가 났던 걸로 기억합니다. (제네릭인줄 알았는데 말이죠.)
- arugment matcher를 사용하려면 모든 파라미터는 matcher 로 전달되어야 합니다. 즉, String 문자열을 넣을 땐
eq()
로 감싸주어야 합니다.
when(someMethod.call(anyInt(), "url")).thenReturn("OK"); // error
when(someMethod.call(anyInt(), eq("url"))).thenReturn("OK"); // 정상 동작
2-4. 호출 여부에 대한 검증
@Test
public void times_검증테스트() {
LinkedList mockedList = mock(LinkedList.class);
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
// 지정한 횟수만큼 호출되었는지 검사
verify(mockedList, times(1)).add("once");
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
// never() == times(0)
verify(mockedList, never()).add("never");
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("six times"); // times(0) 검증 실패
verify(mockedList, atMost(5)).add("three times");
}
- 메소드 호출 횟수와 호출 여부를 확인할 수 있습니다. times(1) 의 경우는 default 이므로 생략 가능합니다.
2-5. void method with exception
@Test
public void void_exception_테스트() {
LinkedList mockedList = mock(LinkedList.class);
doThrow(new RuntimeException()).when(mockedList).clear();
mockedList.clear(); // runtimeException
}
- void 메소드에서 exception 을 던질 때 사용합니다.
2-6. 순서 검증하기
@Test
public void 순서검증하기_테스트() {
List firstMock = mock(List.class);
List secondMock = mock(List.class);
firstMock.add("first call");
secondMock.add("second call");
InOrder inOrder = inOrder(firstMock, secondMock);
inOrder.verify(firstMock).add("first call");
inOrder.verify(secondMock).add("second call");
}
- mock 들이 순서대로 실행되는지 검증하는 방식입니다.
- inOrder 에 들어가는 순서는 상관없네요. verify 를 통해 순서대로 실행되는지 확인할 수 있습니다.
2-7. 연속적인 콜 stubbing
@Test
public void 연속적인_콜_stubbing() {
MockingExample mockingExample = mock(MockingExample.class);
when(mockingExample.someMethod(eq("some arg"))).thenThrow(new RuntimeException()).thenReturn("foo");
// 첫번째 호출 runtime Exception
mockingExample.someMethod("some arg");
// 'foo' 호출
System.out.println(mockingExample.someMethod("some arg"));
// 이후 모든 호출은 마지막 stubbing 인 'foo' 호출
System.out.println(mockingExample.someMethod("some arg"));
// 다음과 같이 간략화 가능
when(mockingExample.someMethod(eq("some arg"))).thenReturn("one", "two", "three");
}
- 해당 메소드를 호출할 때마다 다른 return 값을 보여주기 위해 사용합니다.
- 이런 경우는 거의 없다고 하지만, 알아두는 것도 나쁘지 않을 것 같네요.
2-8. 실제 객체 감시하기
@Test
public void spy_테스트() {
List list = new LinkedList();
List spy = spy(list);
// 특정 메소드 stub
when(spy.size()).thenReturn(100);
// real method 호출
spy.add("one");
spy.add("two");
System.out.println(spy.get(0));
// stub 된 size() 가 호출
System.out.println(spy.size());
verify(spy).add("one");
verify(spy).add("two");
}
- real 객체에 대해 감시할 수 있다고 합니다. method 가 stub 되지 않았을 경우 실제 method 가 호출이 됩니다. 위의 경우에서는 size() 에 대해서만 stub 을 했습니다. 자주 사용하지 않아서 그런지 알쏭달쏭 합니다.
- "partial mocking" 이라는 개념과 연관되어 있다고 합니다.
List list = new LinkedList();
List spy = spy(list);
// spy.get(0)은 list가 아직 비어있으므로 IndexOutOfBoundsException
when(spy.get(0)).thenReturn("foo");
// doReturn을 이용해 stubbing
doReturn("foo").when(spy).get(0);
- 부분만 mocking 한다는 건 이런 문제점이 발생할 수 있기에 잘 써야 할 것 같네요.
2-9. stubbing 되지 않은 메소드에 기본 리턴값 설정
@Test
public void stubbing_기본값_설정_테스트() {
MockingExample mock = mock(MockingExample.class, Mockito.RETURNS_SMART_NULLS);
System.out.println(mock.someMethod("test")); // ""
}
- stub 되지 않은 method 를 호출할 때 리턴되는 값을 정해줄 수 있습니다.
- 좋은 테스트를 위해 꼭 필요한 기능은 아니지만, 레거시 시스템에는 유용하게 사용될 수 있다고 합니다..만, 어디에 사용할지 딱 하고 느낌이 오진 않네요.
2-10. Aliases for Behavior Driven Development
public void shouldBuyBread() throws Exception {
//given
given(seller.askForBread()).willReturn(new Bread());
//when
Goods goods = shop.buyBread();
//then
then(goods).should().containBread();
}
- BDD 스타일의 테스트 작성 방법은 테스트 method 에 기본으로 //given, //when, //then 이라고 주석을 달아주는 것이라 합니다. (저도 모르게 BDD 방식을 사용해 테스트 코드를 작성하고 있었네요.)
- BDDMockito.given(Object) 를 제공해 BDD 스타일의 테스트를 만들 수 있다고 합니다.
Reference
'개발 관련 > spring' 카테고리의 다른 글
HandlerMethodArgumentResolver 사용 (0) | 2022.07.14 |
---|---|
JPA 어노테이션 (0) | 2022.07.14 |
JPA 락에 대해 (0) | 2022.07.14 |
SpringBoot Actuator (0) | 2022.07.14 |
WireMock 사용 (0) | 2022.07.14 |