|
| 1 | +# Mockito |
| 2 | + |
| 3 | +:::{seealso} |
| 4 | +- Read the [guide to testing in IBEX](An-Introduction-to-Unit-Testing) before reading this guide. |
| 5 | +- For more detailed information on Mockito, see |
| 6 | +[the Mockito homepage](https://site.mockito.org/) and the |
| 7 | +[Mockito documentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html). |
| 8 | +::: |
| 9 | + |
| 10 | +## Test Doubles |
| 11 | + |
| 12 | +Test doubles are objects that stand in for a real object, for the purposes of unit testing. Terminology varies but |
| 13 | +there are four usual types that are described: |
| 14 | + |
| 15 | +* **Dummy** - an object that is passed around but not directly used by the method under test |
| 16 | +* **Fake** - a working implementation of a class with a simplified internal implementation, for example an in memory |
| 17 | +database where the production implementation uses a persistent database |
| 18 | +* **Stub** - an object that provides a canned answer to a method call |
| 19 | +* **Mock** - fake objects which know about which method calls they receive |
| 20 | + |
| 21 | +See [this article](http://martinfowler.com/articles/mocksArentStubs.html) for more information. Mockito mostly helps |
| 22 | +with Stub and Mock doubles. |
| 23 | + |
| 24 | +## Verifying Interactions |
| 25 | + |
| 26 | +To create a mock object using Mockito, in a way which is type-safe and works with generics, use the `@Mock` annotation: |
| 27 | + |
| 28 | +```java |
| 29 | +import org.mockito.Mock; |
| 30 | +import org.junit.Test; |
| 31 | +import org.junit.runner.RunWith; |
| 32 | +import org.mockito.junit.MockitoJUnitRunner; |
| 33 | +import static org.mockito.Mockito.verify; |
| 34 | + |
| 35 | +import java.util.List; |
| 36 | + |
| 37 | +@RunWith(MockitoJUnitRunner.StrictStubs.class) |
| 38 | +public class SomeTest { |
| 39 | + @Mock private List<String> mockedList; |
| 40 | + |
| 41 | + @Test |
| 42 | + public void myUnitTest() { |
| 43 | + // using mock object - it does not throw any "unexpected interaction" exception |
| 44 | + mockedList.add("one"); |
| 45 | + mockedList.clear(); |
| 46 | + |
| 47 | + // selective, explicit, highly readable verification |
| 48 | + verify(mockedList).add("one"); |
| 49 | + verify(mockedList).clear(); |
| 50 | + } |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +:::{important} |
| 55 | +The test class must be annotated with `@RunWith(MockitoJUnitRunner.StrictStubs.class)` - otherwise the `@Mock` |
| 56 | +annotation will not be processed, and the mock object will be `null` during the test. |
| 57 | +::: |
| 58 | + |
| 59 | +In the above example, the generic `List<String>` interface is mocked, and has some method calls made on it. |
| 60 | +The verify calls replace the usual assert calls in this unit test, and check the method calls were made. |
| 61 | + |
| 62 | +For non-generic classes, it is possible to use an older syntax to create the mock inline: |
| 63 | + |
| 64 | +```java |
| 65 | +SomeClass mockedList = mock(SomeClass.class); |
| 66 | +``` |
| 67 | + |
| 68 | +:::{seealso} |
| 69 | +The [Mockito Mock documentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mock.html) |
| 70 | +contains further details about how to construct mocks for more advanced use-cases. |
| 71 | +::: |
| 72 | + |
| 73 | +## Stubbing Method Calls |
| 74 | + |
| 75 | +```java |
| 76 | +@Mock private LinkedList<String> mockedList; |
| 77 | + |
| 78 | +@Test |
| 79 | +public void myUnitTest() { |
| 80 | + // stubbing appears before the actual execution |
| 81 | + when(mockedList.get(0)).thenReturn("first"); |
| 82 | + |
| 83 | + // the following prints "first“ |
| 84 | + System.out.println(mockedList.get(0)); |
| 85 | + |
| 86 | + // the following prints "null" because get(999) was not stubbed |
| 87 | + System.out.println(mockedList.get(999)); |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +This time the concrete class `LinkedList` is mocked instead of an interface. |
| 92 | +The mocked object returns what is asked of it when the method call is made with identical arguments. |
| 93 | + |
| 94 | +:::{seealso} |
| 95 | +The [Mockito documentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html) |
| 96 | +contains detailed documentation about how Mockito can be used to mock method calls for various cases. |
| 97 | +::: |
| 98 | + |
| 99 | +## Times Method is Called |
| 100 | + |
| 101 | +Mockito has [several options](https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/verification/VerificationMode.html) |
| 102 | +for checking how many times a particular method is called: |
| 103 | +* `atLeast(int minNumber)` at least this many times |
| 104 | +* `atLeastOnce()` at least once |
| 105 | +* `atMost(int maxNumber)` at most this many times |
| 106 | +* `never()` same as `times(0)` |
| 107 | +* `times(int number)` exactly this number of times |
| 108 | + |
| 109 | +The default is `times(1)`. |
| 110 | + |
| 111 | +## Any Methods |
| 112 | + |
| 113 | +When verifying method calls, if the value of an argument is not important, Mockito allows you to check that any object |
| 114 | +of a specific type was used as an argument instead. |
| 115 | + |
| 116 | +```java |
| 117 | +// The initialisable observer has its update method called once |
| 118 | +verify(mockObserver, times(1)).update(value, any(Exception.class), anyBoolean()); |
| 119 | +``` |
| 120 | + |
| 121 | +For common types methods such as `anyString()` are available, otherwise `any(Object.class)` can be used. A null |
| 122 | +object will also be matched by using any. |
| 123 | +See the [Mockito `ArgumentMatchers`](https://site.mockito.org/javadoc/current/org/mockito/ArgumentMatchers.html) |
| 124 | +documentation for more details. |
| 125 | + |
| 126 | +## Capturing Values on Method Calls |
| 127 | + |
| 128 | +If you want to capture the object called in a method, perhaps to check some value, then a captor can be used. |
| 129 | +See the code below for an example of how to do this. |
| 130 | + |
| 131 | +```java |
| 132 | +@Captor private ArgumentCaptor<Exception> exceptionCaptor; |
| 133 | +@Mock private InitialisableObserver<String> mockObserver; |
| 134 | +@Mock private Converter<Integer, String> mockConverter; |
| 135 | + |
| 136 | +@Test |
| 137 | +public void test_ConvertingObservable_with_conversion_exception() throws ConversionException { |
| 138 | + //Arrange |
| 139 | + // initObservable is what our ConvertingObservable looks at, and testObservable we can call set methods on |
| 140 | + TestableObservable<Integer> testObservable = new TestableObservable<>(); |
| 141 | + InitialiseOnSubscribeObservable<Integer> initObservable = new InitialiseOnSubscribeObservable<Integer>(testObservable); |
| 142 | + |
| 143 | + // converter with a stub conversion method |
| 144 | + when(mockConverter.convert(123)).thenThrow(new ConversionException("conversion exception!")); |
| 145 | + |
| 146 | + // Object we are really testing |
| 147 | + ConvertingObservable<Integer, String> convertObservable = new ConvertingObservable<>(initObservable, mockConverter); |
| 148 | + |
| 149 | + //Act |
| 150 | + convertObservable.addObserver(mockObserver); |
| 151 | + convertObservable.setSource(initObservable); |
| 152 | + testObservable.setValue(123); |
| 153 | + |
| 154 | + //Assert |
| 155 | + // The initialisable observer has its onError message called once, for the ConversionException |
| 156 | + verify(mockObserver, times(0)).onValue(anyString()); |
| 157 | + verify(mockObserver, times(1)).onError(exceptionCaptor.capture()); |
| 158 | + assertEquals("conversion exception!", exceptionCaptor.getValue().getMessage()); |
| 159 | +} |
| 160 | +``` |
| 161 | + |
| 162 | +:::{important} |
| 163 | +As with the `@Mock` annotation, the `@Captor` annotation will only be processed if the test class is annotated with |
| 164 | +`@RunWith(MockitoJUnitRunner.StrictStubs.class)`. |
| 165 | +::: |
| 166 | + |
| 167 | +## Checking Order of Method Calls |
| 168 | + |
| 169 | +Mockito can be used to check the order methods were called in. |
| 170 | + |
| 171 | +```java |
| 172 | +InOrder inOrder = inOrder(firstMock, secondMock); |
| 173 | + |
| 174 | +inOrder.verify(firstMock).add("was called first"); |
| 175 | +inOrder.verify(secondMock).add("was called second"); |
| 176 | +``` |
| 177 | + |
| 178 | +## Spies |
| 179 | + |
| 180 | +Spies can be used to stub a method or verify calls on a real class. Needing to use a partial mock like this might be |
| 181 | +a symptom of problems with code though! |
| 182 | + |
| 183 | +```java |
| 184 | +// These are equivalent, but the first is the preferred approach |
| 185 | +@Spy Foo spyOnFoo = new Foo("argument"); |
| 186 | +Foo spyOnFoo = Mockito.spy(new Foo("argument")); |
| 187 | +``` |
| 188 | + |
| 189 | +## Examples |
| 190 | + |
| 191 | +### IBEX Observable |
| 192 | + |
| 193 | +Below is a full example, showing how the verification and stubbing can be used to check behaviour of an |
| 194 | +observable. |
| 195 | + |
| 196 | +In this example, `InitialiseOnSubscribeObservable` takes another observable as its argument, gets the current value of |
| 197 | +that observable, and listens for changes. Here we stub the class that `InitialiseOnSubscribeObservable` is observing, |
| 198 | +to simplify the test. The only method call we are testing is `getValue()`. |
| 199 | + |
| 200 | +The `InitialisableObserver` is also mocked. As part of the test we want to check that it has its `update()` method |
| 201 | +called with a specific set of arguments. We use `times(1)` to specify we want the method called exactly once. |
| 202 | + |
| 203 | +```java |
| 204 | +@Mock private InitialisableObserver<String> mockObserver; |
| 205 | +@Mock private CachingObservable<String> mockObservable; |
| 206 | + |
| 207 | +@Test |
| 208 | +public void test_InitialiseOnSubscribeObservable_subscription() { |
| 209 | + //Arrange |
| 210 | + String value = "value"; |
| 211 | + |
| 212 | + when(mockObservable.getValue()).thenReturn(value); |
| 213 | + |
| 214 | + // Object we are really testing |
| 215 | + InitialiseOnSubscribeObservable<String> initObservable = |
| 216 | + new InitialiseOnSubscribeObservable<>(mockObservable); |
| 217 | + |
| 218 | + //Act |
| 219 | + Object returned = initObservable.addObserver(mockObserver); |
| 220 | + |
| 221 | + //Assert: The initialisable observer has its update method called once |
| 222 | + verify(mockObserver, times(1)).update(value, null, false); |
| 223 | + |
| 224 | + // The InitialiseOnSubscribeObservable has the value returned from the mock observable |
| 225 | + assertEquals(value, initObservable.getValue()); |
| 226 | + |
| 227 | + // A Unsubscriber is returned |
| 228 | + assertEquals(Unsubscriber.class, returned.getClass()); |
| 229 | +} |
| 230 | +``` |
| 231 | + |
| 232 | +### DB Tests using `Answer` |
| 233 | + |
| 234 | +In [`RdbWritterTests`](https://github.com/ISISComputingGroup/EPICS-IocLogServer/blob/master/LogServer/src/test/java/org/isis/logserver/rdb/RdbWritterTests.java), |
| 235 | +there is an example of using an answer to perform a more complicated return. The answer works like this: |
| 236 | + |
| 237 | +```java |
| 238 | +when(mockPreparedStatement.executeQuery()).thenAnswer(resultAndStatement.new ResultsSetAnswer()); |
| 239 | +``` |
| 240 | + |
| 241 | +In this case the answer class is implemented as an inner class of another class, but this is not necessary. |
| 242 | +The answer looks like: |
| 243 | + |
| 244 | +```java |
| 245 | +public class ResultsSetAnswer implements Answer<ResultSet> { |
| 246 | + @Override |
| 247 | + public ResultSet answer(InvocationOnMock invocation) throws Throwable { |
| 248 | + openedResultsSet++; |
| 249 | + return resultSet; |
| 250 | + } |
| 251 | +} |
| 252 | +``` |
| 253 | + |
| 254 | +In the above example, the `Answer` is used to keep track of the number of times a result set was opened; this `Answer` |
| 255 | +implementation makes that information available in its parent class. |
| 256 | + |
| 257 | +## Tips and Advice |
| 258 | + |
| 259 | +* Use mocks to test interactions between a class and a particular interface |
| 260 | +* Use mocks to avoid unit tests touching complex or buggy dependencies |
| 261 | +* Do not mock type you don't own? Perhaps... |
| 262 | +* Do not mock simple classes or value objects - may as well use the real thing |
| 263 | +* Do not mock everything! |
0 commit comments