/*
* Copyright (c) 2006-2011 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import static org.junit.Assert.*;
import org.junit.*;
public final class DynamicPartialMockingTest
{
static class Collaborator
{
protected final int value;
Collaborator() { value = -1; }
Collaborator(int value) { this.value = value; }
final int getValue() { return value; }
@SuppressWarnings({"UnusedDeclaration"})
final boolean simpleOperation(int a, String b, Date c) { return true; }
@SuppressWarnings({"UnusedDeclaration"})
static void doSomething(boolean b, String s) { throw new IllegalStateException(); }
boolean methodWhichCallsAnotherInTheSameClass()
{
return simpleOperation(1, "internal", null);
}
String overridableMethod() { return "base"; }
}
interface Dependency
{
boolean doSomething();
List<?> doSomethingElse(int n);
}
@Test
public void dynamicallyMockAClass()
{
new Expectations(Collaborator.class)
{
{
new Collaborator().getValue(); result = 123;
}
};
// Mocked:
Collaborator collaborator = new Collaborator();
assertEquals(0, collaborator.value);
assertEquals(123, collaborator.getValue());
// Not mocked:
assertTrue(collaborator.simpleOperation(1, "b", null));
assertEquals(45, new Collaborator(45).value);
}
@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 dynamicallyMockAMockedClass(@Mocked final Collaborator mock)
{
assertEquals(0, mock.value);
new Expectations(mock)
{
{
mock.getValue(); result = 123;
}
};
// Mocked:
assertEquals(123, mock.getValue());
// Not mocked:
Collaborator collaborator = new Collaborator();
assertEquals(-1, collaborator.value);
assertTrue(collaborator.simpleOperation(1, "b", null));
assertEquals(45, new Collaborator(45).value);
}
@Test
public void dynamicallyMockAnInstance()
{
final Collaborator collaborator = new Collaborator();
new Expectations(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(expected = AssertionError.class)
public void expectTwoInvocationsOnStrictDynamicMockButReplayOnce()
{
final Collaborator collaborator = new Collaborator();
new Expectations(collaborator)
{
{
collaborator.getValue(); times = 2;
}
};
assertEquals(0, collaborator.getValue());
}
@Test
public void expectOneInvocationOnStrictDynamicMockButReplayTwice()
{
final Collaborator collaborator = new Collaborator(1);
new Expectations(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 Expectations(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(expected = AssertionError.class)
public void expectTwoInvocationsOnNonStrictDynamicMockButReplayOnce()
{
final Collaborator collaborator = new Collaborator();
new NonStrictExpectations(collaborator)
{
{
collaborator.getValue(); times = 2;
}
};
assertEquals(0, collaborator.getValue());
}
@Test(expected = AssertionError.class)
public void expectOneInvocationOnNonStrictDynamicMockButReplayTwice()
{
final Collaborator collaborator = new Collaborator(1);
new NonStrictExpectations(collaborator)
{
{
collaborator.getValue(); times = 1;
}
};
// Mocked:
assertEquals(0, collaborator.getValue());
// Still mocked because it's non-strict:
assertEquals(0, collaborator.getValue());
}
@Test
public void dynamicallyMockAnInstanceWithNonStrictExpectations()
{
final Collaborator collaborator = new Collaborator(2);
new NonStrictExpectations(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.doSomething(anyBoolean, "test");
collaborator.getValue(); times = 1;
new Collaborator(45);
}
};
}
@Test
public void mockMethodInSameClass()
{
final Collaborator collaborator = new Collaborator();
new NonStrictExpectations(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); }
}
@Test(expected = IllegalStateException.class)
public void dynamicallyMockASubCollaboratorInstance()
{
final SubCollaborator collaborator = new SubCollaborator();
new NonStrictExpectations(collaborator)
{
{
collaborator.getValue(); result = 5;
new SubCollaborator().format(); result = "test";
}
};
// Mocked:
assertEquals(5, collaborator.getValue());
assertEquals("test", collaborator.format());
// Not mocked:
assertTrue(collaborator.simpleOperation(0, null, null));
Collaborator.doSomething(true, null); // will throw the IllegalStateException
}
@Test
public void dynamicallyMockOnlyTheSubclass()
{
final SubCollaborator collaborator = new SubCollaborator();
new NonStrictExpectations(SubCollaborator.class)
{
{
collaborator.getValue();
collaborator.format(); result = "test";
}
};
// Mocked:
assertEquals("test", collaborator.format());
// Not mocked:
assertEquals(1, collaborator.getValue());
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 = "";
collaborator.overridableMethod(); 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()
{
public boolean doSomething() { return false; }
public List<?> doSomethingElse(int n) { return null; }
};
new NonStrictExpectations(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.doSomething();
collaborator.getValue();
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 NonStrictExpectations(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 {}
@Test
public void attemptToUseDynamicMockingForInvalidTypes(AnotherInterface mockedInterface)
{
assertInvalidTypeForDynamicMocking(Dependency.class);
assertInvalidTypeForDynamicMocking(Test.class);
assertInvalidTypeForDynamicMocking(int[].class);
assertInvalidTypeForDynamicMocking(new String[1]);
assertInvalidTypeForDynamicMocking(char.class);
assertInvalidTypeForDynamicMocking(123);
assertInvalidTypeForDynamicMocking(Boolean.class);
assertInvalidTypeForDynamicMocking(true);
assertInvalidTypeForDynamicMocking(2.5);
assertInvalidTypeForDynamicMocking(Mockit.newEmptyProxy(Dependency.class));
assertInvalidTypeForDynamicMocking(mockedInterface);
}
private void assertInvalidTypeForDynamicMocking(Object classOrObject)
{
try {
new Expectations(classOrObject) {};
fail();
}
catch (IllegalArgumentException ignore) {}
}
@Test
public void dynamicPartialMockingWithExactArgumentMatching()
{
final Collaborator collaborator = new Collaborator();
new NonStrictExpectations(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 NonStrictExpectations(mock)
{{
mock.simpleOperation(anyInt, withPrefix("s"), null); result = false;
}};
Collaborator collaborator = new Collaborator();
assertFalse(collaborator.simpleOperation(1, "sSs", null));
assertTrue(collaborator.simpleOperation(2, " s", null));
assertTrue(collaborator.simpleOperation(1, "S", null));
assertFalse(collaborator.simpleOperation(-1, "s", new Date()));
assertTrue(collaborator.simpleOperation(1, null, null));
assertFalse(collaborator.simpleOperation(0, "string", null));
}
@Test
public void dynamicPartialMockingWithOnInstanceMatching()
{
final Collaborator mock = new Collaborator();
new NonStrictExpectations(mock)
{{
onInstance(mock).getValue(); result = 3;
}};
assertEquals(3, mock.getValue());
assertEquals(4, new Collaborator(4).getValue());
new FullVerificationsInOrder()
{
{
onInstance(mock).getValue();
mock.getValue();
}
};
}
@Test
public void methodWithNoRecordedExpectationCalledTwiceDuringReplay()
{
final Collaborator collaborator = new Collaborator(123);
new NonStrictExpectations(collaborator) {};
assertEquals(123, collaborator.getValue());
assertEquals(123, collaborator.getValue());
new FullVerifications()
{
{
collaborator.getValue(); times = 2;
}
};
}
static final class TaskWithConsoleInput
{
boolean finished;
void doIt()
{
int input = '\0';
while (input != 'A') {
try {
input = System.in.read();
}
catch (IOException e) {
throw new RuntimeException(e);
}
if (input == 'Z') {
finished = true;
break;
}
}
}
}
private boolean runTaskWithTimeout(long timeoutInMillis) throws InterruptedException, ExecutionException
{
final TaskWithConsoleInput task = new TaskWithConsoleInput();
Runnable asynchronousTask = new Runnable()
{
public void run() { task.doIt(); }
};
ExecutorService executor = Executors.newSingleThreadExecutor();
while (!task.finished) {
Future<?> worker = executor.submit(asynchronousTask);
try {
worker.get(timeoutInMillis, TimeUnit.MILLISECONDS);
}
catch (TimeoutException ignore) {
executor.shutdownNow();
return false;
}
}
return true;
}
@Test
public void taskWithConsoleInputTerminatingNormally() throws Exception
{
new Expectations(System.in)
{
{
System.in.read(); returns((int) 'A', (int) 'x', (int) 'Z');
}
};
assertTrue(runTaskWithTimeout(5000));
}
@Test
public void taskWithConsoleInputTerminatingOnTimeout() throws Exception
{
new Expectations(System.in)
{
{
System.in.read();
result = new Delegate()
{
void takeTooLong() throws InterruptedException { Thread.sleep(5000); }
};
}
};
assertFalse("no timeout", runTaskWithTimeout(10));
}
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();
}
// Native methods are currently ignored when using dynamic mocking. It should be possible, however, to support it by
// mocking such methods normally at first, then restoring them at the end of the expectation block if no expectations
// were recorded; "onInstance" matching would not work, though.
@Ignore @Test
public void partiallyMockNativeMethod()
{
final ClassWithNative mock = new ClassWithNative();
new Expectations(mock)
{
{
mock.nativeMethod(); result = 123;
}
};
assertEquals(123, mock.doSomething());
}
@Test // with FileIO compiled with "target 1.1", this produced a VerifyError
public void mockClassCompiledForJava11() throws Exception
{
final FileIO f = new FileIO();
new Expectations(f) {{
f.writeToFile("test");
}};
f.writeToFile("test");
}
}