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 Test not allowed on jdk21
248     @Disabled
249     @Test
250     void mockByteBufferAsInjectable(@Injectable final ByteBuffer buf) {
251         ByteBuffer realBuf = ByteBuffer.allocateDirect(10);
252         assertNotNull(realBuf);
253         assertEquals(10, realBuf.capacity());
254 
255         new Expectations() {
256             {
257                 buf.isDirect();
258                 result = true;
259 
260                 // Calling "getBytes()" here indirectly creates a new ByteBuffer, requiring use of @Injectable.
261                 buf.put("Test".getBytes());
262                 times = 1;
263             }
264         };
265 
266         assertTrue(buf.isDirect());
267         buf.put("Test".getBytes());
268     }
269 
270     /**
271      * Mock byte buffer regularly.
272      *
273      * @param mockBuffer
274      *            the mock buffer
275      */
276     // TODO JWL 10/30/2022 Test is very flaky, ignore it
277     @Disabled
278     @Test
279     void mockByteBufferRegularly(@Mocked ByteBuffer mockBuffer) {
280         ByteBuffer buffer = ByteBuffer.allocateDirect(10);
281         // noinspection MisorderedAssertEqualsArguments
282         assertSame(mockBuffer, buffer);
283 
284         new Verifications() {
285             {
286                 ByteBuffer.allocateDirect(anyInt);
287             }
288         };
289     }
290 
291     /**
292      * Mock byte buffer as cascading.
293      *
294      * @param unused
295      *            the unused
296      */
297     // TODO JWL 10/30/2022 Test is very flaky, ignore it
298     @Disabled
299     @Test
300     void mockByteBufferAsCascading(@Mocked ByteBuffer unused) {
301         ByteBuffer cascadedBuf = ByteBuffer.allocateDirect(10);
302         assertNotNull(cascadedBuf);
303         assertEquals(0, cascadedBuf.capacity());
304     }
305 
306     /**
307      * A factory for creating Buffer objects.
308      */
309     static class BufferFactory {
310         /**
311          * Creates a new Buffer object.
312          *
313          * @return the byte buffer
314          */
315         ByteBuffer createBuffer() {
316             return null;
317         }
318     }
319 
320     /**
321      * Mock byte buffer as cascaded mock.
322      *
323      * @param cascadingMock
324      *            the cascading mock
325      */
326     // TODO JWL 2/18/2024 Test not allowed on jdk21
327     @Disabled
328     @Test
329     void mockByteBufferAsCascadedMock(@Mocked BufferFactory cascadingMock) {
330         ByteBuffer realBuf1 = ByteBuffer.allocateDirect(10);
331         assertEquals(10, realBuf1.capacity());
332 
333         ByteBuffer cascadedBuf = cascadingMock.createBuffer();
334         assertEquals(0, cascadedBuf.capacity());
335 
336         ByteBuffer realBuf2 = ByteBuffer.allocateDirect(20);
337         assertEquals(20, realBuf2.capacity());
338     }
339 
340     // Mocking java.io.InputStream /////////////////////////////////////////////////////////////////////////////////////
341 
342     /**
343      * The Class ConcatenatingInputStream.
344      */
345     public static final class ConcatenatingInputStream extends InputStream {
346 
347         /** The sequential inputs. */
348         private final Queue<InputStream> sequentialInputs;
349 
350         /** The current input. */
351         private InputStream currentInput;
352 
353         /**
354          * Instantiates a new concatenating input stream.
355          *
356          * @param sequentialInputs
357          *            the sequential inputs
358          */
359         public ConcatenatingInputStream(InputStream... sequentialInputs) {
360             this.sequentialInputs = new LinkedList<>(Arrays.asList(sequentialInputs));
361             currentInput = this.sequentialInputs.poll();
362         }
363 
364         @Override
365         public int read() throws IOException {
366             if (currentInput == null) {
367                 return -1;
368             }
369 
370             int nextByte = currentInput.read();
371 
372             if (nextByte >= 0) {
373                 return nextByte;
374             }
375 
376             currentInput = sequentialInputs.poll();
377             // noinspection TailRecursion
378             return read();
379         }
380     }
381 
382     /**
383      * Concatenate input streams.
384      *
385      * @param input1
386      *            the input 1
387      * @param input2
388      *            the input 2
389      *
390      * @throws Exception
391      *             the exception
392      */
393     @Test
394     void concatenateInputStreams(@Injectable final InputStream input1, @Injectable final InputStream input2)
395             throws Exception {
396         new Expectations() {
397             {
398                 input1.read();
399                 returns(1, 2, -1);
400                 input2.read();
401                 returns(3, -1);
402             }
403         };
404 
405         InputStream concatenatedInput = new ConcatenatingInputStream(input1, input2);
406         byte[] buf = new byte[3];
407         concatenatedInput.read(buf);
408         concatenatedInput.close();
409 
410         byte[] expectedBytes = { 1, 2, 3 };
411         assertArrayEquals(expectedBytes, buf);
412     }
413 }