stub
메서드를 호출하면 미리 정의된 답변을 줌
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
: 미리 준비된 답변https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
몇 가지의 동작을 확인하자
import static org.mockito.Mockito.mock;
은 static으로 선언하는 것이 코드가 깔끔해진다.import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@DisplayName("Mockito JavaDoc 3.10.0 API")
public class MockitoJavaDoc {
@Test
@DisplayName("1. Let's verify some behaviour!")
void step_01_test(){
//mock 생성
List mockedList = mock(List.class);
//mock 객체 사용
mockedList.add("test1");
mockedList.clear();
//verification(확인)
verify(mockedList).add("test1");
mockedList.clear();
}
}
Stubbing은 어떻게 하지?
1) Step 1 : mock 객체 선언 (interface도 가능)
2) Step 2 : stubbing → stubbing 이후 method호 출시 일정한 값을 반환
3) Step 3 : mock은 null, primitive / primitive value 또는 비어있는 collection을 적절하게 반환 → 999번째 값을 반환해도 Exception이 아닌 null을 반환
4) Step 4 : verify로 검증 가능하다. 만약 stubbing 하지 않은 값을 호출한다면 케이스는 실패한다.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@DisplayName("Mockito JavaDoc 3.10.0 API")
public class MockitoJavaDocStep02 {
@Test
@DisplayName("2. How about some stubbing?")
void step_02_test(){
//step1
LinkedList mockedList = mock(LinkedList.class);
//step2 stubbing
when(mockedList.get(0)).thenReturn("test2");
when(mockedList.get(1)).thenThrow(new NoSuchElementException());
//step3
assertThat(mockedList.get(0)).isEqualTo("test2");
assertThatThrownBy(() -> mockedList.get(1))
.isInstanceOf(NoSuchElementException.class);
assertThat(mockedList.get(999)).isNull();
//step4
verify(mockedList).get(1);
verify(mockedList).get(999);
verify(mockedList).get(0);
//verify(mockedList).get(2);
}
}
Argument matcher 사용법
anyInt()
로 어떤 int 값이 넘어와도 "Integer" 문자열이 리턴 가능하도록 세팅argThat()
을 통해 사용자 정의 argument 검증 가능 → ArgumentMatcher<T>
를 상속anyObject()
, eq()
등도 있다.import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher;
import java.util.LinkedList;
import java.util.List;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@DisplayName("Mockito JavaDoc 3.10.0 API")
public class MockitoJavaDocStep03 {
class IsLengthGreaterThanOneArgument implements ArgumentMatcher<List> {
@Override
public boolean matches(List list) {
return String.valueOf(list.get(anyInt())).length() > 1;
}
}
@Test
@DisplayName("3. Argument matchers")
void step_03_test() {
//step1
LinkedList mockedList = mock(LinkedList.class);
//step2
when(mockedList
.get(anyInt()))
.thenReturn("Integer");
when(mockedList
.contains(argThat(new IsLengthGreaterThanOneArgument())))
.thenReturn(true);
//step3
mockedList.get(1);
mockedList.get(999);
mockedList.get(0);
mockedList.get(2);
//step4
verify(mockedList).get(1);
verify(mockedList).get(999);
verify(mockedList).get(0);
verify(mockedList).get(2);
}
}
Method 호출 횟수 계산하기
times(int)
- 지정한 횟수 만큼 호출 되었는 지 검증 → 기본 값은 times(1)never()
: 호출되지 않았는지 여부 검증atLeastOnce()
: 적어도 한번은 호출 되었는 지 검증atLeast(int)
: 적어도 지정한 횟수 만큼 호출 되었는 지 검증atMost(int)
: 최대 지정한 횟수 만큼 호출 되었는 지 검증import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.LinkedList;
import java.util.List;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.atMostOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep04 {
@Test
@DisplayName("4. Verifying exact number of invocations / at least x / never")
void step_04_test(){
//step1
LinkedList mockedList = mock(LinkedList.class);
//step2
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
//step3
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
//step4
verify(mockedList, atMostOnce()).add("once");
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");
}
}
void Method의 Stubbing 방법
방법 1. doThrow
Step 1 : mock 객체 선언 (interface도 가능)
Step 2 : stubbing → anyInt()
로 어떠한 값이 들어와도 RuntimeException
발생
Step 3 : assertThatThrownBy
로 mockedList에서 조회 시 RuntimeException Throw
발생 확인
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep05 {
@Test
@DisplayName("5. Stubbing void methods with exceptions")
void step_05_test(){
//step1
List mockedList = mock(List.class);
//step2
doThrow(new RuntimeException()).when(mockedList).get(anyInt());
//step3
assertThatThrownBy(() -> mockedList.get(1))
.isInstanceOf(RuntimeException.class);
assertThatThrownBy(() -> mockedList.get(1001))
.isInstanceOf(RuntimeException.class);
}
}
방법 2. doNothing
Step 1 : mock 객체 선언 (interface도 가능)
Step 2 : stubbing → doNothing
으로 반환값 없음으로 설정
Step 3 : method 실행
Step 4 : verify 검증
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep05 {
@Test
@DisplayName("5. Stubbing void methods with exceptions")
void step_05_2_test(){
//step1
List mockedList = mock(List.class);
//step2
doNothing().when(mockedList).clear();
//step3
mockedList.clear();
//step3
verify(mockedList).clear();
}
}
호출 순서 검증
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import java.util.List;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep06 {
@Test
@DisplayName("6. Verification in order")
void step_06_test(){
//step1
List singleMock = mock(List.class);
//step2
singleMock.add("was added first");
singleMock.add("was added second");
//step3
InOrder inOrder = inOrder(singleMock);
//step4
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
//step5
List firstMock = mock(List.class);
List secondMock = mock(List.class);
//step6
firstMock.add("was called first");
secondMock.add("was called second");
//step7
InOrder inOrderTwo = inOrder(firstMock, secondMock);
//step8
inOrderTwo.verify(firstMock).add("was called first");
inOrderTwo.verify(secondMock).add("was called second");
}
}
다수의 객체가 사용되지 않았음을 검증
verifyNoInteraction
로 객체의 사용 유무 검증 → verifyZeroInteractions
는 deprecated
됨import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.~~verifyZeroInteractions~~;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep07 {
@Test
@DisplayName("7. Making sure interaction(s) never happened on mock")
void step_07_test(){
//step1
List mockListOne = mock(List.class);
List mockListTwo = mock(List.class);
//step2
//~~verifyZeroInteractions~~(mockListOne,mockListTwo);
verifyNoInteractions(mockListOne, mockListTwo);
}
}
중복 호출 찾기 (과도한 사용은 유지 관리가 어려움)
verifyNoMoreInteractions
으로 추가 검증 실행여부 확인 → add("two")에 대한 검증은 수행되지 않음 → Fail
Success
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep08 {
@Test
@DisplayName("8. Finding redundant invocations")
void step_08_test(){
//step1
List mockList = mock(List.class);
//step2 stubbing
mockList.add("one");
mockList.add("two");
//step3
verify(mockList).add("one");
//verify(mockList).add("two");
//step4
verifyNoMoreInteractions(mockList);
}
}
빠르게 mock 생성하기 (@Mock annotation)
MockitoJUnitRunner
) / rule은 MockitoRule
**MockitoAnnotations.initMocks
은 deprecated
됨**MockitoAnnotations.openMocks(this);
//MockitoAnnotations.~~initMocks~~(this);
MockitoAnnotations.openMocks(this)
Runner 사용 → Test Class의 Person 선언부에 @Mock
Annotation 삽입when
을 통해 Person의 getName 값 stubbingimport org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class Person {
private Long id;
private String name;
public Person() {
}
public Person(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep09 {
@Mock
private Person person;
@Test
@DisplayName("9. Shorthand for mocks creation - @Mock annotation")
void step_09_test(){
//step1
MockitoAnnotations.openMocks(this);
//step2
when(person.getName()).thenReturn("psjw");
//step3
assertThat(person.getName()).contains("psjw");
//step4
verify(person).getName();
}
}
연속적으로 다른 stubbing 하기
thenReturn
으로 2회, thenThrow
1회 Stubimport org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep10 {
@Test
@DisplayName("10. Stubbing consecutive calls (iterator-style stubbing)")
void step_10_test(){
//step1
List<String> mockedList = mock(List.class);
//step2
when(mockedList.get(anyInt()))
.thenReturn("one")
.thenReturn("two")
.thenThrow(new RuntimeException());
//step3
assertThat(mockedList.get(0)).isEqualTo("one");
assertThat(mockedList.get(0)).isEqualTo("two");
assertThatThrownBy(() -> mockedList.get(0))
.isInstanceOf(RuntimeException.class);
}
}
Answer로 Callback을 Stubbing 함
thenAnswer
를 통해 Callback 함수 정의 → Lambda 식으로도 가능import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep11 {
@Test
@DisplayName("11. Stubbing with callbacks")
void step_11_test() {
//step1
List<String> mockedList = mock(List.class);
//step2
when(mockedList.get(anyInt()))
.thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
Object[] objects = invocationOnMock.getArguments();
Object mock = invocationOnMock.getMock();
return "called with arguments : " + Arrays.toString(objects);
}
});
/*
when(mockedList.get(anyInt()))
.thenAnswer(invocationOnMock -> {
Object[] objects = invocationOnMock.getArguments();
Object mock = invocationOnMock.getMock();
return "called with arguments : " + Arrays.toString(objects);
});
*/
//step3
assertThat(mockedList.get(0)).isEqualTo("called with arguments : [0]");
}
}
doReturn(), doThrow(), doAnswer(), doNothing(), doCallRealMethod() 은 when() 함수에서 호출 됨
[doReturn(Object)](https://javadoc.io/static/org.mockito/mockito-core/3.10.0/org/mockito/Mockito.html#doReturn-java.lang.Object-)
[doThrow(Throwable...)](https://javadoc.io/static/org.mockito/mockito-core/3.10.0/org/mockito/Mockito.html#doThrow-java.lang.Throwable...-)
[doThrow(Class)](https://javadoc.io/static/org.mockito/mockito-core/3.10.0/org/mockito/Mockito.html#doThrow-java.lang.Class-)
[doAnswer(Answer)](https://javadoc.io/static/org.mockito/mockito-core/3.10.0/org/mockito/Mockito.html#doAnswer-org.mockito.stubbing.Answer-)
[doNothing()](https://javadoc.io/static/org.mockito/mockito-core/3.10.0/org/mockito/Mockito.html#doNothing--)
[doCallRealMethod()](https://javadoc.io/static/org.mockito/mockito-core/3.10.0/org/mockito/Mockito.html#doCallRealMethod--)
spy 메서드를 통해 실제 객체에 대한 mock을 생성
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep13 {
@Test
@DisplayName("13. Spying on real objects")
void step_13_test() {
//step1
final List mockedList = new ArrayList();
final List spy = spy(mockedList);
//step2
//when(spy.size()).thenReturn(100);
doReturn("one").when(spy).get(0);
//step3
spy.add("one");
spy.add("two");
//step4
assertThat(spy.get(0)).isEqualTo("one");
assertThatThrownBy(() -> mockedList.get(0))
.isInstanceOf(IndexOutOfBoundsException.class);
}
}
Stub 되지 않은 Method를 호출할 때 default 값을 지정
Foo mock = mock(Foo.class, Mockito.RETURNS_SMART_NULLS);
Foo mockTwo = mock(Foo.class, new YourOwnAnswer());
RETURNS_SMART_NULLS
를 활용하여 반환값이 Null이 아니고 Name 필드는 ""으로 반환 확인import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.RETURNS_SMART_NULLS;
import static org.mockito.Mockito.mock;
class Person2 {
private Long id;
private String name;
public Person2() {
}
public Person2(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep14 {
@Test
@DisplayName("14. Changing default return values of unstubbed invocations (Since 1.7)")
void step_14_test() {
//step1
Person2 mockedP = mock(Person2.class);
assertThat(mockedP.getName()).isNull();
//step2
Person2 mockedSmartP = mock(Person2.class, RETURNS_SMART_NULLS);
assertThat(mockedSmartP.getName()).isNotNull();
assertThat(mockedSmartP.getName()).isEqualTo("");
}
}
Capturing arguments를 통한 검증
ArgumentCaptor
의 forClass(클래스)
메서드를 통해 String.class를 세팅하고, add의 input 값 capture() → "one", "two"ArgumentCaptor
의 allValues()
함수로 리스트로 반환 받음import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep15 {
@Test
@DisplayName("15. Capturing arguments for further assertions (Since 1.8.0)")
void step_15_test() {
//step1
List mockedList = mock(List.class);
//step2
mockedList.add("one");
mockedList.add("two");
//step3
ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
verify(mockedList, times(2)).add(argumentCaptor.capture());
//step4
final List<String> allValues = argumentCaptor.getAllValues();
assertThat(allValues.get(0)).isEqualTo("one");
assertThat(allValues.get(1)).isEqualTo("two");
}
}
spy를 통해 partial mock을 사용할 수 있음.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.LinkedList;
import java.util.List;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
class Person3 {
private Long id;
private String name;
public Person3() {
}
public Person3(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep16 {
@Test
@DisplayName("16. Real partial mocks (Since 1.8.0)")
void step_16_test() {
//step1
List list = spy(new LinkedList());
//step2
Person3 mock = mock(Person3.class);
//step3
when(mock.getName()).thenCallRealMethod();
}
}
mock은 stubbing 초기화가 가능함
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep17 {
@Test
@DisplayName("17. Resetting mocks (Since 1.8.0)")
void step_17_test() {
//step1
List mock = mock(List.class);
//step2
when(mock.size()).thenReturn(10);
mock.add(1);
//step3
assertThat(mock.size()).isEqualTo(10);
//step4
reset(mock);
//step5
assertThat(mock.size()).isEqualTo(0);
}
}
FAQ : https://github.com/mockito/mockito/wiki/FAQ
BDD(Behavior Driven Developmen)은 method에 기본으로 //given, //when, //then 을 주석을 사용
when
을 이용한 구문이 BDD
의 //given
, //when
, //then
주석에 맞지 않음 → stubbing은 given에 속함.given
→ mock 객체 선언 및 given을 통해 stubbingwhen
→ method 호출then
→ return 결과 확인import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@DisplayName("Mockito JavaDoc 3.10.0 API")
class MockitoJavaDocStep19 {
@Test
@DisplayName("19. Aliases for behavior driven development (Since 1.8.0)")
void step_19_test() {
//step1 : given
List<String> mockedList = mock(List.class);
given(mockedList.get(0)).willReturn("one");
//when
String value = mockedList.get(0);
//then
assertThat(value).isEqualTo("one");
then(mockedList).should(times(1)).get(0);
}
}