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