summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hudson <hugohudson@google.com>2012-02-28 18:09:29 +0000
committerHugo Hudson <hugohudson@google.com>2012-02-28 18:09:29 +0000
commit4541568ab5ac306c7dc2b648f7908610868180de (patch)
tree5cff28d88b5dec5b83e732adbe194ca2ae106403
parent40d40a3f2ebf988f36b828157be56cc12c9c70ac (diff)
downloadlittlemock-4541568ab5ac306c7dc2b648f7908610868180de.tar.gz
Update to r16 of LittleMock.
Change-Id: Ia270375e8a549709d89a34021d33e22d883b1c25
-rw-r--r--src/com/google/testing/littlemock/LittleMock.java96
-rw-r--r--tests/com/google/testing/littlemock/LittleMockTest.java20
2 files changed, 101 insertions, 15 deletions
diff --git a/src/com/google/testing/littlemock/LittleMock.java b/src/com/google/testing/littlemock/LittleMock.java
index d13150a..9d756dc 100644
--- a/src/com/google/testing/littlemock/LittleMock.java
+++ b/src/com/google/testing/littlemock/LittleMock.java
@@ -1099,6 +1099,13 @@ public class LittleMock {
}
}
+ /** Helper method to throw an IllegalStateException if given condition is not met. */
+ private static void checkState(boolean condition) {
+ if (!condition) {
+ throw new IllegalStateException();
+ }
+ }
+
/**
* If the input object is one of our mocks, returns the {@link DefaultInvocationHandler}
* we constructed it with. Otherwise fails with {@link IllegalArgumentException}.
@@ -1118,14 +1125,92 @@ public class LittleMock {
return (DefaultInvocationHandler) invocationHandler;
}
} catch (Exception expectedIfNotAProxyBuilderMock) {}
+ try {
+ // Try with javassist.
+ Class<?> proxyObjectClass = Class.forName("javassist.util.proxy.ProxyObject");
+ Method getHandlerMethod = proxyObjectClass.getMethod("getHandler");
+ Object methodHandler = getHandlerMethod.invoke(mock);
+ InvocationHandler invocationHandler = Proxy.getInvocationHandler(methodHandler);
+ Method getOriginalMethod = invocationHandler.getClass().getMethod("$$getOriginal");
+ Object original = getOriginalMethod.invoke(invocationHandler);
+ if (original instanceof DefaultInvocationHandler) {
+ return (DefaultInvocationHandler) original;
+ }
+ } catch (Exception expectedIfNotJavassistProxy) {}
throw new IllegalArgumentException("not a valid mock: " + mock);
}
/** Create a dynamic proxy for the given class, delegating to the given invocation handler. */
- private static Object createProxy(Class<?> clazz, InvocationHandler handler) {
+ private static Object createProxy(Class<?> clazz, final InvocationHandler handler) {
+ // Interfaces are simple. Just proxy them using java.lang.reflect.Proxy.
if (clazz.isInterface()) {
return Proxy.newProxyInstance(getClassLoader(), new Class<?>[] { clazz }, handler);
}
+ // Try with javassist.
+ try {
+ Class<?> proxyFactoryClass = Class.forName("javassist.util.proxy.ProxyFactory");
+ Object proxyFactory = proxyFactoryClass.newInstance();
+ Method setSuperclassMethod = proxyFactoryClass.getMethod("setSuperclass", Class.class);
+ setSuperclassMethod.invoke(proxyFactory, clazz);
+ Class<?> methodFilterClass = Class.forName("javassist.util.proxy.MethodFilter");
+ InvocationHandler methodFilterHandler = new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ checkState(method.getName().equals("isHandled"));
+ checkState(args.length == 1);
+ checkState(args[0] instanceof Method);
+ Method invokedMethod = (Method) args[0];
+ String methodName = invokedMethod.getName();
+ Class<?>[] params = invokedMethod.getParameterTypes();
+ if ("equals".equals(methodName) && params.length == 1
+ && Object.class.equals(params[0])) {
+ return false;
+ }
+ if ("hashCode".equals(methodName) && params.length == 0) {
+ return false;
+ }
+ if ("toString".equals(methodName) && params.length == 0) {
+ return false;
+ }
+ if ("finalize".equals(methodName) && params.length == 0) {
+ return false;
+ }
+ return true;
+ }
+ };
+ Object methodFilter = Proxy.newProxyInstance(getClassLoader(),
+ new Class<?>[] { methodFilterClass }, methodFilterHandler);
+ Method setFilterMethod = proxyFactoryClass.getMethod("setFilter", methodFilterClass);
+ setFilterMethod.invoke(proxyFactory, methodFilter);
+ Method createClassMethod = proxyFactoryClass.getMethod("createClass");
+ Class<?> createdClass = (Class<?>) createClassMethod.invoke(proxyFactory);
+ InvocationHandler methodHandlerHandler = new InvocationHandler() {
+ @SuppressWarnings("unused")
+ public InvocationHandler $$getOriginal() {
+ return handler;
+ }
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ checkState(method.getName().equals("invoke"));
+ checkState(args.length == 4);
+ checkState(args[1] instanceof Method);
+ Method invokedMethod = (Method) args[1];
+ checkState(args[3] instanceof Object[]);
+ return handler.invoke(args[0], invokedMethod, (Object[]) args[3]);
+ }
+ };
+ Class<?> methodHandlerClass = Class.forName("javassist.util.proxy.MethodHandler");
+ Object methodHandler = Proxy.newProxyInstance(getClassLoader(),
+ new Class<?>[] { methodHandlerClass }, methodHandlerHandler);
+ Object proxy = unsafeCreateInstance(createdClass);
+ Class<?> proxyObjectClass = Class.forName("javassist.util.proxy.ProxyObject");
+ Method setHandlerMethod = proxyObjectClass.getMethod("setHandler", methodHandlerClass);
+ setHandlerMethod.invoke(proxy, methodHandler);
+ return proxy;
+ } catch (Exception e) {
+ // Not supported, i.e. javassist missing. Fall through.
+ }
+ // So, this is a class. First try using Android's ProxyBuilder from dexmaker.
try {
Class<?> proxyBuilder = Class.forName("com.google.dexmaker.stock.ProxyBuilder");
Method forClassMethod = proxyBuilder.getMethod("forClass", Class.class);
@@ -1148,6 +1233,15 @@ public class LittleMock {
/** Attempt to construct an instance of the class using hacky methods to avoid calling super. */
@SuppressWarnings("unchecked")
private static <T> T unsafeCreateInstance(Class<T> clazz) {
+ // try jvm
+ try {
+ Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
+ Field f = unsafeClass.getDeclaredField("theUnsafe");
+ f.setAccessible(true);
+ final Object unsafe = f.get(null);
+ final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
+ return (T) allocateInstance.invoke(unsafe, clazz);
+ } catch (Exception ignored) {}
// try dalvikvm, pre-gingerbread
try {
final Method newInstance = ObjectInputStream.class.getDeclaredMethod(
diff --git a/tests/com/google/testing/littlemock/LittleMockTest.java b/tests/com/google/testing/littlemock/LittleMockTest.java
index c9b36be..e7f9bca 100644
--- a/tests/com/google/testing/littlemock/LittleMockTest.java
+++ b/tests/com/google/testing/littlemock/LittleMockTest.java
@@ -1551,15 +1551,18 @@ public class LittleMockTest extends TestCase {
}
public static class Jim {
- public void bob() {
+ public int bob() {
fail();
+ return 3;
}
}
// Does not work on JVM, android only.
- public void suppress_testMockingConcreteClasses() throws Exception {
+ public void testMockingConcreteClasses() throws Exception {
Jim mock = mock(Jim.class);
- mock.bob();
+ assertEquals(0, mock.bob());
+ doReturn(8).when(mock).bob();
+ assertEquals(8, mock.bob());
}
private Future<Void> invokeBarMethodAfterLatchAwait(final CountDownLatch countDownLatch) {
@@ -1576,17 +1579,6 @@ public class LittleMockTest extends TestCase {
// TODO(hugohudson): 5. Every method that throws exceptions could be improved by adding
// test for the content of the error message.
- // TODO(hugohudson): 5. Add InOrder class, so that we can check that the given methods on
- // the given mocks happen in the right order. It will be pretty easy to do. The syntax
- // looks like this:
- // InOrder inOrder = inOrder(firstMock, secondMock);
- // inOrder.verify(firstMock).firstMethod();
- // inOrder.verify(secondMock).secondMethod();
- // This allows us to verify that the calls happened in the desired order.
- // By far the simplest way to do this is have a static AtomicInteger on the class which
- // indicates exactly when every method call happened, and then just compare order based on
- // that.
-
// TODO(hugohudson): 5. Make the doReturn() method take variable arguments.
// The syntax is:
// doReturn(1, 2, 3).when(mFoo).anInt();