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