/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit;
import java.awt.*;
import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.security.*;
import java.time.*;
import java.util.*;
import java.util.List;
import java.util.logging.*;
import org.junit.*;
import org.junit.rules.*;
import org.junit.runners.*;
import static org.junit.Assert.*;
@SuppressWarnings({
"WaitWhileNotSynced", "UnconditionalWait", "WaitWithoutCorrespondingNotify", "WaitNotInLoop",
"WaitOrAwaitWithoutTimeout", "deprecation"})
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public final class JREMockingTest
{
@Rule public final ExpectedException thrown = ExpectedException.none();
@Test
public void mockingOfFile(@Mocked final File file)
{
new Expectations() {{
file.exists(); result = true;
}};
File f = new File("...");
assertTrue(f.exists());
}
@Test
public void mockingOfCalendar()
{
final Calendar calCST = new GregorianCalendar(2010, 4, 15);
final TimeZone tzCST = TimeZone.getTimeZone("CST");
new Expectations(Calendar.class) {{ Calendar.getInstance(tzCST); result = calCST; }};
Calendar cal1 = Calendar.getInstance(tzCST);
assertSame(calCST, cal1);
assertEquals(2010, cal1.get(Calendar.YEAR));
TimeZone tzPST = TimeZone.getTimeZone("PST");
Calendar cal2 = Calendar.getInstance(tzPST);
assertNotSame(calCST, cal2);
}
@Test
public void regularMockingOfAnnotatedJREMethod(@Mocked Date d) throws Exception
{
assertTrue(d.getClass().getDeclaredMethod("parse", String.class).isAnnotationPresent(Deprecated.class));
}
@Test
public void dynamicMockingOfAnnotatedJREMethod() throws Exception
{
final Date d = new Date();
new Expectations(d) {{
d.getMinutes(); result = 5;
}};
assertEquals(5, d.getMinutes());
assertTrue(Date.class.getDeclaredMethod("getMinutes").isAnnotationPresent(Deprecated.class));
}
@Test
public void fullMockingOfSystem(@Mocked System mockSystem)
{
new Expectations() {{
System.currentTimeMillis();
result = 123L;
}};
assertEquals(123L, System.currentTimeMillis());
}
@Test
public void partialMockingOfSystem() throws Exception
{
long t0 = System.currentTimeMillis();
new MockUp<System>() {
@Mock long nanoTime() { return 123L; }
};
// Repeat enough times to pass the JVM inflation threshold, causing a bytecode accessor to be generated.
for (int i = 0; i < 50; i++) {
assertEquals(123L, System.nanoTime());
}
long delay = 40;
Thread.sleep(delay);
long t1 = System.currentTimeMillis();
assertTrue(t1 - t0 >= delay);
}
@Test
public void mockPackagePrivateMethodsInJREClass(@Mocked AWTEvent awtEvent)
{
AccessControlContext ctx = Deencapsulation.invoke(awtEvent, "getAccessControlContext");
ctx.checkPermission(null);
}
// Mocking of java.lang.Thread and native methods //////////////////////////////////////////////////////////////////
// First mocking: puts mocked class in cache, knowing it has native methods to re-register.
@Test
public void firstMockingOfNativeMethods(@Mocked Thread unused) throws Exception
{
Thread.sleep(5000);
}
// Second mocking: retrieves from cache, no longer knowing it has native methods to re-register.
@Test
public void secondMockingOfNativeMethods(@Mocked final Thread mock)
{
new Expectations() {{
mock.isAlive(); result = true;
}};
assertTrue(mock.isAlive());
}
@Test
public void unmockedNativeMethods() throws Exception
{
Thread.sleep(10);
assertTrue(System.currentTimeMillis() > 0);
}
// See http://www.javaspecialists.eu/archive/Issue056.html
public static class InterruptibleThread extends Thread
{
protected final boolean wasInterruptRequested()
{
try {
Thread.sleep(10);
return false;
}
catch (InterruptedException ignore) {
interrupt();
return true;
}
}
}
@Test
public void interruptibleThreadShouldResetItsInterruptStatusWhenInterrupted(@Mocked Thread unused) throws Exception
{
final InterruptibleThread t = new InterruptibleThread();
new Expectations() {{
Thread.sleep(anyLong); result = new InterruptedException();
t.interrupt();
}};
assertTrue(t.wasInterruptRequested());
}
static class ExampleInterruptibleThread extends InterruptibleThread
{
boolean terminatedCleanly;
@Override @SuppressWarnings("MethodWithMultipleLoops")
public void run()
{
while (true) {
for (int i = 0; i < 10; i++) {
if (wasInterruptRequested()) break;
}
if (wasInterruptRequested()) break;
}
terminatedCleanly = true;
}
}
@Test
public void interruptionOfThreadRunningNestedLoops() throws Exception
{
ExampleInterruptibleThread t = new ExampleInterruptibleThread();
t.start();
Thread.sleep(30);
t.interrupt();
t.join();
assertTrue(t.terminatedCleanly);
}
@Test
public void fullMockingOfThread(@Mocked Thread t)
{
new Expectations() {{
Thread.activeCount();
result = 123;
}};
assertEquals(123, Thread.activeCount());
new Verifications() {{
new Thread((Runnable) any); times = 0;
}};
}
@Test
public void dynamicMockingOfThread()
{
final Thread d = new Thread((Runnable) null);
new Expectations(d) {};
d.start();
d.interrupt();
new Verifications() {{
d.start(); times = 1;
d.interrupt();
}};
}
@Test
public void threadMockUp()
{
new MockUp<Thread>() {
@Mock
String getName() { return "test"; }
};
Thread t = new Thread();
String threadName = t.getName();
assertEquals("test", threadName);
}
@Test
public void mockingOfAnnotatedNativeMethod(@Mocked Thread mock) throws Exception
{
Method countStackFrames = Thread.class.getDeclaredMethod("countStackFrames");
assertTrue(countStackFrames.isAnnotationPresent(Deprecated.class));
}
static final class SomeTask extends Thread { boolean doSomething() { return false; } }
@Test
public void recordDelegatedResultForMethodInMockedThreadSubclass(@Mocked final SomeTask task)
{
new Expectations() {{
task.doSomething();
result = new Delegate() {
@SuppressWarnings("unused")
boolean doIt() { return true; }
};
}};
assertTrue(task.doSomething());
}
@Test
public void attemptToMockThreadLocal(@Mocked ThreadLocal<String> tl)
{
// All ThreadLocal methods are excluded from mocking, avoiding potential StackOverflowError's.
assertNull(tl.get());
tl.set("test");
assertEquals("test", tl.get());
tl.remove();
assertNull(tl.get());
}
// Mocking of IO classes ///////////////////////////////////////////////////////////////////////////////////////////
// These would interfere with the test runner if regular mocking was applied.
@Injectable FileOutputStream stream;
@Injectable Writer writer;
@Injectable FileWriter fw;
@Injectable PrintWriter pw;
@Injectable DataInputStream dis;
@Injectable FileInputStream fis;
// These apparently don't interfere with the test runner.
@Mocked OutputStream os;
@Mocked FileReader fr;
@Mocked InputStream is;
@Mocked Reader reader;
@Mocked InputStreamReader isr;
@Test
public void dynamicMockingOfFileOutputStreamThroughMockField() throws Exception
{
new Expectations() {{
//noinspection ConstantConditions
stream.write((byte[]) any);
}};
stream.write("Hello world".getBytes());
writer.append('x');
new Verifications() {{ writer.append('x'); }};
}
@Test @Ignore("Find a way to avoid NPE from superclass constructor")
public void mockConstructorsInFileWriterClass() throws Exception
{
new Expectations(FileWriter.class) {{
new FileWriter("no.file");
}};
new FileWriter("no.file"); // TODO: throws NPE
}
// Mocking of java.lang.Object methods /////////////////////////////////////////////////////////////////////////////
final Object lock = new Object();
void awaitNotification() throws InterruptedException
{
synchronized (lock) {
lock.wait();
}
}
@Test
public void waitingWithDynamicPartialMocking() throws Exception
{
final Object mockedLock = new Object();
new Expectations(Object.class) {{ mockedLock.wait(); }};
awaitNotification();
}
@Test
public void waitingWithMockParameter(@Mocked final Object mockedLock) throws Exception
{
new Expectations() {{
mockedLock.wait();
}};
awaitNotification();
}
// Mocking the Reflection API //////////////////////////////////////////////////////////////////////////////////////
@Retention(RetentionPolicy.RUNTIME) @interface AnAnnotation { String value(); }
@Retention(RetentionPolicy.RUNTIME) @interface AnotherAnnotation {}
enum AnEnum { @AnAnnotation("one") First, @AnAnnotation("two") Second, @AnotherAnnotation Third }
@Test
public void mockingOfGetAnnotation() throws Exception
{
//noinspection ClassExtendsConcreteCollection
new MockUp<Field>() {
final Map<Object, Annotation> annotationsApplied = new HashMap<Object, Annotation>() {{
put(AnEnum.First, anAnnotation("1"));
put(AnEnum.Second, anAnnotation("2"));
}};
AnAnnotation anAnnotation(final String value)
{
return new AnAnnotation() {
@Override public Class<? extends Annotation> annotationType() { return AnAnnotation.class; }
@Override public String value() { return value; }
};
}
@Mock
<T extends Annotation> T getAnnotation(Invocation inv, Class<T> annotation) throws IllegalAccessException
{
Field it = inv.getInvokedInstance();
Object fieldValue = it.get(null);
Annotation value = annotationsApplied.get(fieldValue);
if (value != null) {
//noinspection unchecked
return (T) value;
}
return inv.proceed();
}
};
Field firstField = AnEnum.class.getField(AnEnum.First.name());
AnAnnotation annotation1 = firstField.getAnnotation(AnAnnotation.class);
assertEquals("1", annotation1.value());
Field secondField = AnEnum.class.getField(AnEnum.Second.name());
AnAnnotation annotation2 = secondField.getAnnotation(AnAnnotation.class);
assertEquals("2", annotation2.value());
Field thirdField = AnEnum.class.getField(AnEnum.Third.name());
assertNull(thirdField.getAnnotation(AnAnnotation.class));
assertNotNull(thirdField.getAnnotation(AnotherAnnotation.class));
}
// Un-mockable JRE classes /////////////////////////////////////////////////////////////////////////////////////////
@Test
public void attemptToMockJREClassThatIsNeverMockable()
{
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("java.lang.ClassLoader is not mockable");
new Expectations(ClassLoader.class) {};
}
@Test
public void attemptToMockClassClass()
{
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("java.lang.Class is not mockable");
new Expectations(Class.class) {};
}
@Test
public void attemptToMockMathClass()
{
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("java.lang.Math is not mockable");
new Expectations(Math.class) {};
}
@Test
public void attemptToMockStrictMathClass()
{
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("java.lang.StrictMath is not mockable");
new Expectations(StrictMath.class) {};
}
@Test
public void attemptToMockDurationClass()
{
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("java.time.Duration is not mockable");
//noinspection Since15
new Expectations(Duration.class) {};
}
public interface DurationProvider { @SuppressWarnings("Since15") Duration getDuration(); }
@Test
public void mockMethodWhichReturnsADuration(@Mocked DurationProvider mock)
{
Object d = mock.getDuration();
assertNull(d);
}
// Mocking java.util.logging ///////////////////////////////////////////////////////////////////////////////////////
@Test
public void mockLogManager(@Mocked LogManager mock)
{
LogManager logManager = LogManager.getLogManager();
assertSame(mock, logManager);
}
@Test
public void mockLogger(@Mocked Logger mock)
{
assertNotNull(LogManager.getLogManager());
assertSame(mock, Logger.getLogger("test"));
}
// Mocking critical collection classes /////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("CollectionDeclaredAsConcreteClass")
@Mocked final ArrayList<String> mockedArrayList = new ArrayList<String>();
@Test
public void useMockedArrayList()
{
assertTrue(mockedArrayList.add("test"));
assertEquals("test", mockedArrayList.get(0));
List<Object> l2 = new ArrayList<Object>();
assertTrue(l2.add("test"));
assertNotNull(l2.get(0));
}
@Test
public void useMockedHashMap(@Mocked HashMap<String, Object> mockedHashMap)
{
Map<String, Object> m = new HashMap<String, Object>();
m.put("test", 123);
assertEquals(123, m.get("test"));
assertEquals(1, m.size());
}
@Test
public void useMockedHashSet(@Mocked final HashSet<String> mockedHashSet)
{
Set<String> s = new HashSet<String>();
assertTrue(s.add("test"));
assertFalse(s.contains("test"));
assertEquals(0, s.size());
new Expectations() {{
mockedHashSet.size();
result = 15;
}};
assertEquals(15, mockedHashSet.size());
}
}