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 하는 것도 당연 가능하다.
1 2 3 4 5 6 7 8 9 10 | repositories { jcenter() } dependencies { … testCompile “jnuit:jnuit:+” testCompile "org.mockito:mockito-core:+” androidTestCompile "org.mockito:mockito-android:+” } |
-
JUnit 과 Mockito integration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | // 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()); } } <p></p> |
-
mockito 를 사용할 때는 org.mockito.Mockito.*; 로 static import 를 하는 것이 추천된다.
그렇지 않으면 함수 호출시마다 Mockito.mock() 과 같이 class 이름을 계속 붙여주어야 한다.
-
mock 을 만들 때는 mock(CalculatorService.class) 와 같이 mock 함수를 쓰면 된다.
@Mock annotation 을 사용해도 된다. 이를 사용할 때는 아래와 같이 MockitoRule 을 만들어야 @Mock annotation 이 붙어 있는 녀석을 자동으로. mock 으로 만든다.
1 2 | @Rule public MokitoRule mockitoRule = MockitoJUnit.rule();<p></p> |
아니면 test class 자체에 @RunWith(MokitoJUnitRunner.class) 를 마킹해줌으로 갈음할 수 있다.
(위 예제는 이 방법을 사용하였다.)
혹은 MockitoAnnotations.initMocks(testClass); 이 녀석은 test base class 에 넣어두면 좋다.
-
1 | 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 한다. )
1 2 3 4 5 6 7 | 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 도 연결할 수 있다.
1 2 3 4 5 6 7 8 | 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 할 수 있다.
1 2 3 4 5 6 7 8 | 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 된다.<p></p> |
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>
1 2 3 4 5 | Properties prop = new Properties(); Properties spyProp = spy(prop); doReturn( "42”).when(spyProp).get(" shoeSize"); String value = spyProp.get(“shoeSize”); assertEquals(“ 42 ”, value);<p></p> |
<doThrow when>
1 2 3 | 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 의 함수들이 호출된다.
1 2 3 4 5 | 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 처럼도 쓸 수 있다.
1 2 | Foo mock = mock(Foo. class ); when(mock.someMethod()).thenCallRealMethod(); |
-
Mockito 에서는 verify 를 통해서 mock method 가 불렸는지 확인할 수 있다.
이를 behavior testing 이라고 하는지, 결과를 보는 것이 아니라 해당 동작을 했는지를 확인하는 것이다.
1 | verify(calcService).add( 10.0 , 20.0 ); |
아래와 같은 syntax 로 작동한다.
verify(mock).verifyFunction
즉 위의 코드는 calcService 에 add(10.0, 20.0) 이 불렸는지를 확인해준다.
-
verify 는 mock method 가 불린 횟수도 확인 가능하다.
1 | 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 가 있고, 이 녀석을 검증하고 싶다면..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 도 검증할 수 있다.
1 2 3 | 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 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 으로 이루어진다.
1 2 3 4 5 6 7 8 | // 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 도 있다.
1 2 3 | 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 이 발생하면서 테스트를 조기 종료할 수 있다.
1 | @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 생성과 조건 걸기를 할 수 있다.
1 | Car boringStubbedCar = when(mock(Car. class ).shiftGear()).thenThrow(EngineNotStarted. class ).getMock(); |
-
2.2.x 부터는 mock object 에 대한 추가 정보를 얻을 수 있다.
1 2 3 4 5 6 7 8 9 | 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 |
댓글