https://www.tutorialspoint.com/mockito/mockito_overview.htm
http://www.vogella.com/tutorials/Mockito/article.html
https://static.javadoc.io/org.mockito/mockito-core/2.12.0/org/mockito/Mockito.html#mockito
-
Mockito 는 JUnit 위에서 동작하며 Mocking 과 Verification, Stubbing 을 도와주는 프레임워크이다. ( 이 자체가 testing 하는 framework 는 아니다!! )
Mockito 를 사용하면 Mock 을 만들어서 external dependency 를 제거할 수 있고, code 가 제대로 수행하는지 검증할 수도 있다.
-
unit testing 의 목표 중 하나는 test 가 독립적이어야 한다는 것이다.
다른 class 에 side effect 를 주면 안 된다.
이것은 real dependency 가 아닌 test replacement(test double)을 통해 할 수 있으며, test doubles 는 아래와 같이 구분될 수 있다.
1. dummy object : method 는 전혀 사용되지 않을 dummy object 가 전달된다.
2. fake object : working impl 을 가진 녀석인데 보통 간소화되어있다. 예를 들면 real db 가 아닌 memory db 를 참조해 결과를 return 한다던지
3. stub : stub 은 test 를 위해 사용되기 위한 부분 구현된 녀석이다. 보통 이 녀석은 외부 호출에 대해 아무 반응하지 않고, log 만 쌓는다던지 그런 역할만 한다.
4. mock object : dummy 구현을 가진 녀석으로, 구현 없이 output 만 내어준다.
test double 은 test 당하는 object 에 전달되어질 수 있다.
-
Mock interface 를 통해 실제 database connection, property file, server 연결 등이 필요 없게 된다. mock 이 dummy data 를 제공해주어 이것들을 대신해준다. Proxy 라고 보면 된다.
이 Mocking 과정은 reflection 을 이용해서 된다. 따라서 test 를 위한 mock class 를 생성할 필요가 없다는 것이 최대 장점 중 하나이다.
-
Mockito version 2.6.1 부터 AndroidTest 로 support 가능해졌다.
core 만으로 일반 VM 에서 test 하는 것도 당연 가능하다.
repositories { jcenter() } dependencies { … testCompile “jnuit:jnuit:+” testCompile "org.mockito:mockito-core:+” androidTestCompile "org.mockito:mockito-android:+” }
-
JUnit 과 Mockito integration
// Mock 이 될 Interface public interface CalculatorService { public double add(double input1, double input2); public double subtract(double input1, double input2); public double multiply(double input1, double input2); public double divide(double input1, double input2); } // 테스트 대상 public class MathApplication { private CalculatorService calcService; // 주입 받는다. public void setCalculatorService(CalculatorService calcService){ this.calcService = calcService; } public double add(double input1, double input2){ return calcService.add(input1, input2); } public double subtract(double input1, double input2){ return calcService.subtract(input1, input2); } public double multiply(double input1, double input2){ return calcService.multiply(input1, input2); } public double divide(double input1, double input2){ return calcService.divide(input1, input2); } } // Test 대상을 test 하기 위한 class // @RunWith attaches a runner with the test class to initialize the test data @RunWith(MockitoJUnitRunner.class) public class MathApplicationTester { //@InjectMocks annotation is used to create and inject the mock object @InjectMocks MathApplication mathApplication = new MathApplication(); //@Mock annotation is used to create the mock object to be injected @Mock CalculatorService calcService; @Test public void testAdd(){ //add the behavior of calc service to add two numbers when(calcService.add(10.0,20.0)).thenReturn(30.00); //test the add functionality Assert.assertEquals(mathApplication.add(10.0, 20.0),30.0,0); } } public class TestRunner { public static void main(String[] args) { Result result = JUnitCore.runClasses(MathApplicationTester.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } System.out.println(result.wasSuccessful()); } }
-
mockito 를 사용할 때는 org.mockito.Mockito.*; 로 static import 를 하는 것이 추천된다.
그렇지 않으면 함수 호출시마다 Mockito.mock() 과 같이 class 이름을 계속 붙여주어야 한다.
-
mock 을 만들 때는 mock(CalculatorService.class) 와 같이 mock 함수를 쓰면 된다.
@Mock annotation 을 사용해도 된다. 이를 사용할 때는 아래와 같이 MockitoRule 을 만들어야 @Mock annotation 이 붙어 있는 녀석을 자동으로. mock 으로 만든다.
@Rule public MokitoRule mockitoRule = MockitoJUnit.rule();
아니면 test class 자체에 @RunWith(MokitoJUnitRunner.class) 를 마킹해줌으로 갈음할 수 있다.
(위 예제는 이 방법을 사용하였다.)
혹은 MockitoAnnotations.initMocks(testClass); 이 녀석은 test base class 에 넣어두면 좋다.
-
when(calcService.add(10.0,20.0)).thenReturn(30.00);
아래와 같은 syntax 로 작동한다.
when(mock 에 대한 operation(behavior)).thenReturn(returnValue);
위와 같이 코드를 작성하면 mock 에 대해 정의한 operation(behavior) 이 수행되면 thenReturn 에 정의한 return value 가 전달된다. 이를 behavior recording 이라고 부른다.
calcService 를 mocking 한 것이다.
mock 에서 return 이 있는 것들은 기본적으로 null, primitive wrapping, empty collection 이 return 된다.
-
when 문을 통해 behavior 를 추가할 때 특정값이 아닌 특정 형태를 받고 싶을 수 있다.
이럴 때는 anyInt(), anyString(), isNull(), 또는 isA(SpecificClass.class) 와 같은 것을 쓸 수 있다.
( 참고로 isA 는 null 을 return 하기 떄문에 kotlin 에서 nonnull 을 인자로 받는 경우 변형된 형태로 써야 한다. )
또한 specific 한 matching 을 쓰고싶다면 argThat(matcherObj) 혹은 argThat(predicate) 등을 사용할 수도 있다.
( argThat 도 마찬가지로 null return 한다. )
Comparable<Integer> c = mock(Comparable.class); when(c.compareTo(anyInt())).thenReturn(-1); assertEquals(-1, c.compareTo(9)); Comparable<TestClass> c2 = mock(ComparableClass.class) when(c2.comapreTo(isA(TestClass.class))).thenReturn(0); assertEquals(0, c.compareTo(new TestClass()));
-
when 문은 thenReturn 대신 thenThrow 도 연결할 수 있다.
Properties prop = mock(Properties.class); when(prop.get(“Anddroid”)).thenThrow(new IllegalArgumentException(…)); try{ prop.get(“Anddroid”); fail(‘Anddroid is misspelled”); }catch(IllegalArgumentException ex){ // good }
-
아래와 같이 똑같은 호출에도 다른 결과를 return 할 수 있다.
when(mock.someMethod(“some arg”)) .thenThrow(new RuntimeException()) .thenReturn(“foo”) // 첫번째 call 은 exception 이 던져지고, 두번째 call 부터는 foo 가 return 된다. when(mock.someMethod(“some arg”)) .thenReturn(“one”, “two”, “three”); // 첫번째 call 은 one, 두번째 call 은 two, 세번째 call 부터는 three 가 return 된다.
chaining 없이 다시 똑같은 코드를 쓰면서 return 값만 다르게 하거나 하면, overriding 이 된다. 추가가 아니라.
따라서 위의 코드를 수행하면 마지막 thenReturn 이 앞의 thenThrow, thenReturn 을 덮어쓴다.
-
when thenReturn 과 when thenThrow 와 비슷한 doReturn when 과 doThrow when 이 있다.
when thenXXX 은 mock 과 작업할 때 사용하고, doXXX when 은 spy 와 함께 사용할 때 사용한다.
spy 는 나중에 나오지만, mock 이 아닌 진짜 object 를 상대로 api 호출하도록 할 때 사용되는 녀석이다.
do 시리즈를 사용하는 또 다른 이유는 compiler 가 void method 를 bracket 안에 사용하는 것을 좋아하지 않기 때문이다.
그래서 void method call 일 때는 do 시리즈를 사용하는 것도 추천된다.
<doReturn when>
Properties prop = new Properties(); Properties spyProp = spy(prop); doReturn("42”).when(spyProp).get("shoeSize"); String value = spyProp.get(“shoeSize”); assertEquals(“42”, value);
<doThrow when>
CalcService calcService = new CalcService(); spyCalcService = spy(calcService); doThrow(new RuntimeException(“divide operation not implemented”)).when(spyCalcService).divide(10.0, 20.0);
-
@Spy 또는 spy() 는 mock 이 아닌 real object 를 wrapping 한다.
그래서 추가로 when 문 등이 정의되지 않은 녀석들은 실제 object 의 함수들이 호출된다.
List<String> list = new LinkedList<>(); List<String> spy = spy(list); // when(spy.get(0)).thenReturn(“foo”); 위에서 봤지만 이 경우는 stubbing 되는게 아니라 real method 가 호출된다. doReturn(“foo”).when(spy).get(0); assertEquals(“foo”, spy.get(0));
-
spy 를 통해 partial mock 을 만들수도 있지만, 1.8.0 부터는 mock 을 partial mock 처럼도 쓸 수 있다.
Foo mock = mock(Foo.class); when(mock.someMethod()).thenCallRealMethod();
-
Mockito 에서는 verify 를 통해서 mock method 가 불렸는지 확인할 수 있다.
이를 behavior testing 이라고 하는지, 결과를 보는 것이 아니라 해당 동작을 했는지를 확인하는 것이다.
verify(calcService).add(10.0, 20.0);
아래와 같은 syntax 로 작동한다.
verify(mock).verifyFunction
즉 위의 코드는 calcService 에 add(10.0, 20.0) 이 불렸는지를 확인해준다.
-
verify 는 mock method 가 불린 횟수도 확인 가능하다.
vefify(calcService, times(1)).add(10.0, 20.0);
위 코드는 calcService.add(10.0, 20.0) 은 한번만 불렸어야 함을 verify 한다.
times(int times) 외에도 다른 함수들이 들어갈 수 있다.
never()
atLeast(int min)
atLeastOnce
atMost(int max)
-
verifyNoMoreInteractions(mockObj) 를 해주면, 이전에 verify 로 등록해준 녀석들 외 다른 녀석들은 불렸으면 안 된다는 것을 얘기한다.
verifyZeroInteractions(mock1, mock2, … ) 는 해당 mock 에는 아무것도 호출되지 않았어야 함을 명시한다.
-
Annotation 들을 사용해서 세팅 과정을 쉽게 할 수 있다.
아래와 같은 class 가 있고, 이 녀석을 검증하고 싶다면..
public class ArticleManager { private User user; private ArticleDatabase database; public ArticleManager(User user, ArticleDatabase database) { super(); this.user = user; this.database = database; } public void initialize() { database.addListener(new ArticleListener()); } } @RunWith(MockitoJUnitRunner.class) // annotation 들 처리를 위해 써줘야 함 public class ArticleManagerTest { @Mock ArticleCalculator calculator; // mock 으로 생성 @Mock ArticleDatabase database; @Mock User user; @Spy private UserProvider userProvider = new ConsumerUserProvider(); // spy 로 생성 @InjectMocks private ArticleManager manager; // manager 에게 mock 중에 전달할 수 있는 것들을 전달 @Test public void shouldDoSomething() { // calls addListener with an instance of ArticleListener manager.initialize(); // validate that addListener was called verify(database).addListener(any(ArticleListener.class)); } }
@InjectMocks 는 constructor injection, setter injection, property injection 순서로 작동한다.
만약 위의 예제에서 manager 가 constructor 에서 User 만 받고 있고, setter 로 database 를 받는 구조로 설계되어 있었다면, User 만 inject 된다.
-
method call 의 order 도 검증할 수 있다.
InOrder inOrder = inOrder(calcService) // mock 이 들어간다. inOrder.verify(calcService).add(20.0, 10.0); inOrder.verify(calcService).subtract(20.0, 10.0);
-
ArgumentCaptor 를 사용하면 verification 단계에서 argument 로 사용된 녀석들을 capture 할 수 있다.
public class MockitoTests { @Rule public MockitoRule rule = MockitoJUnit.rule(); @Captor private ArgumentCaptor<List<String>> captor; // annotation 대신 captor = ArgumentCaptor.forClass(List.class); 와 같이 쓸 수도 있다. @Test public final void shouldContainCertainListItem() { List<String> asList = Arrays.asList("someElement_test", "someElement"); final List<String> mockedList = mock(List.class); mockedList.addAll(asList); verify(mockedList).addAll(captor.capture()); // captor 에 addAll 로 불린 녀석들이 capture 된다. final List<String> capturedArgument = captor.getValue(); assertTrue(capturedArgument.contains("someElement")); } }
-
Answer interface 를 사용하면, thenReturn 으로 단조로운 값만 return 하던 것을 벗어나
thenAnswer 를 통해 복잡한 계산 결과를 return 할 수 있다.
when(calcService.add(20.0,10.0)).thenAnswer(new Answer<Double>(){ @Override public Double answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); Object mock = invocation.getMock(); // do something with args & mock return 30.0; } } doAnswer(returnsFirstArg()).when(list).add(anyString()); // returnsFirstArg() 는 이미 구현된 Answer 객체 return when(list.add(anyString())).thenAnswer(returnsFirstArg()); when(list.add(anyString())).then(returnsFirstArg());
-
mock 을 reuse 하려면 reset(calcService);
-
Behavior driven development 는 given, when, then format 으로 이루어진다.
// given given(calcService.add(20.0, 10.0)).willReturn(30.0); // when double result = calcService.add(20.0, 10.0); // then Assert.assertEquals(result, 30.0, 0);
-
timeout option 도 있다.
verify(calcService, timeout(100)).add(20.0, 10.0); verify(calcService, timeout(100).times(2)).subtract(20.0, 10.0); // 2번 subtract 가 불렸어야 하며, 100ms 안에 끝났어야 한다. verify(calcService, new Timeout(100, yourOwnVerificationMode)).subtract(20.0, 10.0);
-
원래 final class 들은 Mocking 이 안 되었는데, v2 부터 가능해졌다.
단, 이는 기본적으로 되는 것이 아니라서 org.mockito.plugins.MockMaker 파일을 src/test/resources/mockito-extensions/ 혹은 src/mockito-extensions/ 폴더에 만들어야 한다.
그리고 파일 내용물은 "mock-maker-inline” (따옴표 없이) 이여야 한다.
-
MockitoRule 을 만들때 Strict stub 옵션을 주면 실패할 경우 exception 이 발생하면서 테스트를 조기 종료할 수 있다.
@Rule public MockitoRule rule = MokitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
-
Mockito 는 static method 를 mock 하지 못한다.
이 때 사용할 수 있는 것이 Powermock 이다. PowerMock 은 PowerMockito 라는 class 를 제공한다.
Powermock 이 별로라면 static method 를 wrapping 하는 class 를 만드는 것도 추천된다.
-
1.9.0 부터 one-liner stub 이 가능하다.
아래와 같이 한줄로 mock 생성과 조건 걸기를 할 수 있다.
Car boringStubbedCar = when(mock(Car.class).shiftGear()).thenThrow(EngineNotStarted.class).getMock();
-
2.2.x 부터는 mock object 에 대한 추가 정보를 얻을 수 있다.
Mokito.mockingDetails(someObject).isMock(); Mokito.mockingDetails(someObject).isSpy(); MockingDetails details = mockingDetails(mock); details.getMockCreationSettings().getTypeToMock(); details.getMockCreationSettings().getDefaultAnswer(); details.getInteractions(); details.getStubbings(); details.printInvocations();
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
[android] AutoFill Service 란 녀석이 나타났다. (0) | 2018.12.09 |
---|---|
[android] Robolectric tutorial (0) | 2018.12.08 |
[android] Dagger2 tutorial part2 (0) | 2018.12.05 |
[android] Dagger2 Tutorial (0) | 2018.12.04 |
[android] Dagger2 for Android Beginners (0) | 2018.12.03 |
댓글