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.assertEquals;
9   import static org.junit.jupiter.api.Assertions.assertFalse;
10  import static org.junit.jupiter.api.Assertions.assertNotNull;
11  import static org.junit.jupiter.api.Assertions.assertNull;
12  import static org.junit.jupiter.api.Assertions.assertSame;
13  import static org.junit.jupiter.api.Assertions.assertTrue;
14  
15  import java.lang.reflect.Method;
16  import java.util.ArrayList;
17  import java.util.Arrays;
18  import java.util.Collections;
19  import java.util.Date;
20  import java.util.List;
21  import java.util.concurrent.Callable;
22  
23  import javax.security.auth.callback.Callback;
24  import javax.security.auth.callback.CallbackHandler;
25  import javax.security.auth.callback.NameCallback;
26  
27  import mockit.internal.expectations.invocation.MissingInvocation;
28  import mockit.internal.expectations.invocation.UnexpectedInvocation;
29  
30  import org.junit.jupiter.api.Assertions;
31  import org.junit.jupiter.api.Disabled;
32  import org.junit.jupiter.api.Test;
33  
34  @SuppressWarnings("JUnitTestMethodWithNoAssertions")
35  public final class MockAnnotationsTest {
36  
37      final CodeUnderTest codeUnderTest = new CodeUnderTest();
38      boolean mockExecuted;
39  
40      static class CodeUnderTest {
41          private final Collaborator dependency = new Collaborator();
42  
43          void doSomething() {
44              dependency.provideSomeService();
45          }
46  
47          long doSomethingElse(int i) {
48              return dependency.getThreadSpecificValue(i);
49          }
50  
51          int performComputation(int a, boolean b) {
52              int i = dependency.getValue();
53              List<?> results = dependency.complexOperation(a, i);
54  
55              if (b) {
56                  dependency.setValue(i + results.size());
57              }
58  
59              return i;
60          }
61      }
62  
63      @SuppressWarnings("UnusedDeclaration")
64      static class Collaborator {
65          static Object xyz;
66          protected int value;
67  
68          Collaborator() {
69          }
70  
71          Collaborator(int value) {
72              this.value = value;
73          }
74  
75          @Deprecated
76          private static String doInternal() {
77              return "123";
78          }
79  
80          void provideSomeService() {
81              throw new RuntimeException("Real provideSomeService() called");
82          }
83  
84          int getValue() {
85              return value;
86          }
87  
88          void setValue(int value) {
89              this.value = value;
90          }
91  
92          List<?> complexOperation(Object input1, Object... otherInputs) {
93              return input1 == null ? Collections.emptyList() : Arrays.asList(otherInputs);
94          }
95  
96          final void simpleOperation(int a, String b, Date c) {
97          }
98  
99          long getThreadSpecificValue(int i) {
100             return Thread.currentThread().getId() + i;
101         }
102     }
103 
104     // Mocks without expectations //////////////////////////////////////////////////////////////////////////////////////
105 
106     static class MockCollaborator1 extends MockUp<Collaborator> {
107         @Mock
108         void provideSomeService() {
109         }
110     }
111 
112     @Test
113     public void mockWithNoExpectationsPassingMockInstance() {
114         new MockCollaborator1();
115 
116         codeUnderTest.doSomething();
117     }
118 
119     // TODO 12/2/2023 yukkes Mocked method must be checked if it exists in the calling class.
120     @Disabled("Mocked method must be checked if it exists in the calling class.")
121     @Test
122     public void attemptToSetUpMockForClassLackingAMatchingRealMethod() {
123         Assertions.assertThrows(IllegalArgumentException.class, () -> new MockForClassWithoutRealMethod());
124     }
125 
126     static final class MockForClassWithoutRealMethod extends MockUp<Collaborator> {
127         @Mock
128         void noMatchingRealMethod() {
129         }
130     }
131 
132     public interface GenericInterface<T> {
133         void method(T t);
134 
135         String method(int[] ii, T l, String[][] ss, T[] ll);
136     }
137 
138     public interface NonGenericSubInterface extends GenericInterface<Long> {
139     }
140 
141     public static final class MockForNonGenericSubInterface extends MockUp<NonGenericSubInterface> {
142         @Mock(invocations = 1)
143         public void method(Long l) {
144             assertTrue(l > 0);
145         }
146 
147         @Mock
148         public String method(int[] ii, Long l, String[][] ss, Long[] ll) {
149             assertTrue(ii.length > 0 && l > 0);
150             return "mocked";
151         }
152     }
153 
154     @Test
155     public void mockMethodOfSubInterfaceWithGenericTypeArgument() {
156         NonGenericSubInterface mock = new MockForNonGenericSubInterface().getMockInstance();
157 
158         mock.method(123L);
159         assertEquals("mocked", mock.method(new int[] { 1 }, 45L, null, null));
160     }
161 
162     @Test
163     public void mockMethodOfGenericInterfaceWithArrayAndGenericTypeArgument() {
164         GenericInterface<Long> mock = new MockUp<GenericInterface<Long>>() {
165             @Mock
166             String method(int[] ii, Long l, String[][] ss, Long[] tt) {
167                 assertTrue(ii.length > 0 && l > 0);
168                 return "mocked";
169             }
170         }.getMockInstance();
171 
172         assertEquals("mocked", mock.method(new int[] { 1 }, 45L, null, null));
173     }
174 
175     @Test
176     public void setUpMocksFromInnerMockClassWithMockConstructor() {
177         new MockCollaborator4();
178         assertFalse(mockExecuted);
179 
180         new CodeUnderTest().doSomething();
181 
182         assertTrue(mockExecuted);
183     }
184 
185     class MockCollaborator4 extends MockUp<Collaborator> {
186         @Mock
187         void $init() {
188             mockExecuted = true;
189         }
190 
191         @Mock
192         void provideSomeService() {
193         }
194     }
195 
196     // Mocks WITH expectations /////////////////////////////////////////////////////////////////////////////////////////
197 
198     @Test
199     public void setUpMocksContainingExpectations() {
200         new MockCollaboratorWithExpectations();
201 
202         int result = codeUnderTest.performComputation(2, true);
203 
204         assertEquals(0, result);
205     }
206 
207     static class MockCollaboratorWithExpectations extends MockUp<Collaborator> {
208         @Mock(minInvocations = 1)
209         int getValue() {
210             return 0;
211         }
212 
213         @Mock(maxInvocations = 2)
214         void setValue(int value) {
215             assertEquals(1, value);
216         }
217 
218         @Mock
219         List<?> complexOperation(Object input1, Object... otherInputs) {
220             int i = (Integer) otherInputs[0];
221             assertEquals(0, i);
222 
223             List<Integer> values = new ArrayList<Integer>();
224             values.add((Integer) input1);
225             return values;
226         }
227 
228         @Mock(invocations = 0)
229         void provideSomeService() {
230         }
231     }
232 
233     // TODO 12/2/2023 yukkes Determination of the number of method calls is not yet implemented.
234     @Disabled("Determination of the number of method calls is not yet implemented.")
235     @Test
236     public void setUpMockWithMinInvocationsExpectationButFailIt() {
237         Assertions.assertThrows(MissingInvocation.class, () -> new MockCollaboratorWithMinInvocationsExpectation());
238     }
239 
240     static class MockCollaboratorWithMinInvocationsExpectation extends MockUp<Collaborator> {
241         @Mock(minInvocations = 2)
242         int getValue() {
243             return 1;
244         }
245     }
246 
247     // TODO 12/2/2023 yukkes Determination of the number of method calls is not yet implemented.
248     @Disabled("Determination of the number of method calls is not yet implemented.")
249     @Test
250     public void setUpMockWithMaxInvocationsExpectationButFailIt() {
251         new MockCollaboratorWithMaxInvocationsExpectation();
252 
253         Assertions.assertThrows(UnexpectedInvocation.class, () -> new Collaborator().setValue(23));
254     }
255 
256     static class MockCollaboratorWithMaxInvocationsExpectation extends MockUp<Collaborator> {
257         @Mock(maxInvocations = 0)
258         void setValue(int v) {
259             assertEquals(23, v);
260         }
261     }
262 
263     // TODO 12/2/2023 yukkes Determination of the number of method calls is not yet implemented.
264     @Disabled("Determination of the number of method calls is not yet implemented.")
265     @Test
266     public void setUpMockWithInvocationsExpectationButFailIt() {
267         new MockCollaboratorWithInvocationsExpectation();
268 
269         Assertions.assertThrows(UnexpectedInvocation.class, () -> {
270             codeUnderTest.doSomething();
271             codeUnderTest.doSomething();
272         });
273     }
274 
275     static class MockCollaboratorWithInvocationsExpectation extends MockUp<Collaborator> {
276         @Mock(invocations = 1)
277         void provideSomeService() {
278         }
279     }
280 
281     // Reentrant mocks /////////////////////////////////////////////////////////////////////////////////////////////////
282 
283     @Test
284     public void setUpReentrantMock() {
285         new MockCollaboratorWithReentrantMock();
286 
287         Assertions.assertThrows(RuntimeException.class, () -> codeUnderTest.doSomething());
288     }
289 
290     static class MockCollaboratorWithReentrantMock extends MockUp<Collaborator> {
291         @Mock
292         int getValue() {
293             return 123;
294         }
295 
296         @Mock(invocations = 1)
297         void provideSomeService(Invocation inv) {
298             inv.proceed();
299         }
300     }
301 
302     // Mocks for constructors and static methods ///////////////////////////////////////////////////////////////////////
303 
304     @Test
305     public void setUpMockForConstructor() {
306         new MockCollaboratorWithConstructorMock();
307 
308         new Collaborator(5);
309     }
310 
311     static class MockCollaboratorWithConstructorMock extends MockUp<Collaborator> {
312         @Mock(invocations = 1)
313         void $init(int value) {
314             assertEquals(5, value);
315         }
316     }
317 
318     @Test
319     public void setUpMockForStaticMethod() {
320         new MockCollaboratorForStaticMethod();
321 
322         // noinspection deprecation
323         Collaborator.doInternal();
324     }
325 
326     static class MockCollaboratorForStaticMethod extends MockUp<Collaborator> {
327         @Mock(invocations = 1)
328         static String doInternal() {
329             return "";
330         }
331     }
332 
333     @Test
334     public void setUpMockForSubclassConstructor() {
335         new MockSubCollaborator();
336 
337         new SubCollaborator(31);
338     }
339 
340     static class SubCollaborator extends Collaborator {
341         SubCollaborator(int i) {
342             throw new RuntimeException(String.valueOf(i));
343         }
344 
345         @Override
346         void provideSomeService() {
347             value = 123;
348         }
349     }
350 
351     static class MockSubCollaborator extends MockUp<SubCollaborator> {
352         @Mock(invocations = 1)
353         void $init(int i) {
354             assertEquals(31, i);
355         }
356 
357         @SuppressWarnings("UnusedDeclaration")
358         native void doNothing();
359     }
360 
361     @Test
362     public void setUpMocksForClassHierarchy() {
363         new MockUp<SubCollaborator>() {
364             @Mock
365             void $init(Invocation inv, int i) {
366                 assertNotNull(inv.getInvokedInstance());
367                 assertTrue(i > 0);
368             }
369 
370             @Mock
371             void provideSomeService(Invocation inv) {
372                 SubCollaborator it = inv.getInvokedInstance();
373                 it.value = 45;
374             }
375 
376             @Mock
377             int getValue(Invocation inv) {
378                 assertNotNull(inv.getInvokedInstance());
379                 // The value of "it" is undefined here; it will be null if this is the first mock invocation reaching
380                 // this
381                 // mock class instance, or the last instance of the mocked subclass if a previous invocation of a mock
382                 // method whose mocked method is defined in the subclass occurred on this mock class instance.
383                 return 123;
384             }
385         };
386 
387         SubCollaborator collaborator = new SubCollaborator(123);
388         collaborator.provideSomeService();
389         assertEquals(45, collaborator.value);
390         assertEquals(123, collaborator.getValue());
391     }
392 
393     @Test
394     public void mockNativeMethodInClassWithRegisterNatives() {
395         // Native methods annotated with IntrinsicCandidate cannot be mocked.
396         Assertions.assertThrows(UnsupportedOperationException.class, () -> {
397             MockSystem mockUp = new MockSystem();
398             assertEquals(0, System.nanoTime());
399 
400             mockUp.onTearDown();
401             assertTrue(System.nanoTime() > 0);
402         });
403     }
404 
405     static class MockSystem extends MockUp<System> {
406         @Mock
407         public static long nanoTime() {
408             return 0;
409         }
410     }
411 
412     @Test
413     public void mockNativeMethodInClassWithoutRegisterNatives() throws Exception {
414         // Native methods annotated with IntrinsicCandidate cannot be mocked.
415         Assertions.assertThrows(UnsupportedOperationException.class, () -> {
416             MockFloat mockUp = new MockFloat();
417             assertEquals(0.0, Float.intBitsToFloat(2243019), 0.0);
418 
419             mockUp.onTearDown();
420             assertTrue(Float.intBitsToFloat(2243019) > 0);
421         });
422     }
423 
424     static class MockFloat extends MockUp<Float> {
425         @SuppressWarnings("UnusedDeclaration")
426         @Mock
427         public static float intBitsToFloat(int bits) {
428             return 0;
429         }
430     }
431 
432     @Test
433     public void setUpMockForJREClass() {
434         MockThread mockThread = new MockThread();
435 
436         Thread.currentThread().interrupt();
437 
438         assertTrue(mockThread.interrupted);
439     }
440 
441     public static class MockThread extends MockUp<Thread> {
442         boolean interrupted;
443 
444         @Mock(invocations = 1)
445         public void interrupt() {
446             interrupted = true;
447         }
448     }
449 
450     // Stubbing of static class initializers ///////////////////////////////////////////////////////////////////////////
451 
452     static class ClassWithStaticInitializers {
453         static String str = "initialized"; // if final it would be a compile-time constant
454         static final Object obj = new Object(); // constant, but only at runtime
455 
456         static {
457             System.exit(1);
458         }
459 
460         static void doSomething() {
461         }
462 
463         static {
464             try {
465                 Class.forName("NonExistentClass");
466             } catch (ClassNotFoundException e) {
467                 e.printStackTrace();
468             }
469         }
470     }
471 
472     @Test
473     public void mockStaticInitializer() {
474         new MockUp<ClassWithStaticInitializers>() {
475             @Mock(invocations = 1)
476             void $clinit() {
477             }
478         };
479 
480         ClassWithStaticInitializers.doSomething();
481 
482         assertNull(ClassWithStaticInitializers.str);
483         assertNull(ClassWithStaticInitializers.obj);
484     }
485 
486     static class AnotherClassWithStaticInitializers {
487         static {
488             System.exit(1);
489         }
490 
491         static void doSomething() {
492             throw new RuntimeException();
493         }
494     }
495 
496     @Test
497     public void stubOutStaticInitializer() throws Exception {
498         new MockForClassWithInitializer();
499 
500         AnotherClassWithStaticInitializers.doSomething();
501     }
502 
503     static class MockForClassWithInitializer extends MockUp<AnotherClassWithStaticInitializers> {
504         @Mock
505         void $clinit() {
506         }
507 
508         @Mock(minInvocations = 1, maxInvocations = 1)
509         void doSomething() {
510         }
511     }
512 
513     static class YetAnotherClassWithStaticInitializer {
514         static {
515             System.loadLibrary("none.dll");
516         }
517 
518         static void doSomething() {
519         }
520     }
521 
522     static class MockForYetAnotherClassWithInitializer extends MockUp<YetAnotherClassWithStaticInitializer> {
523         @Mock
524         void $clinit() {
525         }
526     }
527 
528     // Other tests /////////////////////////////////////////////////////////////////////////////////////////////////////
529 
530     @Test
531     public void stubOutStaticInitializerWithEmptyMockClass() throws Exception {
532         new MockForYetAnotherClassWithInitializer();
533 
534         YetAnotherClassWithStaticInitializer.doSomething();
535     }
536 
537     @Test
538     public void mockJREInterface() throws Exception {
539         CallbackHandler callbackHandler = new MockCallbackHandler().getMockInstance();
540 
541         callbackHandler.handle(new Callback[] { new NameCallback("Enter name:") });
542     }
543 
544     public static class MockCallbackHandler extends MockUp<CallbackHandler> {
545         @Mock(invocations = 1)
546         public void handle(Callback[] callbacks) {
547             assertEquals(1, callbacks.length);
548             assertTrue(callbacks[0] instanceof NameCallback);
549         }
550     }
551 
552     @Test
553     public void mockJREInterfaceWithMockUp() throws Exception {
554         CallbackHandler callbackHandler = new MockUp<CallbackHandler>() {
555             @Mock(invocations = 1)
556             void handle(Callback[] callbacks) {
557                 assertEquals(1, callbacks.length);
558                 assertTrue(callbacks[0] instanceof NameCallback);
559             }
560         }.getMockInstance();
561 
562         callbackHandler.handle(new Callback[] { new NameCallback("Enter name:") });
563     }
564 
565     public interface AnInterface {
566         int doSomething();
567     }
568 
569     @Test
570     public void mockPublicInterfaceWithMockUpHavingInvocationParameter() {
571         AnInterface obj = new MockUp<AnInterface>() {
572             @Mock
573             int doSomething(Invocation inv) {
574                 assertNotNull(inv.getInvokedInstance());
575                 return 122 + inv.getInvocationCount();
576             }
577         }.getMockInstance();
578 
579         assertEquals(123, obj.doSomething());
580     }
581 
582     abstract static class AnAbstractClass {
583         abstract int doSomething();
584     }
585 
586     @Test
587     public <A extends AnAbstractClass> void mockAbstractClassWithMockForAbstractMethodHavingInvocationParameter() {
588         final AnAbstractClass obj = new AnAbstractClass() {
589             @Override
590             int doSomething() {
591                 return 0;
592             }
593         };
594 
595         new MockUp<A>() {
596             @Mock
597             int doSomething(Invocation inv) {
598                 assertSame(obj, inv.getInvokedInstance());
599                 Method invokedMethod = inv.getInvokedMember();
600                 assertTrue(AnAbstractClass.class.isAssignableFrom(invokedMethod.getDeclaringClass()));
601                 return 123;
602             }
603         };
604 
605         assertEquals(123, obj.doSomething());
606     }
607 
608     interface AnotherInterface {
609         int doSomething();
610     }
611 
612     AnotherInterface interfaceInstance;
613 
614     @Test
615     public void attemptToProceedIntoInterfaceImplementation() {
616         interfaceInstance = new MockUp<AnotherInterface>() {
617             @Mock
618             int doSomething(Invocation inv) {
619                 return inv.proceed();
620             }
621         }.getMockInstance();
622 
623         Throwable exception = Assertions.assertThrows(UnsupportedOperationException.class,
624                 () -> interfaceInstance.doSomething());
625         assertEquals("Cannot proceed into abstract/interface method", exception.getMessage());
626     }
627 
628     @Test
629     public void mockNonPublicInterfaceWithMockUpHavingInvocationParameter() {
630         interfaceInstance = new MockUp<AnotherInterface>() {
631             @Mock
632             int doSomething(Invocation inv) {
633                 AnotherInterface invokedInstance = inv.getInvokedInstance();
634                 assertSame(interfaceInstance, invokedInstance);
635 
636                 int invocationCount = inv.getInvocationCount();
637                 assertTrue(invocationCount > 0);
638 
639                 return invocationCount == 1 ? invokedInstance.doSomething() : 123;
640             }
641         }.getMockInstance();
642 
643         assertEquals(123, interfaceInstance.doSomething());
644     }
645 
646     @Test
647     public void mockGenericInterfaceWithMockUpHavingInvocationParameter() throws Exception {
648         Callable<String> mock = new MockUp<Callable<String>>() {
649             @Mock
650             String call(Invocation inv) {
651                 return "mocked";
652             }
653         }.getMockInstance();
654 
655         assertEquals("mocked", mock.call());
656     }
657 
658     static class GenericClass<T> {
659         T doSomething() {
660             return null;
661         }
662     }
663 
664     @Test
665     public void mockGenericClassWithMockUpHavingInvocationParameter() {
666         new MockUp<GenericClass<String>>() {
667             @Mock
668             String doSomething(Invocation inv) {
669                 return "mocked";
670             }
671         };
672 
673         GenericClass<String> mock = new GenericClass<String>();
674         assertEquals("mocked", mock.doSomething());
675     }
676 
677     @Test
678     public void stubbedOutAnnotatedMethodInMockedClass() throws Exception {
679         new MockCollaborator7();
680 
681         assertTrue(Collaborator.class.getDeclaredMethod("doInternal").isAnnotationPresent(Deprecated.class));
682     }
683 
684     static class MockCollaborator7 extends MockUp<Collaborator> {
685         @Mock
686         String doInternal() {
687             return null;
688         }
689 
690         @Mock
691         void provideSomeService() {
692         }
693     }
694 
695     @Test
696     public void concurrentMock() throws Exception {
697         new MockUp<Collaborator>() {
698             @Mock
699             long getThreadSpecificValue(int i) {
700                 return Thread.currentThread().getId() + 123;
701             }
702         };
703 
704         Thread[] threads = new Thread[5];
705 
706         for (int i = 0; i < threads.length; i++) {
707             threads[i] = new Thread() {
708                 @Override
709                 public void run() {
710                     long threadSpecificValue = Thread.currentThread().getId() + 123;
711                     long actualValue = new CodeUnderTest().doSomethingElse(0);
712                     assertEquals(threadSpecificValue, actualValue);
713                 }
714             };
715         }
716 
717         for (Thread thread : threads) {
718             thread.start();
719         }
720         for (Thread thread : threads) {
721             thread.join();
722         }
723     }
724 
725     @Test
726     public void mockUpAffectsInstancesOfSpecifiedSubclassAndNotOfBaseClass() {
727         new MockUpForSubclass();
728 
729         // Mocking applies to instance methods executed on instances of the subclass:
730         assertEquals(123, new SubCollaborator(5).getValue());
731 
732         // And to static methods from any class in the hierarchy:
733         // noinspection deprecation
734         assertEquals("mocked", Collaborator.doInternal());
735 
736         // But not to instance methods executed on instances of the base class:
737         assertEquals(62, new Collaborator(62).getValue());
738     }
739 
740     static class MockUpForSubclass extends MockUp<SubCollaborator> {
741         @Mock
742         void $init(int i) {
743         }
744 
745         @Mock
746         String doInternal() {
747             return "mocked";
748         }
749 
750         @Mock
751         int getValue() {
752             return 123;
753         }
754     }
755 }