/* * Copyright (c) 2006 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit; import java.io.*; import java.lang.annotation.*; import java.lang.reflect.*; import java.util.*; import javax.xml.bind.annotation.*; import org.junit.*; import org.junit.rules.*; import static org.junit.Assert.*; import mockit.internal.*; @SuppressWarnings("deprecation") public final class DynamicPartialMockingTest { @Rule public final ExpectedException thrown = ExpectedException.none(); @SuppressWarnings("unused") @Deprecated static class Collaborator { @Deprecated protected int value; Collaborator() { value = -1; } @Deprecated Collaborator(@Deprecated int value) { this.value = value; } final int getValue() { return value; } void setValue(int value) { this.value = value; } final boolean simpleOperation(int a, @XmlElement(name = "test") String b, Date c) { return true; } static void doSomething(boolean b, String s) { throw new IllegalStateException(); } @Ignore("test") boolean methodWhichCallsAnotherInTheSameClass() { return simpleOperation(1, "internal", null); } String overridableMethod() { return "base"; } @Deprecated native void nativeMethod(); void readFile(File f) {} private void initialize() {} } interface Dependency { boolean doSomething(); List<?> doSomethingElse(int n); } @Test public void dynamicallyMockAClassStrictly() { final Collaborator toBeMocked = new Collaborator(); new StrictExpectations(Collaborator.class) {{ toBeMocked.getValue(); result = 123; }}; // Not mocked: Collaborator collaborator = new Collaborator(); assertEquals(-1, collaborator.value); assertTrue(collaborator.simpleOperation(1, "b", null)); assertEquals(45, new Collaborator(45).value); // Mocked: assertEquals(123, collaborator.getValue()); } @Test public void dynamicallyMockJREClass() throws Exception { new Expectations(ByteArrayOutputStream.class) {{ new ByteArrayOutputStream().size(); result = 123; }}; // Mocked: ByteArrayOutputStream collaborator = new ByteArrayOutputStream(); assertNull(Deencapsulation.getField(collaborator, "buf")); assertEquals(123, collaborator.size()); // Not mocked: ByteArrayOutputStream buf = new ByteArrayOutputStream(200); buf.write(65); assertEquals("A", buf.toString("UTF-8")); } @Test public void dynamicallyMockClass() { new Expectations(Collaborator.class) {{ new Collaborator().getValue(); result = 123; }}; // Mocked: final Collaborator col1 = new Collaborator(); assertEquals(123, col1.getValue()); // Not mocked: final Collaborator col2 = new Collaborator(200); col2.setValue(45); assertEquals(45, col2.value); assertEquals(45, col2.getValue()); new Verifications() {{ col1.getValue(); times = 1; col2.getValue(); times = 1; Collaborator col2Equivalent = new Collaborator(200); times = 1; col2Equivalent.getValue(); times = 1; }}; } @Test public void mockOnlyTheFutureObjectsThatMatchASpecificConstructorInvocation() { // Not mocked: Collaborator f0 = new Collaborator(12); assertEquals(12, f0.getValue()); // Applies partial mocking to all instances. new Expectations(Collaborator.class) {{ Collaborator anyFutureInstanceWithValue1 = new Collaborator(1); anyFutureInstanceWithValue1.overridableMethod(); result = "mock"; times = 2; }}; // Mocked: Collaborator f1 = new Collaborator(1); assertEquals("mock", f1.overridableMethod()); // Not mocked: Collaborator f2 = new Collaborator(2); assertEquals("base", f2.overridableMethod()); // Also mocked: Collaborator f3 = new Collaborator(1); assertEquals("mock", f3.overridableMethod()); // Invocations to non-mocked instances can also be verified (excluding those existing before mocking was applied). new Verifications() {{ Collaborator anyOtherInstance = new Collaborator(withNotEqual(1)); anyOtherInstance.overridableMethod(); times = 1; }}; } @Test public void verifyFutureMockedAndNonMockedObjectsInOrder() { new Expectations(Collaborator.class) {{ Collaborator anyFutureInstanceWithValue1 = new Collaborator(1); anyFutureInstanceWithValue1.getValue(); result = 15; }}; Collaborator f1 = new Collaborator(1); assertEquals(15, f1.getValue()); Collaborator f2 = new Collaborator(2); assertEquals(2, f2.getValue()); assertEquals("base", f2.overridableMethod()); assertEquals("base", f1.overridableMethod()); Collaborator f3 = new Collaborator(1); assertEquals(15, f3.getValue()); new FullVerificationsInOrder() {{ Collaborator anyInstanceWithValue1 = new Collaborator(1); anyInstanceWithValue1.getValue(); Collaborator anyOtherInstance = new Collaborator(withNotEqual(1)); anyOtherInstance.getValue(); anyOtherInstance.overridableMethod(); anyInstanceWithValue1.overridableMethod(); new Collaborator(1); anyInstanceWithValue1.getValue(); }}; } @Test public void dynamicallyMockAnInstanceStrictly() { final Collaborator collaborator = new Collaborator(); new StrictExpectations(collaborator) {{ collaborator.getValue(); result = 123; }}; // Mocked: assertEquals(123, collaborator.getValue()); // Not mocked: assertTrue(collaborator.simpleOperation(1, "b", null)); assertEquals(45, new Collaborator(45).value); assertEquals(-1, new Collaborator().value); } @Test public void expectTwoInvocationsOnStrictDynamicMockButReplayOnce() { final Collaborator collaborator = new Collaborator(); new StrictExpectations(collaborator) {{ collaborator.getValue(); times = 2; }}; assertEquals(0, collaborator.getValue()); thrown.expect(MissingInvocation.class); } @Test public void expectOneInvocationOnStrictDynamicMockButReplayTwice() { final Collaborator collaborator = new Collaborator(1); new StrictExpectations(collaborator) {{ collaborator.methodWhichCallsAnotherInTheSameClass(); result = false; }}; // Mocked: assertFalse(collaborator.methodWhichCallsAnotherInTheSameClass()); // No longer mocked, since it's strict: assertTrue(collaborator.methodWhichCallsAnotherInTheSameClass()); } @Test public void expectTwoInvocationsOnStrictDynamicMockButReplayMoreTimes() { final Collaborator collaborator = new Collaborator(1); new StrictExpectations(collaborator) {{ collaborator.getValue(); times = 2; }}; // Mocked: assertEquals(0, collaborator.getValue()); assertEquals(0, collaborator.getValue()); // No longer mocked, since it's strict and all expected invocations were already replayed: assertEquals(1, collaborator.getValue()); } @Test public void expectTwoOrderedInvocationsOnStrictDynamicMockButReplayOutOfOrder() { final Collaborator collaborator = new Collaborator(1); new StrictExpectations(collaborator) {{ collaborator.setValue(1); collaborator.setValue(2); }}; // Not mocked since the first expectation that can be matched is the one setting the value to 1: collaborator.setValue(2); assertEquals(2, collaborator.value); // Mocked since the first expectation wasn't yet matched by a replayed one: collaborator.setValue(1); assertEquals(2, collaborator.value); // The recorded call to "setValue(2)" is missing at this point. thrown.expect(MissingInvocation.class); } @Test public void dynamicMockFullyVerified_verifyAllRecordedExpectationsButNotAllOfTheReplayedOnes() { final Collaborator collaborator = new Collaborator(0); new Expectations(collaborator) {{ collaborator.setValue(1); }}; collaborator.setValue(1); collaborator.setValue(2); // Verifies all the *mocked* invocations that would be left unverified; ignores those not mocked: new FullVerifications() { // No need to verify "setValue(1)" since it was recorded and implicitly verified. // No need to verify "setValue(2)" since it was not recorded. }; } @Test public void dynamicMockFullyVerifiedInOrder_verifyAllRecordedExpectationsButNotAllOfTheReplayedOnes() { final Collaborator collaborator = new Collaborator(0); new Expectations(collaborator) {{ collaborator.setValue(2); collaborator.setValue(3); }}; collaborator.setValue(1); collaborator.setValue(2); collaborator.setValue(3); // Verifies all the *mocked* (recorded) invocations, ignoring those not mocked: new FullVerificationsInOrder() {{ // No need to verify "setValue(1)" since it was not recorded. collaborator.setValue(2); collaborator.setValue(3); }}; } @Test public void dynamicallyMockedClassFullyVerified_verifyRecordedExpectationButNotReplayedOne() { final Collaborator collaborator = new Collaborator(); new Expectations(Collaborator.class) {{ collaborator.simpleOperation(1, "internal", null); result = false; }}; assertFalse(collaborator.methodWhichCallsAnotherInTheSameClass()); new FullVerifications() {{ collaborator.simpleOperation(anyInt, anyString, null); }}; } @Test public void expectTwoInvocationsOnDynamicMockButReplayOnce() { final Collaborator collaborator = new Collaborator(); new Expectations(collaborator) {{ collaborator.getValue(); times = 2; }}; assertEquals(0, collaborator.getValue()); thrown.expect(MissingInvocation.class); } @Test public void expectOneInvocationOnDynamicMockButReplayTwice() { final Collaborator collaborator = new Collaborator(1); new Expectations(collaborator) {{ collaborator.getValue(); times = 1; }}; // Mocked: assertEquals(0, collaborator.getValue()); // Still mocked because it's not strict: thrown.expect(UnexpectedInvocation.class); assertEquals(0, collaborator.getValue()); } @Test public void dynamicallyMockAnInstance() { final Collaborator collaborator = new Collaborator(2); new Expectations(collaborator) {{ collaborator.simpleOperation(1, "", null); result = false; Collaborator.doSomething(anyBoolean, "test"); }}; // Mocked: assertFalse(collaborator.simpleOperation(1, "", null)); Collaborator.doSomething(true, "test"); // Not mocked: assertEquals(2, collaborator.getValue()); assertEquals(45, new Collaborator(45).value); assertEquals(-1, new Collaborator().value); try { Collaborator.doSomething(false, null); fail(); } catch (IllegalStateException ignore) {} new Verifications() {{ collaborator.getValue(); times = 1; }}; } @Test public void mockMethodInSameClass() { final Collaborator collaborator = new Collaborator(); new Expectations(collaborator) {{ collaborator.simpleOperation(1, anyString, null); result = false; }}; assertFalse(collaborator.methodWhichCallsAnotherInTheSameClass()); assertTrue(collaborator.simpleOperation(2, "", null)); assertFalse(collaborator.simpleOperation(1, "", null)); } static final class SubCollaborator extends Collaborator { SubCollaborator() { this(1); } SubCollaborator(int value) { super(value); } @Override String overridableMethod() { return super.overridableMethod() + " overridden"; } String format() { return String.valueOf(value); } static void causeFailure() { throw new RuntimeException(); } } @Test public void dynamicallyMockASubCollaboratorInstance() { final SubCollaborator collaborator = new SubCollaborator(); new Expectations(collaborator) {{ collaborator.getValue(); result = 5; collaborator.format(); result = "test"; SubCollaborator.causeFailure(); }}; // Mocked: assertEquals(5, collaborator.getValue()); SubCollaborator.causeFailure(); assertEquals("test", collaborator.format()); // Not mocked: assertTrue(collaborator.simpleOperation(0, null, null)); // not recorded assertEquals("1", new SubCollaborator().format()); // was recorded but on a different instance try { Collaborator.doSomething(true, null); // not recorded fail(); } catch (IllegalStateException ignore) {} } @Test public void dynamicallyMockClassHierarchyForSpecifiedSubclass() { final SubCollaborator collaborator = new SubCollaborator(); new Expectations(SubCollaborator.class) {{ collaborator.getValue(); result = 123; collaborator.format(); result = "test"; }}; // Mocked: assertEquals("test", collaborator.format()); assertEquals(123, collaborator.getValue()); // Not mocked: assertTrue(collaborator.simpleOperation(0, null, null)); // Mocked sub-constructor/not mocked base constructor: assertEquals(-1, new SubCollaborator().value); new VerificationsInOrder() {{ collaborator.format(); new SubCollaborator(); }}; } @Test public void mockTheBaseMethodWhileExercisingTheOverride() { final Collaborator collaborator = new Collaborator(); new Expectations(Collaborator.class) {{ collaborator.overridableMethod(); result = ""; result = "mocked"; }}; assertEquals("", collaborator.overridableMethod()); assertEquals("mocked overridden", new SubCollaborator().overridableMethod()); } @Test public void dynamicallyMockAnAnonymousClassInstanceThroughTheImplementedInterface() { final Collaborator collaborator = new Collaborator(); final Dependency dependency = new Dependency() { @Override public boolean doSomething() { return false; } @Override public List<?> doSomethingElse(int n) { return null; } }; new Expectations(collaborator, dependency) {{ collaborator.getValue(); result = 5; dependency.doSomething(); result = true; }}; // Mocked: assertEquals(5, collaborator.getValue()); assertTrue(dependency.doSomething()); // Not mocked: assertTrue(collaborator.simpleOperation(0, null, null)); assertNull(dependency.doSomethingElse(3)); new FullVerifications() {{ dependency.doSomethingElse(anyInt); collaborator.simpleOperation(0, null, null); }}; } @Test public void dynamicallyMockInstanceOfJREClass() { final List<String> list = new LinkedList<String>(); @SuppressWarnings("UseOfObsoleteCollectionType") List<String> anotherList = new Vector<String>(); new Expectations(list, anotherList) {{ list.get(1); result = "an item"; list.size(); result = 2; }}; // Use mocked methods: assertEquals(2, list.size()); assertEquals("an item", list.get(1)); // Use unmocked methods: assertTrue(list.add("another")); assertEquals("another", list.remove(0)); anotherList.add("one"); assertEquals("one", anotherList.get(0)); assertEquals(1, anotherList.size()); } public interface AnotherInterface {} interface NonPublicInterface {} @Test public void attemptToUseDynamicMockingForInvalidTypes( @Mocked AnotherInterface publicInterfaceMock, @Injectable NonPublicInterface nonPublicInterfaceMock) { assertInvalidTypeForDynamicPartialMocking(Dependency.class); assertInvalidTypeForDynamicPartialMocking(Test.class); assertInvalidTypeForDynamicPartialMocking(int[].class); assertInvalidTypeForDynamicPartialMocking(new String[1]); assertInvalidTypeForDynamicPartialMocking(char.class); assertInvalidTypeForDynamicPartialMocking(123); assertInvalidTypeForDynamicPartialMocking(Boolean.class); assertInvalidTypeForDynamicPartialMocking(true); assertInvalidTypeForDynamicPartialMocking(2.5); assertInvalidTypeForDynamicPartialMocking(publicInterfaceMock); assertInvalidTypeForDynamicPartialMocking(nonPublicInterfaceMock); } void assertInvalidTypeForDynamicPartialMocking(Object classOrObject) { try { new StrictExpectations(classOrObject) {}; fail(); } catch (IllegalArgumentException e) { assertTrue(e.getMessage().contains("partial mocking")); } } @Test public void dynamicPartialMockingWithExactArgumentMatching() { final Collaborator collaborator = new Collaborator(); new Expectations(collaborator) {{ collaborator.simpleOperation(1, "s", null); result = false; }}; assertFalse(collaborator.simpleOperation(1, "s", null)); assertTrue(collaborator.simpleOperation(2, "s", null)); assertTrue(collaborator.simpleOperation(1, "S", null)); assertTrue(collaborator.simpleOperation(1, "s", new Date())); assertTrue(collaborator.simpleOperation(1, null, new Date())); assertFalse(collaborator.simpleOperation(1, "s", null)); new FullVerifications() {{ collaborator.simpleOperation(anyInt, null, null); }}; } @Test public void dynamicPartialMockingWithFlexibleArgumentMatching() { final Collaborator mock = new Collaborator(); new Expectations(mock) {{ mock.simpleOperation(anyInt, withPrefix("s"), null); result = false; }}; assertFalse(mock.simpleOperation(1, "sSs", null)); assertTrue(mock.simpleOperation(2, " s", null)); assertTrue(mock.simpleOperation(1, "S", null)); assertFalse(mock.simpleOperation(-1, "s", new Date())); assertTrue(mock.simpleOperation(1, null, null)); assertFalse(mock.simpleOperation(0, "string", null)); Collaborator collaborator = new Collaborator(); assertTrue(collaborator.simpleOperation(1, "sSs", null)); assertTrue(collaborator.simpleOperation(-1, null, new Date())); } @Test public void dynamicPartialMockingWithInstanceSpecificMatching() { final Collaborator collaborator1 = new Collaborator(); final Collaborator collaborator2 = new Collaborator(4); new Expectations(collaborator1, collaborator2) {{ collaborator1.getValue(); result = 3; }}; assertEquals(3, collaborator1.getValue()); assertEquals(4, collaborator2.getValue()); new FullVerificationsInOrder() {{ collaborator1.getValue(); times = 1; collaborator2.getValue(); times = 1; }}; } @Test public void dynamicPartialMockingWithInstanceSpecificMatchingOnTwoInstancesOfSameClass() { final Collaborator mock1 = new Collaborator(); final Collaborator mock2 = new Collaborator(); new Expectations(mock1, mock2) {{ mock1.getValue(); result = 1; mock2.getValue(); result = 2; }}; assertEquals(2, mock2.getValue()); assertEquals(1, mock1.getValue()); } @Test public void methodWithNoRecordedExpectationCalledTwiceDuringReplay() { final Collaborator collaborator = new Collaborator(123); new Expectations(collaborator) {}; assertEquals(123, collaborator.getValue()); assertEquals(123, collaborator.getValue()); new FullVerifications() {{ collaborator.getValue(); times = 2; }}; } static class ClassWithStaticInitializer { static boolean initialized = true; static int doSomething() { return initialized ? 1 : -1; } } @Test public void doNotStubOutStaticInitializersWhenDynamicallyMockingAClass() { new Expectations(ClassWithStaticInitializer.class) {{ ClassWithStaticInitializer.doSomething(); result = 2; }}; assertEquals(2, ClassWithStaticInitializer.doSomething()); assertTrue(ClassWithStaticInitializer.initialized); } static final class ClassWithNative { int doSomething() { return nativeMethod(); } private native int nativeMethod(); } @Test public void attemptToPartiallyMockNativeMethod() { thrown.expect(UnsatisfiedLinkError.class); final ClassWithNative mock = new ClassWithNative(); new Expectations(mock) {{ // The native method is ignored when using dynamic mocking, so this actually tries to execute the real method, // failing since there is no native implementation. mock.nativeMethod(); }}; } @Test public void mockedClassWithAnnotatedElements() throws Exception { new Expectations(Collaborator.class) {}; Collaborator mock = new Collaborator(123); Class<?> mockedClass = mock.getClass(); assertTrue(mockedClass.isAnnotationPresent(Deprecated.class)); assertTrue(mockedClass.getDeclaredField("value").isAnnotationPresent(Deprecated.class)); Method mockedMethod1 = mockedClass.getDeclaredMethod("simpleOperation", int.class, String.class, Date.class); Annotation xmlElement = mockedMethod1.getParameterAnnotations()[1][0]; assertTrue(xmlElement instanceof XmlElement); assertEquals("test", ((XmlElement) xmlElement).name()); Method mockedMethod2 = mockedClass.getDeclaredMethod("methodWhichCallsAnotherInTheSameClass"); Ignore ignore = mockedMethod2.getAnnotation(Ignore.class); assertNotNull(ignore); assertEquals("test", ignore.value()); assertTrue(mockedClass.getDeclaredMethod("nativeMethod").isAnnotationPresent(Deprecated.class)); } @Test public void mockAnnotatedConstructor(@Mocked Collaborator mock) throws Exception { Constructor<?> mockedConstructor = Collaborator.class.getDeclaredConstructor(int.class); assertTrue(mockedConstructor.isAnnotationPresent(Deprecated.class)); assertTrue(mockedConstructor.getParameterAnnotations()[0][0] instanceof Deprecated); } @Test public void regularMockedMethodCallingOverriddenEqualsInDynamicallyMockedClass(@Mocked final Collaborator mock) { @SuppressWarnings("TooBroadScope") final File f = new File("test"); new Expectations(File.class) {}; mock.readFile(new File("test")); new Verifications() {{ mock.readFile(f); }}; } static final class TestedClass { private boolean value; TestedClass() { this(true); } TestedClass(boolean value) { initialize(value); } private void initialize(boolean flag) { value = flag; } } @Test public void mockClassWithConstructorWhichCallsPrivateMethod() { new Expectations(TestedClass.class) {}; assertTrue(new TestedClass(true).value); assertFalse(new TestedClass(false).value); new Verifications() {{ TestedClass t = new TestedClass(anyBoolean); times = 2; t.initialize(anyBoolean); times = 2; }}; } static class Action implements Runnable { @Override public void run() {} } @Test public void partiallyMockAClassImplementingAMockedInterface(@Mocked Runnable mock) { new Expectations(Action.class) {}; } static class Base { List<String> list() { return null; } } static class Sub extends Base {} @Test public void callIterableReturningInstanceMethodOnPartiallyMockedSubclassWithNoRecordedExpectations() { new Expectations(Sub.class) {}; Object result = new Base().list(); assertNull(result); } }