View Javadoc
1   package mockit;
2   
3   import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4   import static org.junit.jupiter.api.Assertions.assertEquals;
5   import static org.junit.jupiter.api.Assertions.assertFalse;
6   import static org.junit.jupiter.api.Assertions.assertNotNull;
7   import static org.junit.jupiter.api.Assertions.assertSame;
8   import static org.junit.jupiter.api.Assertions.assertTrue;
9   import static org.junit.jupiter.api.Assertions.fail;
10  
11  import java.io.IOException;
12  import java.io.InputStream;
13  import java.nio.ByteBuffer;
14  import java.util.Arrays;
15  import java.util.Date;
16  import java.util.LinkedList;
17  import java.util.Queue;
18  
19  import org.junit.jupiter.api.Disabled;
20  import org.junit.jupiter.api.Test;
21  import org.junit.runner.RunWith;
22  import org.junit.runners.BlockJUnit4ClassRunner;
23  
24  /**
25   * The Class InstanceSpecificMockingTest.
26   */
27  final class InstanceSpecificMockingTest {
28  
29      /**
30       * The Class Collaborator.
31       */
32      static class Collaborator {
33  
34          /** The value. */
35          protected final int value;
36  
37          /**
38           * Instantiates a new collaborator.
39           */
40          Collaborator() {
41              value = -1;
42          }
43  
44          /**
45           * Instantiates a new collaborator.
46           *
47           * @param value
48           *            the value
49           */
50          Collaborator(int value) {
51              this.value = value;
52          }
53  
54          /**
55           * Gets the value.
56           *
57           * @return the value
58           */
59          int getValue() {
60              return value;
61          }
62  
63          /**
64           * Simple operation.
65           *
66           * @param a
67           *            the a
68           * @param b
69           *            the b
70           * @param c
71           *            the c
72           *
73           * @return true, if successful
74           */
75          @SuppressWarnings("unused")
76          final boolean simpleOperation(int a, String b, Date c) {
77              return true;
78          }
79  
80          /**
81           * Do something.
82           *
83           * @param b
84           *            the b
85           * @param s
86           *            the s
87           */
88          @SuppressWarnings("unused")
89          static void doSomething(boolean b, String s) {
90              throw new IllegalStateException();
91          }
92      }
93  
94      /** The previous instance. */
95      final Collaborator previousInstance = new Collaborator();
96  
97      /** The mock. */
98      @Injectable
99      Collaborator mock;
100 
101     /**
102      * Exercise injected instance during replay only.
103      */
104     @Test
105     void exerciseInjectedInstanceDuringReplayOnly() {
106         assertThatPreviouslyCreatedInstanceIsNotMocked();
107 
108         assertEquals(0, mock.value);
109         assertEquals(0, mock.getValue());
110         assertFalse(mock.simpleOperation(1, "test", null));
111 
112         assertThatNewlyCreatedInstanceIsNotMocked();
113     }
114 
115     /**
116      * Assert that previously created instance is not mocked.
117      */
118     void assertThatPreviouslyCreatedInstanceIsNotMocked() {
119         assertEquals(-1, previousInstance.value);
120         assertEquals(-1, previousInstance.getValue());
121         assertTrue(previousInstance.simpleOperation(1, "test", null));
122     }
123 
124     /**
125      * Assert that newly created instance is not mocked.
126      */
127     void assertThatNewlyCreatedInstanceIsNotMocked() {
128         Collaborator newInstance = new Collaborator();
129         assertEquals(-1, newInstance.value);
130         assertEquals(-1, newInstance.getValue());
131         assertTrue(newInstance.simpleOperation(1, "test", null));
132     }
133 
134     /**
135      * Mock specific instance.
136      */
137     @Test
138     void mockSpecificInstance() {
139         new Expectations() {
140             {
141                 mock.simpleOperation(1, "", null);
142                 result = false;
143                 mock.getValue();
144                 result = 123;
145                 times = 1;
146             }
147         };
148 
149         assertFalse(mock.simpleOperation(1, "", null));
150         assertEquals(123, mock.getValue());
151         assertThatPreviouslyCreatedInstanceIsNotMocked();
152         assertThatNewlyCreatedInstanceIsNotMocked();
153 
154         try {
155             Collaborator.doSomething(false, null);
156             fail();
157         } catch (IllegalStateException ignore) {
158         }
159     }
160 
161     /**
162      * Use A second mock instance of the same type.
163      *
164      * @param mock2
165      *            the mock 2
166      */
167     @Test
168     void useASecondMockInstanceOfTheSameType(@Injectable final Collaborator mock2) {
169         assertThatPreviouslyCreatedInstanceIsNotMocked();
170 
171         new Expectations() {
172             {
173                 mock2.getValue();
174                 result = 2;
175                 mock.getValue();
176                 returns(1, 3);
177             }
178         };
179 
180         assertEquals(1, mock.getValue());
181         assertEquals(2, mock2.getValue());
182         assertEquals(3, mock.getValue());
183         assertEquals(2, mock2.getValue());
184         assertEquals(3, mock.getValue());
185 
186         assertThatPreviouslyCreatedInstanceIsNotMocked();
187         assertThatNewlyCreatedInstanceIsNotMocked();
188     }
189 
190     // Injectable mocks of unusual types ///////////////////////////////////////////////////////////////////////////////
191 
192     /**
193      * Allow injectable mock of interface type.
194      *
195      * @param runnable
196      *            the runnable
197      */
198     @Test
199     void allowInjectableMockOfInterfaceType(@Injectable final Runnable runnable) {
200         runnable.run();
201         runnable.run();
202 
203         new Verifications() {
204             {
205                 runnable.run();
206                 minTimes = 1;
207                 maxTimes = 2;
208             }
209         };
210     }
211 
212     /**
213      * Allow injectable mock of annotation type.
214      *
215      * @param runWith
216      *            the run with
217      */
218     @Test
219     void allowInjectableMockOfAnnotationType(@Injectable final RunWith runWith) {
220         new Expectations() {
221             {
222                 runWith.value();
223                 result = BlockJUnit4ClassRunner.class;
224             }
225         };
226 
227         assertSame(BlockJUnit4ClassRunner.class, runWith.value());
228     }
229 
230     // Mocking java.nio.ByteBuffer /////////////////////////////////////////////////////////////////////////////////////
231 
232     /**
233      * Mock byte buffer as injectable.
234      *
235      * @param buf
236      *            the buf
237      */
238     // TODO JWL 2/18/2024 Test not allowed on jdk21
239     @Disabled
240     @Test
241     void mockByteBufferAsInjectable(@Injectable final ByteBuffer buf) {
242         ByteBuffer realBuf = ByteBuffer.allocateDirect(10);
243         assertNotNull(realBuf);
244         assertEquals(10, realBuf.capacity());
245 
246         new Expectations() {
247             {
248                 buf.isDirect();
249                 result = true;
250 
251                 // Calling "getBytes()" here indirectly creates a new ByteBuffer, requiring use of @Injectable.
252                 buf.put("Test".getBytes());
253                 times = 1;
254             }
255         };
256 
257         assertTrue(buf.isDirect());
258         buf.put("Test".getBytes());
259     }
260 
261     /**
262      * Mock byte buffer regularly.
263      *
264      * @param mockBuffer
265      *            the mock buffer
266      */
267     // TODO JWL 10/30/2022 Test is very flaky, ignore it
268     @Disabled
269     @Test
270     void mockByteBufferRegularly(@Mocked ByteBuffer mockBuffer) {
271         ByteBuffer buffer = ByteBuffer.allocateDirect(10);
272         // noinspection MisorderedAssertEqualsArguments
273         assertSame(mockBuffer, buffer);
274 
275         new Verifications() {
276             {
277                 ByteBuffer.allocateDirect(anyInt);
278             }
279         };
280     }
281 
282     /**
283      * Mock byte buffer as cascading.
284      *
285      * @param unused
286      *            the unused
287      */
288     // TODO JWL 10/30/2022 Test is very flaky, ignore it
289     @Disabled
290     @Test
291     void mockByteBufferAsCascading(@Mocked ByteBuffer unused) {
292         ByteBuffer cascadedBuf = ByteBuffer.allocateDirect(10);
293         assertNotNull(cascadedBuf);
294         assertEquals(0, cascadedBuf.capacity());
295     }
296 
297     /**
298      * A factory for creating Buffer objects.
299      */
300     static class BufferFactory {
301         /**
302          * Creates a new Buffer object.
303          *
304          * @return the byte buffer
305          */
306         ByteBuffer createBuffer() {
307             return null;
308         }
309     }
310 
311     /**
312      * Mock byte buffer as cascaded mock.
313      *
314      * @param cascadingMock
315      *            the cascading mock
316      */
317     // TODO JWL 2/18/2024 Test not allowed on jdk21
318     @Disabled
319     @Test
320     void mockByteBufferAsCascadedMock(@Mocked BufferFactory cascadingMock) {
321         ByteBuffer realBuf1 = ByteBuffer.allocateDirect(10);
322         assertEquals(10, realBuf1.capacity());
323 
324         ByteBuffer cascadedBuf = cascadingMock.createBuffer();
325         assertEquals(0, cascadedBuf.capacity());
326 
327         ByteBuffer realBuf2 = ByteBuffer.allocateDirect(20);
328         assertEquals(20, realBuf2.capacity());
329     }
330 
331     // Mocking java.io.InputStream /////////////////////////////////////////////////////////////////////////////////////
332 
333     /**
334      * The Class ConcatenatingInputStream.
335      */
336     public static final class ConcatenatingInputStream extends InputStream {
337 
338         /** The sequential inputs. */
339         private final Queue<InputStream> sequentialInputs;
340 
341         /** The current input. */
342         private InputStream currentInput;
343 
344         /**
345          * Instantiates a new concatenating input stream.
346          *
347          * @param sequentialInputs
348          *            the sequential inputs
349          */
350         public ConcatenatingInputStream(InputStream... sequentialInputs) {
351             this.sequentialInputs = new LinkedList<>(Arrays.asList(sequentialInputs));
352             currentInput = this.sequentialInputs.poll();
353         }
354 
355         @Override
356         public int read() throws IOException {
357             if (currentInput == null) {
358                 return -1;
359             }
360 
361             int nextByte = currentInput.read();
362 
363             if (nextByte >= 0) {
364                 return nextByte;
365             }
366 
367             currentInput = sequentialInputs.poll();
368             // noinspection TailRecursion
369             return read();
370         }
371     }
372 
373     /**
374      * Concatenate input streams.
375      *
376      * @param input1
377      *            the input 1
378      * @param input2
379      *            the input 2
380      *
381      * @throws Exception
382      *             the exception
383      */
384     @Test
385     void concatenateInputStreams(@Injectable final InputStream input1, @Injectable final InputStream input2)
386             throws Exception {
387         new Expectations() {
388             {
389                 input1.read();
390                 returns(1, 2, -1);
391                 input2.read();
392                 returns(3, -1);
393             }
394         };
395 
396         InputStream concatenatedInput = new ConcatenatingInputStream(input1, input2);
397         byte[] buf = new byte[3];
398         concatenatedInput.read(buf);
399         concatenatedInput.close();
400 
401         byte[] expectedBytes = { 1, 2, 3 };
402         assertArrayEquals(expectedBytes, buf);
403     }
404 }