summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hudson <hugohudson@google.com>2012-01-10 17:34:02 +0000
committerHugo Hudson <hugohudson@google.com>2012-01-10 17:35:38 +0000
commitebb9a5e46fa2c58fa091a8de2c12d87828fae2b0 (patch)
tree2e4aed3ad2a179efddf4ca2bf7f73c9e79f488b1
parentda0be1ebc3a6f7dbd434a3a60c5b89a1f96cbc62 (diff)
downloadlittlemock-ebb9a5e46fa2c58fa091a8de2c12d87828fae2b0.tar.gz
Updates to r6 of littlemock, concrete classes.
- Updates source to r6. Highlights: - Can now mock concrete classes if you have dexmaker.jar on your classpath. - Can use blocking verify() calls with timeout() method. - Thread-safe mocks, and only one thread can verify() and stub(). Change-Id: I42f55cb33d2ed99097317705a35e73e63f8a864f
-rw-r--r--README4
-rw-r--r--src/com/google/testing/littlemock/AppDataDirGuesser.java97
-rw-r--r--src/com/google/testing/littlemock/LittleMock.java272
-rw-r--r--tests/com/google/testing/littlemock/AppDataDirGuesserTest.java101
-rw-r--r--tests/com/google/testing/littlemock/LittleMockTest.java596
5 files changed, 848 insertions, 222 deletions
diff --git a/README b/README
index fba3f30..6ca0de4 100644
--- a/README
+++ b/README
@@ -8,8 +8,8 @@ License: Apache 2.0
Description:
Mocking framework. Looks very like Mockito (see http://mockito.org).
-Has a number of restrictions - mocks interfaces only, no class
-generation. Has no dependencies, consequently can be used on Android.
+Has a number of restrictions.
+Can mock concrete classes, requires dexmaker.jar on classpath.
Local Modifications:
No modifications.
diff --git a/src/com/google/testing/littlemock/AppDataDirGuesser.java b/src/com/google/testing/littlemock/AppDataDirGuesser.java
new file mode 100644
index 0000000..0fdd475
--- /dev/null
+++ b/src/com/google/testing/littlemock/AppDataDirGuesser.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.testing.littlemock;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for helping guess the application data directory.
+ */
+public class AppDataDirGuesser {
+
+ /** A single default instance of app data dir guesser, for overriding if you really need to. */
+ private static volatile AppDataDirGuesser sInjectableInstance = new AppDataDirGuesser();
+
+ public static void setInstance(AppDataDirGuesser instance) {
+ sInjectableInstance = instance;
+ }
+
+ public static AppDataDirGuesser getsInstance() {
+ return sInjectableInstance;
+ }
+
+ public File guessSuitableDirectoryForGeneratedClasses() {
+ try {
+ ClassLoader classLoader = AppDataDirGuesser.class.getClassLoader();
+ // Check that we have an instance of the PathClassLoader.
+ Class<?> clazz = Class.forName("dalvik.system.PathClassLoader");
+ clazz.cast(classLoader);
+ // Use the toString() method to calculate the data directory.
+ String pathFromThisClassLoader = getPathFromThisClassLoader(classLoader);
+ File[] results = guessPath(pathFromThisClassLoader);
+ if (results.length > 0) {
+ return results[0];
+ }
+ } catch (ClassCastException e) {
+ // Fall through, we will return null.
+ } catch (ClassNotFoundException e) {
+ // Fall through, we will return null.
+ }
+ return null;
+ }
+
+ private String getPathFromThisClassLoader(ClassLoader classLoader) {
+ // Parsing toString() method: yuck. But no other way to get the path.
+ // Strip out the bit between angle brackets, that's our path.
+ String result = classLoader.toString();
+ int index = result.lastIndexOf('[');
+ result = (index == -1) ? result : result.substring(index + 1);
+ index = result.indexOf(']');
+ return (index == -1) ? result : result.substring(0, index);
+ }
+
+ // @VisibleForTesting
+ File[] guessPath(String input) {
+ List<File> results = new ArrayList<File>();
+ for (String potential : input.split(":")) {
+ if (!potential.startsWith("/data/app/")) {
+ continue;
+ }
+ int start = "/data/app/".length();
+ int end = potential.lastIndexOf(".apk");
+ if (end != potential.length() - 4) {
+ continue;
+ }
+ int dash = potential.indexOf("-");
+ if (dash != -1) {
+ end = dash;
+ }
+ File file = new File("/data/data/" + potential.substring(start, end) + "/cache");
+ if (isWriteableDirectory(file)) {
+ results.add(file);
+ }
+ }
+ return results.toArray(new File[results.size()]);
+ }
+
+ // @VisibleForTesting
+ boolean isWriteableDirectory(File file) {
+ return file.isDirectory() && file.canWrite();
+ }
+}
diff --git a/src/com/google/testing/littlemock/LittleMock.java b/src/com/google/testing/littlemock/LittleMock.java
index 03e2c7a..c8f2b2f 100644
--- a/src/com/google/testing/littlemock/LittleMock.java
+++ b/src/com/google/testing/littlemock/LittleMock.java
@@ -16,6 +16,7 @@
package com.google.testing.littlemock;
+import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@@ -23,10 +24,11 @@ import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Very lightweight and simple mocking framework, inspired by Mockito, http://mockito.org.
@@ -35,9 +37,6 @@ import java.util.concurrent.Callable;
* <ul>
* <li>Doesn't support mocking concrete classes, <b>interfaces only</b>.</li>
*
- * <li><b>Is not thread-safe.</b> You'll need to provide your own synchronization if you want to
- * run multi-threaded unit tests.</li>
- *
* <li>It supports only a <b>small subset</b> of the APIs provided by Mockito and other mocking
* frameworks.</li>
* </ul>
@@ -149,11 +148,21 @@ public class LittleMock {
if (howManyTimes == null) {
throw new IllegalArgumentException("Can't pass null for howManyTimes parameter");
}
+ DefaultInvocationHandler handler = getHandlerFrom(mock);
+ checkState(handler.mHowManyTimes == null, "Unfinished verify() statements");
+ checkState(handler.mStubbingAction == null, "Unfinished stubbing statements");
checkNoMatchers();
- getHandlerFrom(mock).mHowManyTimes = howManyTimes;
- return mock;
+ handler.mHowManyTimes = howManyTimes;
+ sUnfinishedCallCounts.add(howManyTimes);
+ return handler.<T>getVerifyingMock();
}
+ /** The list of outstanding calls to verify() that haven't finished, used to check for errors. */
+ private static List<CallCount> sUnfinishedCallCounts = new ArrayList<CallCount>();
+
+ /** The list of outstanding calls to when() that haven't finished, used to check for errors. */
+ private static List<Action> sUnfinishedStubbingActions = new ArrayList<Action>();
+
/** Begins a verification step for exactly one method call. */
public static <T> T verify(T mock) { return verify(mock, times(1)); }
@@ -161,7 +170,7 @@ public class LittleMock {
public static void verifyZeroInteractions(Object... mocks) {
checkNoMatchers();
for (Object mock : mocks) {
- LinkedList<MethodCall> mMethodCalls = getHandlerFrom(mock).mRecordedCalls;
+ List<MethodCall> mMethodCalls = getHandlerFrom(mock).mRecordedCalls;
expect(mMethodCalls.isEmpty(), "Mock expected zero interactions, had " + mMethodCalls);
}
}
@@ -200,7 +209,12 @@ public class LittleMock {
/** Creates a {@link CallCount} that matches exactly the given number of calls. */
public static CallCount times(long n) { return new CallCount(n, n); }
- /** Creates a {@link CallCount} that only matches if the method was never called. */
+ /** Claims that the verified call must happen before the given timeout. */
+ public static Timeout timeout(long timeoutMillis) {
+ return new Timeout(1, 1, timeoutMillis);
+ }
+
+/** Creates a {@link CallCount} that only matches if the method was never called. */
public static CallCount never() { return new CallCount(0, 0); }
/** Creates a {@link CallCount} that matches at least one method call. */
@@ -362,8 +376,9 @@ public class LittleMock {
}
/** Creates a mock, more easily done via the {@link #initMocks(Object)} method. */
+ @SuppressWarnings("unchecked")
private static <T> T mock(Class<T> clazz, String fieldName) {
- return LittleMock.<T>newProxy(clazz, new DefaultInvocationHandler(clazz, fieldName));
+ return (T) createProxy(clazz, new DefaultInvocationHandler(clazz, fieldName));
}
/** Pick a suitable name for a field of the given clazz. */
@@ -380,7 +395,19 @@ public class LittleMock {
}
/** Use this in tear down to check for programming errors. */
- public static void checkForProgrammingErrorsDuringTearDown() { checkNoMatchers(); }
+ public static void checkForProgrammingErrorsDuringTearDown() {
+ checkNoMatchers();
+ checkNoUnfinishedCalls(sUnfinishedCallCounts, "verify()");
+ checkNoUnfinishedCalls(sUnfinishedStubbingActions, "stubbing");
+ }
+
+ /** Helper function to check that there are no verify or stubbing commands outstanding. */
+ private static void checkNoUnfinishedCalls(List<?> list, String type) {
+ if (!list.isEmpty()) {
+ list.clear();
+ throw new IllegalStateException("Unfinished " + type + " statements");
+ }
+ }
/** Implementation of {@link Behaviour}. */
private static class BehaviourImpl implements Behaviour {
@@ -392,8 +419,12 @@ public class LittleMock {
@Override
public <T> T when(T mock) {
- getHandlerFrom(mock).mStubbingAction = mAction;
- return mock;
+ DefaultInvocationHandler handler = getHandlerFrom(mock);
+ checkState(handler.mHowManyTimes == null, "Unfinished verify() statements");
+ checkState(handler.mStubbingAction == null, "Unfinished stubbing statements");
+ handler.mStubbingAction = mAction;
+ sUnfinishedStubbingActions.add(mAction);
+ return handler.<T>getStubbingMock();
}
}
@@ -478,9 +509,10 @@ public class LittleMock {
private final String mFieldName;
/** The list of method calls executed on the mock. */
- private LinkedList<MethodCall> mRecordedCalls = new LinkedList<MethodCall>();
+ private List<MethodCall> mRecordedCalls = new CopyOnWriteArrayList<MethodCall>();
/** The list of method calls that were stubbed out and their corresponding actions. */
- private LinkedList<StubbedCall> mStubbedCalls = new LinkedList<StubbedCall>();
+ private List<StubbedCall> mStubbedCalls = new CopyOnWriteArrayList<StubbedCall>();
+
/**
* The number of times a given call should be verified.
*
@@ -490,6 +522,7 @@ public class LittleMock {
* <p>It is reset to null once the verification has occurred.
*/
private CallCount mHowManyTimes = null;
+
/**
* The action to be associated with the stubbed method.
*
@@ -498,6 +531,12 @@ public class LittleMock {
*/
private Action mStubbingAction = null;
+ /** Dynamic proxy used to verify calls against this mock. */
+ private final Object mVerifyingMock;
+
+ /** Dynamic proxy used to stub calls against this mock. */
+ private final Object mStubbingMock;
+
/**
* Creates a new invocation handler for an object.
*
@@ -509,24 +548,84 @@ public class LittleMock {
public DefaultInvocationHandler(Class<?> clazz, String fieldName) {
mClazz = clazz;
mFieldName = fieldName;
+ mVerifyingMock = createVerifyingMock(clazz);
+ mStubbingMock = createStubbingMock(clazz);
+ }
+
+ // Safe if you call getHandlerFrom(mock).getVerifyingMock(), since this is guaranteed to be
+ // of the same type as mock itself.
+ @SuppressWarnings("unchecked")
+ public <T> T getVerifyingMock() {
+ return (T) mVerifyingMock;
+ }
+
+ // Safe if you call getHandlerFrom(mock).getStubbingMock(), since this is guaranteed to be
+ // of the same type as mock itself.
+ @SuppressWarnings("unchecked")
+ public <T> T getStubbingMock() {
+ return (T) mStubbingMock;
+ }
+
+ /** Used to check that we always stub and verify from the same thread. */
+ private AtomicReference<Thread> mCurrentThread = new AtomicReference<Thread>();
+
+ /** Check that we are stubbing and verifying always from the same thread. */
+ private void checkThread() {
+ Thread currentThread = Thread.currentThread();
+ mCurrentThread.compareAndSet(null, currentThread);
+ if (mCurrentThread.get() != currentThread) {
+ throw new IllegalStateException("Must always mock and stub from one thread only. "
+ + "This thread: " + currentThread + ", the other thread: " + mCurrentThread.get());
+ }
+ }
+
+ /** Generate the dynamic proxy that will handle verify invocations. */
+ private Object createVerifyingMock(Class<?> clazz) {
+ return createProxy(clazz, new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ checkThread();
+ expect(mHowManyTimes != null, "verifying mock doesn't know how many times");
+ try {
+ ArgumentMatcher[] matchers = checkClearAndGetMatchers(method);
+ StackTraceElement callSite = new Exception().getStackTrace()[2];
+ MethodCall methodCall = new MethodCall(method, callSite, args);
+ innerVerify(method, matchers, methodCall, proxy, callSite, mHowManyTimes);
+ return defaultReturnValue(method.getReturnType());
+ } finally {
+ sUnfinishedCallCounts.remove(mHowManyTimes);
+ mHowManyTimes = null;
+ }
+ }
+ });
+ }
+
+ /** Generate the dynamic proxy that will handle stubbing invocations. */
+ private Object createStubbingMock(Class<?> clazz) {
+ return createProxy(clazz, new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ checkThread();
+ expect(mStubbingAction != null, "stubbing mock doesn't know what action to perform");
+ try {
+ ArgumentMatcher[] matchers = checkClearAndGetMatchers(method);
+ StackTraceElement callSite = new Exception().getStackTrace()[2];
+ MethodCall methodCall = new MethodCall(method, callSite, args);
+ innerStub(method, matchers, methodCall, callSite, mStubbingAction);
+ return defaultReturnValue(method.getReturnType());
+ } finally {
+ sUnfinishedStubbingActions.remove(mStubbingAction);
+ mStubbingAction = null;
+ }
+ }
+ });
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- ArgumentMatcher[] matchers = checkClearAndGetMatchers(method);
StackTraceElement callSite = new Exception().getStackTrace()[2];
MethodCall methodCall = new MethodCall(method, callSite, args);
- if (mHowManyTimes != null) {
- innerVerify(method, matchers, methodCall, proxy, callSite);
- mHowManyTimes = null;
- return defaultReturnValue(method.getReturnType());
- } else if (mStubbingAction != null) {
- innerStub(method, matchers, methodCall, callSite);
- mStubbingAction = null;
- return defaultReturnValue(method.getReturnType());
- } else {
- return innerRecord(method, args, methodCall, proxy, callSite);
- }
+ return innerRecord(method, args, methodCall, proxy, callSite);
}
/**
@@ -579,13 +678,12 @@ public class LittleMock {
}
private void innerStub(Method method, final ArgumentMatcher[] matchers, MethodCall methodCall,
- StackTraceElement callSite) {
- final Action stubbingAction = mStubbingAction;
+ StackTraceElement callSite, final Action stubbingAction) {
checkSpecialObjectMethods(method, "stub");
checkThisActionCanBeUsedForThisMethod(method, stubbingAction, callSite);
if (matchers.length == 0) {
// If there are no matchers, then this is a simple stubbed method call with an action.
- mStubbedCalls.addFirst(new StubbedCall(methodCall, stubbingAction));
+ mStubbedCalls.add(0, new StubbedCall(methodCall, stubbingAction));
return;
}
// If there are matchers, then we need to make a new method call which matches only
@@ -607,7 +705,7 @@ public class LittleMock {
return stubbingAction.getReturnType();
}
};
- mStubbedCalls.addFirst(new StubbedCall(matchMatchersMethodCall, setCapturesThenAction));
+ mStubbedCalls.add(0, new StubbedCall(matchMatchersMethodCall, setCapturesThenAction));
}
private void checkThisActionCanBeUsedForThisMethod(Method method, final Action stubbingAction,
@@ -645,8 +743,32 @@ public class LittleMock {
}
private void innerVerify(Method method, ArgumentMatcher[] matchers, MethodCall methodCall,
- Object proxy, StackTraceElement callSite) {
+ Object proxy, StackTraceElement callSite, CallCount callCount) {
checkSpecialObjectMethods(method, "verify");
+ int total = countMatchingInvocations(method, matchers, methodCall);
+ long callTimeout = callCount.getTimeout();
+ if (callTimeout > 0) {
+ long endTime = System.currentTimeMillis() + callTimeout;
+ while (!callCount.matches(total)) {
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ fail("interrupted whilst waiting to verify");
+ }
+ if (System.currentTimeMillis() > endTime) {
+ fail(formatFailedVerifyMessage(methodCall, total, callTimeout, callCount));
+ }
+ total = countMatchingInvocations(method, matchers, methodCall);
+ }
+ } else {
+ if (!callCount.matches(total)) {
+ fail(formatFailedVerifyMessage(methodCall, total, 0, callCount));
+ }
+ }
+ }
+
+ private int countMatchingInvocations(Method method, ArgumentMatcher[] matchers,
+ MethodCall methodCall) {
int total = 0;
for (MethodCall call : mRecordedCalls) {
if (call.mMethod.equals(method)) {
@@ -658,17 +780,22 @@ public class LittleMock {
}
}
}
- expect(mHowManyTimes.matches(total), formatFailedVerifyMessage(methodCall, total));
+ return total;
}
- private String formatFailedVerifyMessage(MethodCall methodCall, int total) {
+ private String formatFailedVerifyMessage(MethodCall methodCall, int total, long timeoutMillis,
+ CallCount callCount) {
StringBuffer sb = new StringBuffer();
- sb.append("\nExpected ").append(mHowManyTimes).append(" to:");
+ sb.append("\nExpected ").append(callCount);
+ if (timeoutMillis > 0) {
+ sb.append(" within " + timeoutMillis + "ms");
+ }
+ sb.append(" to:");
appendDebugStringForMethodCall(sb, methodCall.mMethod,
methodCall.mElement, mFieldName, false);
sb.append("\n\n");
if (mRecordedCalls.size() == 0) {
- sb.append("No method calls happened to this mock");
+ sb.append("No method calls happened on this mock");
} else {
sb.append("Method calls that did happen:");
for (MethodCall recordedCall : mRecordedCalls) {
@@ -768,10 +895,10 @@ public class LittleMock {
/** Encapsulates the number of times a method is called, between upper and lower bounds. */
private static class CallCount {
- long mLowerBound;
- long mUpperBound;
+ private long mLowerBound;
+ private long mUpperBound;
- private CallCount(long lowerBound, long upperBound) {
+ public CallCount(long lowerBound, long upperBound) {
mLowerBound = lowerBound;
mUpperBound = upperBound;
}
@@ -781,6 +908,21 @@ public class LittleMock {
return total >= mLowerBound && total <= mUpperBound;
}
+ /** Tells us how long we should block waiting for the verify to happen. */
+ public long getTimeout() {
+ return 0;
+ }
+
+ public CallCount setLowerBound(long lowerBound) {
+ mLowerBound = lowerBound;
+ return this;
+ }
+
+ public CallCount setUpperBound(long upperBound) {
+ mUpperBound = upperBound;
+ return this;
+ }
+
@Override
public String toString() {
if (mLowerBound == mUpperBound) {
@@ -792,6 +934,26 @@ public class LittleMock {
}
}
+ /** Encapsulates adding number of times behaviour to a call count with timeout. */
+ public static final class Timeout extends CallCount {
+ private long mTimeoutMillis;
+
+ public Timeout(long lowerBound, long upperBound, long timeoutMillis) {
+ super(lowerBound, upperBound);
+ mTimeoutMillis = timeoutMillis;
+ }
+
+ @Override
+ public long getTimeout() {
+ return mTimeoutMillis;
+ }
+
+ public CallCount times(int times) { return setLowerBound(times).setUpperBound(times); }
+ public CallCount atLeast(long n) { return setLowerBound(n).setUpperBound(Long.MAX_VALUE); }
+ public CallCount atLeastOnce() { return setLowerBound(1).setUpperBound(Long.MAX_VALUE); }
+ public CallCount between(long n, long m) { return setLowerBound(n).setUpperBound(m); }
+ }
+
/** Helper method to add an 's' to a string iff the count is not 1. */
private static String plural(String prefix, long count) {
return (count == 1) ? prefix : (prefix + "s");
@@ -861,12 +1023,6 @@ public class LittleMock {
return (DefaultInvocationHandler) invocationHandler;
}
- /** Builds a dynamic proxy that implements the given interface, delegating to given handler. */
- @SuppressWarnings("unchecked")
- private static <T> T newProxy(Class<?> theInterface, InvocationHandler theHandler) {
- return (T) Proxy.newProxyInstance(getClassLoader(), new Class<?>[]{ theInterface }, theHandler);
- }
-
/** Gets a suitable class loader for use with the proxy. */
private static ClassLoader getClassLoader() {
return LittleMock.class.getClassLoader();
@@ -878,4 +1034,32 @@ public class LittleMock {
field.set(object, value);
field.setAccessible(false);
}
+
+ /** Helper method to throw an IllegalStateException if given condition is not met. */
+ private static void checkState(boolean condition, String message) {
+ if (!condition) {
+ throw new IllegalStateException(message);
+ }
+ }
+
+ /** Create a dynamic proxy for the given class, delegating to the given invocation handler. */
+ private static Object createProxy(Class<?> clazz, InvocationHandler handler) {
+ if (clazz.isInterface()) {
+ return Proxy.newProxyInstance(getClassLoader(), new Class<?>[] { clazz }, handler);
+ }
+ try {
+ Class<?> proxyBuilder = Class.forName("com.google.dexmaker.stock.ProxyBuilder");
+ Method forClassMethod = proxyBuilder.getMethod("forClass", Class.class);
+ Object builder = forClassMethod.invoke(null, clazz);
+ Method handlerMethod = builder.getClass().getMethod("handler", InvocationHandler.class);
+ builder = handlerMethod.invoke(builder, handler);
+ Method dexCacheMethod = builder.getClass().getMethod("dexCache", File.class);
+ File directory = AppDataDirGuesser.getsInstance().guessSuitableDirectoryForGeneratedClasses();
+ builder = dexCacheMethod.invoke(builder, directory);
+ Method buildMethod = builder.getClass().getMethod("build");
+ return buildMethod.invoke(builder);
+ } catch (Exception e) {
+ throw new IllegalStateException("Could not mock this concrete class", e);
+ }
+ }
}
diff --git a/tests/com/google/testing/littlemock/AppDataDirGuesserTest.java b/tests/com/google/testing/littlemock/AppDataDirGuesserTest.java
new file mode 100644
index 0000000..b537097
--- /dev/null
+++ b/tests/com/google/testing/littlemock/AppDataDirGuesserTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.testing.littlemock;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class AppDataDirGuesserTest extends TestCase {
+ public void testGuessCacheDir_SimpleExample() {
+ guessCacheDirFor("/data/app/a.b.c.apk").shouldGive("/data/data/a.b.c/cache");
+ guessCacheDirFor("/data/app/a.b.c.tests.apk").shouldGive("/data/data/a.b.c.tests/cache");
+ }
+
+ public void testGuessCacheDir_MultipleResultsSeparatedByColon() {
+ guessCacheDirFor("/data/app/a.b.c.apk:/data/app/d.e.f.apk")
+ .shouldGive("/data/data/a.b.c/cache", "/data/data/d.e.f/cache");
+ }
+
+ public void testGuessCacheDir_NotWriteableSkipped() {
+ guessCacheDirFor("/data/app/a.b.c.apk:/data/app/d.e.f.apk")
+ .withNonWriteable("/data/data/a.b.c/cache")
+ .shouldGive("/data/data/d.e.f/cache");
+ }
+
+ public void testGuessCacheDir_StripHyphenatedSuffixes() {
+ guessCacheDirFor("/data/app/a.b.c-2.apk").shouldGive("/data/data/a.b.c/cache");
+ }
+
+ public void testGuessCacheDir_LeadingAndTrailingColonsIgnored() {
+ guessCacheDirFor("/data/app/a.b.c.apk:asdf:").shouldGive("/data/data/a.b.c/cache");
+ guessCacheDirFor(":asdf:/data/app/a.b.c.apk").shouldGive("/data/data/a.b.c/cache");
+ }
+
+ public void testGuessCacheDir_InvalidInputsGiveEmptyArray() {
+ guessCacheDirFor("").shouldGive();
+ }
+
+ public void testGuessCacheDir_JarsIgnored() {
+ guessCacheDirFor("/data/app/a.b.c.jar").shouldGive();
+ guessCacheDirFor("/system/framework/android.test.runner.jar").shouldGive();
+ }
+
+ public void testGuessCacheDir_RealWorldExample() {
+ String realPath = "/system/framework/android.test.runner.jar:" +
+ "/data/app/com.google.android.voicesearch.tests-2.apk:" +
+ "/data/app/com.google.android.voicesearch-1.apk";
+ guessCacheDirFor(realPath)
+ .withNonWriteable("/data/data/com.google.android.voicesearch.tests/cache")
+ .shouldGive("/data/data/com.google.android.voicesearch/cache");
+ }
+
+ private interface TestCondition {
+ TestCondition withNonWriteable(String... files);
+ void shouldGive(String... files);
+ }
+
+ private TestCondition guessCacheDirFor(final String path) {
+ final Set<String> notWriteable = new HashSet<String>();
+ return new TestCondition() {
+ @Override
+ public void shouldGive(String... files) {
+ AppDataDirGuesser guesser = new AppDataDirGuesser() {
+ @Override
+ public boolean isWriteableDirectory(File file) {
+ return !notWriteable.contains(file.getAbsolutePath());
+ }
+ };
+ File[] results = guesser.guessPath(path);
+ assertNotNull("Null results for " + path, results);
+ assertEquals("Bad lengths for " + path, files.length, results.length);
+ for (int i = 0; i < files.length; ++i) {
+ assertEquals("Element " + i, new File(files[i]), results[i]);
+ }
+ }
+
+ @Override
+ public TestCondition withNonWriteable(String... files) {
+ notWriteable.addAll(Arrays.asList(files));
+ return this;
+ }
+ };
+ }
+}
diff --git a/tests/com/google/testing/littlemock/LittleMockTest.java b/tests/com/google/testing/littlemock/LittleMockTest.java
index 9d3cf38..e8c7b1e 100644
--- a/tests/com/google/testing/littlemock/LittleMockTest.java
+++ b/tests/com/google/testing/littlemock/LittleMockTest.java
@@ -42,6 +42,7 @@ import static com.google.testing.littlemock.LittleMock.isA;
import static com.google.testing.littlemock.LittleMock.mock;
import static com.google.testing.littlemock.LittleMock.never;
import static com.google.testing.littlemock.LittleMock.reset;
+import static com.google.testing.littlemock.LittleMock.timeout;
import static com.google.testing.littlemock.LittleMock.times;
import static com.google.testing.littlemock.LittleMock.verify;
import static com.google.testing.littlemock.LittleMock.verifyNoMoreInteractions;
@@ -50,16 +51,18 @@ import static com.google.testing.littlemock.LittleMock.verifyZeroInteractions;
import junit.framework.TestCase;
import java.io.IOException;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
/**
* Unit tests for the LittleMock class.
@@ -67,16 +70,6 @@ import java.util.concurrent.Callable;
* @author hugohudson@gmail.com (Hugo Hudson)
*/
public class LittleMockTest extends TestCase {
- /**
- * Used in these unit tests to indicate that the method should throw a given type of exception.
- */
- @Target({ ElementType.METHOD })
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ShouldThrow {
- public Class<? extends Throwable> value();
- public String[] messages() default {};
- }
-
@Mock private Foo mFoo;
@Mock private Bar mBar;
@Mock private BarSubtype mBarSubtype;
@@ -84,36 +77,20 @@ public class LittleMockTest extends TestCase {
@Captor private ArgumentCaptor<String> mCaptureAnotherString;
@Captor private ArgumentCaptor<Integer> mCaptureInteger;
@Captor private ArgumentCaptor<Callback> mCaptureCallback;
+ private ExecutorService mExecutorService;
@Override
protected void setUp() throws Exception {
super.setUp();
LittleMock.initMocks(this);
+ mExecutorService = Executors.newCachedThreadPool();
}
@Override
- protected void runTest() throws Throwable {
- Method method = getClass().getMethod(getName(), (Class[]) null);
- ShouldThrow shouldThrowAnnotation = method.getAnnotation(ShouldThrow.class);
- if (shouldThrowAnnotation != null) {
- try {
- super.runTest();
- fail("Should have thrown " + shouldThrowAnnotation.value());
- } catch (Throwable e) {
- if (!e.getClass().equals(shouldThrowAnnotation.value())) {
- fail("Should have thrown " + shouldThrowAnnotation.value() + " but threw " + e);
- }
- for (String requiredSubstring : shouldThrowAnnotation.messages()) {
- if (!e.getMessage().contains(requiredSubstring)) {
- fail("Error message didn't contain " + requiredSubstring + ", was " + e.getMessage());
- }
- }
- // Good, test passes.
- }
- } else {
- super.runTest();
+ protected void tearDown() throws Exception {
+ mExecutorService.shutdown();
+ super.tearDown();
}
- }
/** Simple interface for testing against. */
public interface Callback {
@@ -186,14 +163,18 @@ public class LittleMockTest extends TestCase {
assertEquals(null, mFoo.anInterface());
}
- @ShouldThrow(IllegalArgumentException.class)
public void testVerify_FailsIfNotDoneOnAProxy() {
- verify("hello").contains("something");
+ try {
+ verify("hello").contains("something");
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
- @ShouldThrow(IllegalArgumentException.class)
public void testVerify_FailsIfNotCreatedByOurMockMethod() {
- verify(createNotARealMock()).add("something");
+ try {
+ verify(createNotARealMock()).add("something");
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
public void testVerify_SuccessfulVerification() {
@@ -215,22 +196,28 @@ public class LittleMockTest extends TestCase {
verify(mFoo).add("something");
}
- @ShouldThrow(AssertionError.class)
public void testVerify_MeansOnlyOnceSoShouldFailIfCalledTwice() {
mFoo.add("something");
mFoo.add("something");
- verify(mFoo).add("something");
+ try {
+ verify(mFoo).add("something");
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(AssertionError.class)
public void testVerify_FailedVerification_CalledWithWrongArgument() {
mFoo.add("something else");
- verify(mFoo).add("something");
+ try {
+ verify(mFoo).add("something");
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(AssertionError.class)
public void testVerify_FailedVerification_WasNeverCalled() {
- verify(mFoo).add("something");
+ try {
+ verify(mFoo).add("something");
+ fail();
+ } catch (AssertionError expected) {}
}
public void testVerify_TimesTwice_Succeeds() {
@@ -239,25 +226,31 @@ public class LittleMockTest extends TestCase {
verify(mFoo, LittleMock.times(2)).add("jim");
}
- @ShouldThrow(AssertionError.class)
public void testVerify_TimesTwice_ButThreeTimesFails() {
mFoo.add("jim");
mFoo.add("jim");
mFoo.add("jim");
- verify(mFoo, LittleMock.times(2)).add("jim");
+ try {
+ verify(mFoo, LittleMock.times(2)).add("jim");
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(AssertionError.class)
public void testVerify_TimesTwice_ButOnceFails() {
mFoo.add("jim");
- verify(mFoo, LittleMock.times(2)).add("jim");
+ try {
+ verify(mFoo, LittleMock.times(2)).add("jim");
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(AssertionError.class)
public void testVerify_TimesTwice_DifferentStringsFails() {
mFoo.add("jim");
mFoo.add("bob");
- verify(mFoo, LittleMock.times(2)).add("jim");
+ try {
+ verify(mFoo, LittleMock.times(2)).add("jim");
+ fail();
+ } catch (AssertionError expected) {}
}
public void testVerify_TimesTwice_WorksWithAnyString() {
@@ -266,18 +259,22 @@ public class LittleMockTest extends TestCase {
verify(mFoo, LittleMock.times(2)).add(anyString());
}
- @ShouldThrow(AssertionError.class)
public void testVerify_TimesTwice_FailsIfJustOnceWithAnyString() {
mFoo.add("bob");
- verify(mFoo, LittleMock.times(2)).add(anyString());
+ try {
+ verify(mFoo, LittleMock.times(2)).add(anyString());
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(AssertionError.class)
public void testVerify_TimesTwice_FailsIfThreeTimesWithAnyString() {
mFoo.add("bob");
mFoo.add("jim");
mFoo.add("james");
- verify(mFoo, LittleMock.times(2)).add(anyString());
+ try {
+ verify(mFoo, LittleMock.times(2)).add(anyString());
+ fail();
+ } catch (AssertionError expected) {}
}
public void testVerify_Never_Succeeds() {
@@ -285,10 +282,12 @@ public class LittleMockTest extends TestCase {
verify(mFoo, never()).anInt();
}
- @ShouldThrow(AssertionError.class)
public void testVerify_Never_FailsIfWasCalled() {
mFoo.add("jim");
- verify(mFoo, never()).add("jim");
+ try {
+ verify(mFoo, never()).add("jim");
+ fail();
+ } catch (AssertionError expected) {}
}
public void testVerify_Never_PassesIfArgumentsDontMatch() {
@@ -308,9 +307,11 @@ public class LittleMockTest extends TestCase {
verify(mFoo, atLeastOnce()).add("jim");
}
- @ShouldThrow(AssertionError.class)
public void testVerify_AtLeastOnce_FailsForNoCalls() {
- verify(mFoo, atLeastOnce()).add("jim");
+ try {
+ verify(mFoo, atLeastOnce()).add("jim");
+ fail();
+ } catch (AssertionError expected) {}
}
public void testVerify_AtLeastThreeTimes_SuceedsForThreeCalls() {
@@ -329,11 +330,13 @@ public class LittleMockTest extends TestCase {
verify(mFoo, atLeast(3)).add("jim");
}
- @ShouldThrow(AssertionError.class)
public void testVerify_AtLeastThreeTimes_FailsForTwoCalls() {
mFoo.add("jim");
mFoo.add("jim");
- verify(mFoo, atLeast(3)).add("jim");
+ try {
+ verify(mFoo, atLeast(3)).add("jim");
+ fail();
+ } catch (AssertionError expected) {}
}
public void testVerify_AtMostThreeTimes_SuceedsForThreeCalls() {
@@ -343,14 +346,16 @@ public class LittleMockTest extends TestCase {
verify(mFoo, atMost(3)).add("jim");
}
- @ShouldThrow(AssertionError.class)
public void testVerify_AtMostThreeTimes_FailsForFiveCalls() {
mFoo.add("jim");
mFoo.add("jim");
mFoo.add("jim");
mFoo.add("jim");
mFoo.add("jim");
- verify(mFoo, atMost(3)).add("jim");
+ try {
+ verify(mFoo, atMost(3)).add("jim");
+ fail();
+ } catch (AssertionError expected) {}
}
public void testVerify_AtMostThreeTimes_SucceedsForTwoCalls() {
@@ -377,20 +382,24 @@ public class LittleMockTest extends TestCase {
verify(mFoo, between(2, 4)).add("jim");
}
- @ShouldThrow(AssertionError.class)
public void testVerify_BetweenTwoAndFour_FailsForOneCall() {
mFoo.add("jim");
- verify(mFoo, between(2, 4)).add("jim");
+ try {
+ verify(mFoo, between(2, 4)).add("jim");
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(AssertionError.class)
public void testVerify_BetweenTwoAndFour_FailsForFiveCalls() {
mFoo.add("jim");
mFoo.add("jim");
mFoo.add("jim");
mFoo.add("jim");
mFoo.add("jim");
- verify(mFoo, LittleMock.between(2, 4)).add("jim");
+ try {
+ verify(mFoo, LittleMock.between(2, 4)).add("jim");
+ fail();
+ } catch (AssertionError expected) {}
}
public void testDoReturnWhen_SimpleReturn() {
@@ -416,14 +425,18 @@ public class LittleMockTest extends TestCase {
assertEquals(null, mFoo.get(2));
}
- @ShouldThrow(IllegalArgumentException.class)
public void testDoReturnWhen_CalledOnString() {
- doReturn("first").when("hello").contains("something");
+ try {
+ doReturn("first").when("hello").contains("something");
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
- @ShouldThrow(IllegalArgumentException.class)
public void testDoReturnWhen_CalledOnNonMock() {
- doReturn("first").when(createNotARealMock()).get(0);
+ try {
+ doReturn("first").when(createNotARealMock()).get(0);
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
public void testDoReturnWhen_CanAlsoBeVerified() {
@@ -469,10 +482,12 @@ public class LittleMockTest extends TestCase {
assertEquals(null, mFoo.anInterface());
}
- @ShouldThrow(RuntimeException.class)
public void testDoThrow_SimpleException() {
doThrow(new RuntimeException()).when(mFoo).aDouble();
- mFoo.aDouble();
+ try {
+ mFoo.aDouble();
+ fail();
+ } catch (RuntimeException expected) {}
}
public void testDoThrow_IfItDoesntMatchItIsntThrown() {
@@ -480,17 +495,17 @@ public class LittleMockTest extends TestCase {
mFoo.aChar();
}
- @ShouldThrow(RuntimeException.class)
public void testDoThrow_KeepsThrowingForever() {
doThrow(new RuntimeException()).when(mFoo).aDouble();
try {
mFoo.aDouble();
- fail("Should have thrown a RuntimeException");
- } catch (RuntimeException e) {
- // Expected.
- }
+ fail();
+ } catch (RuntimeException expected) {}
// This second call should also throw a RuntimeException.
- mFoo.aDouble();
+ try {
+ mFoo.aDouble();
+ fail();
+ } catch (RuntimeException expected) {}
}
public void testDoNothing() {
@@ -502,21 +517,25 @@ public class LittleMockTest extends TestCase {
verifyZeroInteractions(mFoo);
}
- @ShouldThrow(AssertionError.class)
public void testVerifyZeroInteractions_FailsIfSomethingHasHappened() {
mFoo.aBoolean();
- verifyZeroInteractions(mFoo);
+ try {
+ verifyZeroInteractions(mFoo);
+ fail();
+ } catch (AssertionError expected) {}
}
public void testVerifyZeroInteractions_HappyWithMultipleArguments() {
verifyZeroInteractions(mFoo, mBar);
}
- @ShouldThrow(AssertionError.class)
public void testVerifyZeroInteractions_ShouldFailEvenIfOtherInteractionsWereFirstVerified() {
mFoo.add("test");
verify(mFoo).add("test");
- verifyZeroInteractions(mFoo);
+ try {
+ verifyZeroInteractions(mFoo);
+ fail();
+ } catch (AssertionError expected) {}
}
public void testVerifyEq_Method() {
@@ -534,22 +553,28 @@ public class LittleMockTest extends TestCase {
verify(mBar).mixedArguments(eq(8), eq("test"));
}
- @ShouldThrow(AssertionError.class)
public void testVerifyEq_MethodFailsIfNotEqual() {
mFoo.add("bob");
- verify(mFoo).add(eq("jim"));
+ try {
+ verify(mFoo).add(eq("jim"));
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(AssertionError.class)
public void testVerifyEq_MethodFailsIfJustOneIsNotEqual() {
mBar.twoStrings("first", "second");
- verify(mBar).twoStrings(eq("first"), eq("third"));
+ try {
+ verify(mBar).twoStrings(eq("first"), eq("third"));
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(AssertionError.class)
public void testVerifyEq_MethodFailsIfSameParamsButInWrongOrder() {
mBar.twoStrings("first", "second");
- verify(mBar).twoStrings(eq("second"), eq("first"));
+ try {
+ verify(mBar).twoStrings(eq("second"), eq("first"));
+ fail();
+ } catch (AssertionError expected) {}
}
public void testCapture_SimpleCapture() {
@@ -622,14 +647,6 @@ public class LittleMockTest extends TestCase {
assertEquals(newList("whinny", "jessica"), mCaptureAnotherString.getAllValues());
}
- public static <T> List<T> newList(T... things) {
- ArrayList<T> list = new ArrayList<T>();
- for (T thing : things) {
- list.add(thing);
- }
- return list;
- }
-
public void testAnyString() {
doReturn("jim").when(mFoo).lookup(anyString());
assertEquals("jim", mFoo.lookup("barney"));
@@ -641,10 +658,12 @@ public class LittleMockTest extends TestCase {
verify(mFoo).takesObject(anyString());
}
- @ShouldThrow(AssertionError.class)
public void testAnyString_ObjectValue() {
mFoo.takesObject(new Object());
- verify(mFoo).takesObject(anyString());
+ try {
+ verify(mFoo).takesObject(anyString());
+ fail();
+ } catch (AssertionError expected) {}
}
public void testAnyObject() {
@@ -696,11 +715,13 @@ public class LittleMockTest extends TestCase {
verifyZeroInteractions(mFoo);
}
- @ShouldThrow(AssertionError.class)
public void testReset_VerifyFailsAfterReset() {
mFoo.aByte();
reset(mFoo);
- verify(mFoo).aByte();
+ try {
+ verify(mFoo).aByte();
+ fail();
+ } catch (AssertionError expected) {}
}
public void testCapture_BothBeforeAndAfter() {
@@ -738,7 +759,6 @@ public class LittleMockTest extends TestCase {
verify(mFoo).add("yes");
}
- @ShouldThrow(IOException.class)
public void testDoAction_CanThrowDeclaredException() throws Exception {
doAnswer(new Callable<Void>() {
@Override
@@ -746,10 +766,12 @@ public class LittleMockTest extends TestCase {
throw new IOException("Problem");
}
}).when(mFoo).exceptionThrower();
- mFoo.exceptionThrower();
+ try {
+ mFoo.exceptionThrower();
+ fail();
+ } catch (IOException expected) {}
}
- @ShouldThrow(RuntimeException.class)
public void testDoAction_CanThrowUndelcaredException() {
doAnswer(new Callable<Void>() {
@Override
@@ -757,7 +779,10 @@ public class LittleMockTest extends TestCase {
throw new RuntimeException("Problem");
}
}).when(mFoo).aBoolean();
- mFoo.aBoolean();
+ try {
+ mFoo.aBoolean();
+ fail();
+ } catch (RuntimeException expected) {}
}
public void testRunThisIsAnAliasForDoAction() {
@@ -796,10 +821,12 @@ public class LittleMockTest extends TestCase {
verifyNoMoreInteractions(mFoo, mBar);
}
- @ShouldThrow(AssertionError.class)
public void testVerifyNoMoreInteractions_FailsWhenOneActionWasNotVerified() {
mFoo.aBoolean();
- verifyNoMoreInteractions(mFoo, mBar);
+ try {
+ verifyNoMoreInteractions(mFoo, mBar);
+ fail();
+ } catch (AssertionError expected) {}
}
public void testVerifyNoMoreInteractions_SucceedsWhenAllActionsWereVerified() {
@@ -809,13 +836,15 @@ public class LittleMockTest extends TestCase {
verifyNoMoreInteractions(mFoo);
}
- @ShouldThrow(AssertionError.class)
public void testVerifyNoMoreInteractions_FailsWhenMostButNotAllActionsWereVerified() {
mFoo.get(3);
mFoo.get(20);
mFoo.aByte();
verify(mFoo, atLeastOnce()).get(anyInt());
- verifyNoMoreInteractions(mFoo);
+ try {
+ verifyNoMoreInteractions(mFoo);
+ fail();
+ } catch (AssertionError expected) {}
}
public void testVerifyNoMoreInteractions_ShouldIngoreStubbedCalls() {
@@ -824,17 +853,20 @@ public class LittleMockTest extends TestCase {
verifyNoMoreInteractions(mFoo);
}
- @ShouldThrow(IllegalArgumentException.class)
public void testMatchers_WrongNumberOfMatchersOnStubbingCausesError() {
- doReturn("hi").when(mBar).twoStrings("jim", eq("bob"));
+ try {
+ doReturn("hi").when(mBar).twoStrings("jim", eq("bob"));
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
- @ShouldThrow(IllegalArgumentException.class)
public void testMatchers_WrongNumberOfMatchersOnVerifyCausesError() {
- verify(mBar).twoStrings("jim", eq("bob"));
+ try {
+ verify(mBar).twoStrings("jim", eq("bob"));
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
- @ShouldThrow(IllegalStateException.class)
public void testCreateACaptureButDontUseItShouldFailAtNextVerify() {
// If we create a capture illegally, outside of a method call, like so:
mCaptureString.capture();
@@ -843,23 +875,29 @@ public class LittleMockTest extends TestCase {
// call on the mock object.
// Thus we should check in the verify() method that there are *no matchers* on the static
// list, as this would indicate a programming error such as the above.
- verify(mFoo, anyTimes()).aBoolean();
+ try {
+ verify(mFoo, anyTimes()).aBoolean();
+ fail();
+ } catch (IllegalStateException expected) {}
}
- @ShouldThrow(IllegalStateException.class)
public void testCreateACaptureButDontUseItShouldFailAtNextVerify_AlsoNoMoreInteractions() {
// Same result desired as in previous test.
mCaptureString.capture();
- verifyNoMoreInteractions(mFoo);
+ try {
+ verifyNoMoreInteractions(mFoo);
+ fail();
+ } catch (IllegalStateException expected) {}
}
- @ShouldThrow(IllegalStateException.class)
public void testCreateACaptureButDontUseItShouldFailAtNextVerify_AlsoZeroInteraction() {
mCaptureString.capture();
- verifyZeroInteractions(mFoo);
+ try {
+ verifyZeroInteractions(mFoo);
+ fail();
+ } catch (IllegalStateException expected) {}
}
- @ShouldThrow(IllegalStateException.class)
public void testCheckStaticVariablesMethod() {
// To help us avoid programming errors, I'm adding a method that you can call from tear down,
// which will explode if there is anything still left in your static variables at the end
@@ -867,12 +905,17 @@ public class LittleMockTest extends TestCase {
// variable (so that the next test won't fail). It should fail if we create a matcher
// be it a capture or anything else that is then not consumed.
anyInt();
- checkForProgrammingErrorsDuringTearDown();
+ try {
+ checkForProgrammingErrorsDuringTearDown();
+ fail();
+ } catch (IllegalStateException expected) {}
}
- @ShouldThrow(IllegalArgumentException.class)
public void testCantPassNullToVerifyCount() {
- verify(mFoo, null).aBoolean();
+ try {
+ verify(mFoo, null).aBoolean();
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
public void testInjectionInNestedClasses() throws Exception {
@@ -901,10 +944,12 @@ public class LittleMockTest extends TestCase {
verify(mFoo).takesObject(isA(String.class));
}
- @ShouldThrow(AssertionError.class)
public void testIsA_FailsWithSuperclass() {
mFoo.takesObject(new Object());
- verify(mFoo).takesObject(isA(String.class));
+ try {
+ verify(mFoo).takesObject(isA(String.class));
+ fail();
+ } catch (AssertionError expected) {}
}
public void testIsA_WillAcceptNull() {
@@ -918,49 +963,57 @@ public class LittleMockTest extends TestCase {
verify(mFoo).takesBar(isA(BarSubtype.class));
}
- @ShouldThrow(AssertionError.class)
public void testIsA_MatchSubtypeFailed() {
mFoo.takesBar(mBar);
- verify(mFoo).takesBar(isA(BarSubtype.class));
+ try {
+ verify(mFoo).takesBar(isA(BarSubtype.class));
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(value = AssertionError.class,
- messages = {"cannot verify call to", "boolean java.lang.Object.equals(java.lang.Object)"})
public void testVerifyEquals_ShouldFail() {
mFoo.equals(null);
- verify(mFoo).equals(null);
+ try {
+ verify(mFoo).equals(null);
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(value = AssertionError.class,
- messages = {"cannot verify call to", "int java.lang.Object.hashCode()"})
public void testVerifyHashCode_ShouldFail() {
mFoo.hashCode();
- verify(mFoo).hashCode();
+ try {
+ verify(mFoo).hashCode();
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(value = AssertionError.class,
- messages = {"cannot verify call to", "java.lang.String java.lang.Object.toString()"})
public void testVerifyToString_ShouldFail() {
mFoo.toString();
- verify(mFoo).toString();
+ try {
+ verify(mFoo).toString();
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(value = AssertionError.class,
- messages = {"cannot stub call to", "boolean java.lang.Object.equals(java.lang.Object)"})
public void testStubEquals_ShouldFail() {
- doReturn(false).when(mFoo).equals(null);
+ try {
+ doReturn(false).when(mFoo).equals(null);
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(value = AssertionError.class,
- messages = {"cannot stub call to", "int java.lang.Object.hashCode()"})
public void testStubHashCode_ShouldFail() {
- doReturn(0).when(mFoo).hashCode();
+ try {
+ doReturn(0).when(mFoo).hashCode();
+ fail();
+ } catch (AssertionError expected) {}
}
- @ShouldThrow(value = AssertionError.class,
- messages = {"cannot stub call to", "java.lang.String java.lang.Object.toString()"})
public void testStubToString_ShouldFail() {
- doReturn("party").when(mFoo).toString();
+ try {
+ doReturn("party").when(mFoo).toString();
+ fail();
+ } catch (AssertionError expected) {}
}
public void testEqualsMethod_DoesntCountAsInteraction() {
@@ -1014,7 +1067,7 @@ public class LittleMockTest extends TestCase {
+ " mFoo.aBoolean()\n"
+ " at " + singleLineStackTrace(verifyLineNumber) + "\n"
+ "\n"
- + "No method calls happened to this mock\n";
+ + "No method calls happened on this mock\n";
assertEquals(expectedMessage, e.getMessage());
}
}
@@ -1100,23 +1153,29 @@ public class LittleMockTest extends TestCase {
}
}
- @ShouldThrow(IllegalArgumentException.class)
public void testDoReturn_ThisShouldFailSinceDoubleIsNotAString() {
- doReturn("hello").when(mFoo).aDouble();
+ try {
+ doReturn("hello").when(mFoo).aDouble();
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
public void testDoReturn_ThisShouldPassSinceStringCanBeReturnedFromObjectMethod() {
doReturn("hello").when(mFoo).anObject();
}
- @ShouldThrow(IllegalArgumentException.class)
public void testDoReturn_ThisShouldFailSinceObjectCantBeReturnedFromString() {
- doReturn(new Object()).when(mFoo).aString();
+ try {
+ doReturn(new Object()).when(mFoo).aString();
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
- @ShouldThrow(IllegalArgumentException.class)
public void testDoReturn_ThisShouldFailSinceBarIsNotSubtypeOfBarSubtype() {
- doReturn(mBar).when(mFoo).aBarSubtype();
+ try {
+ doReturn(mBar).when(mFoo).aBarSubtype();
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
public void testDoReturn_ThisShouldPassSinceBarSubTypeIsABar() {
@@ -1132,10 +1191,12 @@ public class LittleMockTest extends TestCase {
// TODO(hugohudson): 7. Should fix this.
// Once we fix the previous test we won't need this one.
// I'm just demonstrating that currently this fails with NPE at use-time not at stub-time.
- @ShouldThrow(NullPointerException.class)
public void testDoReturn_ThisShouldFailBecauseNullIsNotAByte2() {
doReturn(null).when(mFoo).aByte();
- mFoo.aByte();
+ try {
+ mFoo.aByte();
+ fail();
+ } catch (NullPointerException expected) {}
}
public void testDoReturn_ThisShouldPassSinceNullIsAnObject() {
@@ -1155,30 +1216,36 @@ public class LittleMockTest extends TestCase {
// TODO(hugohudson): 7. Should fix this to give proper message.
// We could at least give a good message saying why you get failure - saying that your string
// is not a byte, and pointing to where you stubbed it.
- @ShouldThrow(ClassCastException.class)
public void testDoAnswer_ThisShouldFailSinceStringIsNotAByte2() {
doAnswer(new Callable<String>() {
@Override public String call() throws Exception { return "hi"; }
}).when(mFoo).aByte();
- mFoo.aByte();
+ try {
+ mFoo.aByte();
+ fail();
+ } catch (ClassCastException expected) {}
}
- @ShouldThrow(value = IllegalArgumentException.class,
- messages = { " (Bar) mFoo.aBar()" })
public void testDoAnswer_ShouldHaveSimpleNameOnReturnValue() {
- doReturn("hi").when(mFoo).aBar();
+ try {
+ doReturn("hi").when(mFoo).aBar();
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
- @ShouldThrow(IllegalArgumentException.class)
public void testCantCreateMockOfNullType() {
- mock(null);
+ try {
+ mock(null);
+ fail();
+ } catch (IllegalArgumentException expected) {}
}
- @ShouldThrow(value = AssertionError.class,
- messages = { "Expected exactly 1 call to:", "onClickListener.onClick(Bar)" })
public void testCreateMockWithNullFieldName() {
OnClickListener mockClickListener = mock(OnClickListener.class);
- verify(mockClickListener).onClick(null);
+ try {
+ verify(mockClickListener).onClick(null);
+ fail();
+ } catch (AssertionError expected) {}
}
public void testDoubleVerifyNoProblems() {
@@ -1191,10 +1258,185 @@ public class LittleMockTest extends TestCase {
verify(mFoo).aByte();
}
- // TODO(hugohudson): 5. Every @ShouldThrow method should be improved by adding test for the
- // content of the error message. First augment the annotation to take a String which must
- // form a substring of the getMessage() for the Exception, then check that the exceptions
- // thrown are as informative as possible.
+ public void testUnfinishedVerifyCaughtInTearDown_Issue5() {
+ verify(mFoo);
+ try {
+ checkForProgrammingErrorsDuringTearDown();
+ fail();
+ } catch (IllegalStateException expected) {}
+ }
+
+ public void testUnfinishedWhenCaughtInTearDown_Issue5() {
+ doThrow(new RuntimeException()).when(mFoo);
+ try {
+ checkForProgrammingErrorsDuringTearDown();
+ fail();
+ } catch (IllegalStateException expected) {}
+ }
+
+ public void testUnfinishedVerifyCaughtByStartingWhen_Issue5() {
+ verify(mFoo, never());
+ try {
+ doReturn(null).when(mFoo).clear();
+ fail();
+ } catch (IllegalStateException expected) {}
+ }
+
+ public void testUnfinishedWhenCaughtByStartingVerify_Issue5() {
+ doThrow(new RuntimeException()).when(mFoo);
+ try {
+ verify(mFoo).clear();
+ fail();
+ } catch (IllegalStateException expected) {}
+ }
+
+ public void testSecondUnfinishedVerifyShouldFailImmediately() throws Exception {
+ verify(mFoo);
+ try {
+ verify(mFoo);
+ fail();
+ } catch (IllegalStateException expected) {}
+ }
+
+ public void testSecondUnfinishedWhenShouldFailImmediately() throws Exception {
+ doReturn(null).when(mFoo);
+ try {
+ doReturn(null).when(mFoo);
+ fail();
+ } catch (IllegalStateException expected) {}
+ }
+
+ public void testVerifyWithTimeout_SuccessCase() throws Exception {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ invokeBarMethodAfterLatchAwait(countDownLatch);
+ doReturn(null).when(mFoo).aBar();
+ verify(mFoo, never()).aBar();
+ countDownLatch.countDown();
+ verify(mFoo, timeout(100)).aBar();
+ }
+
+ public void testVerifyWithTimeout_FailureCase() throws Exception {
+ long start = System.currentTimeMillis();
+ try {
+ verify(mFoo, timeout(10)).aBar();
+ fail();
+ } catch (AssertionError expected) {}
+ long duration = System.currentTimeMillis() - start;
+ assertTrue(duration > 5);
+ }
+
+ public void testVerifyWithTimeoutMultipleInvocations_SuccessCase() throws Exception {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+ invokeBarMethodAfterLatchAwait(countDownLatch);
+ invokeBarMethodAfterLatchAwait(countDownLatch);
+ doReturn(null).when(mFoo).aBar();
+ verify(mFoo, never()).aBar();
+ countDownLatch.countDown();
+ verify(mFoo, timeout(100).times(2)).aBar();
+ verify(mFoo, timeout(100).atLeast(2)).aBar();
+ verify(mFoo, timeout(100).between(2, 4)).aBar();
+ verify(mFoo, timeout(100).atLeastOnce()).aBar();
+ }
+
+ public void testVerifyWithTimeoutMultipleInvocations_FailureCase() throws Exception {
+ long start = System.currentTimeMillis();
+ mFoo.aBar();
+ try {
+ verify(mFoo, timeout(10).between(2, 3)).aBar();
+ fail();
+ } catch (AssertionError expected) {}
+ long duration = System.currentTimeMillis() - start;
+ assertTrue(duration > 5);
+
+ }
+
+ public void testConcurrentModificationExceptions() throws Exception {
+ // This test concurrently stubs, verifies, and uses a mock.
+ // It used to totally reproducibly throw a ConcurrentModificationException.
+ Future<?> task1 = mExecutorService.submit(new Runnable() {
+ @Override
+ public void run() {
+ while (!Thread.currentThread().isInterrupted()) {
+ mFoo.aBar();
+ Thread.yield();
+ }
+ }
+ });
+ Future<?> task2 = mExecutorService.submit(new Runnable() {
+ @Override
+ public void run() {
+ while (!Thread.currentThread().isInterrupted()) {
+ verify(mFoo, anyTimes()).aBar();
+ Thread.yield();
+ }
+ }
+ });
+ Thread.sleep(20);
+ task1.cancel(true);
+ task2.cancel(true);
+ try {
+ task1.get();
+ fail();
+ } catch (CancellationException expected) {}
+ try {
+ task2.get();
+ fail();
+ } catch (CancellationException expected) {}
+ }
+
+ public void testCannotVerifyFromSecondThreadAfterStubbingInFirst() throws Exception {
+ doReturn(null).when(mFoo).aBar();
+ Future<?> submit = mExecutorService.submit(new Runnable() {
+ @Override
+ public void run() {
+ verify(mFoo, anyTimes()).aBar();
+ }
+ });
+ try {
+ submit.get();
+ fail();
+ } catch (ExecutionException expected) {
+ assertTrue(expected.getCause() instanceof IllegalStateException);
+ }
+ }
+
+ public void testCannotStubFromSecondThreadAfterVerifyingInFirst() throws Exception {
+ mExecutorService.submit(new Runnable() {
+ @Override
+ public void run() {
+ verify(mFoo, anyTimes()).aBar();
+ }
+ }).get();
+ try {
+ doReturn(null).when(mFoo).aBar();
+ fail();
+ } catch (IllegalStateException expected) {}
+ }
+
+ public class Jim {
+ public void bob() {
+ fail();
+ }
+ }
+
+ public void testMockingConcreteClasses() throws Exception {
+ Jim mock = mock(Jim.class);
+ mock.bob();
+ }
+
+ private Future<Void> invokeBarMethodAfterLatchAwait(final CountDownLatch countDownLatch) {
+ return mExecutorService.submit(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ countDownLatch.await();
+ mFoo.aBar();
+ return null;
+ }
+ });
+ }
+
+ // 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
@@ -1258,14 +1500,8 @@ public class LittleMockTest extends TestCase {
// //then
// assertThat(goods, containBread());
- // TODO(hugohudson): 6. How about timeouts that are mentioned in Mockito docs?
- // Sounds like a good idea. Would look like this:
- // verify(mFoo, within(50).milliseconds()).get(0);
-
- // TODO(hugohudson): 6. Consider bringing back in the async code I wrote for the AndroidMock
- // framework so that you can expect to wait for the method call.
- // Although obviously we're more keen on encouraging people to write unit tests that don't
- // require async behaviour in the first place.
+ // TODO: All unfinished verify and when statements should have sensible error messages telling
+ // you where the unfinished statement comes from.
/** Returns the line number of the line following the method call. */
private int getNextLineNumber() {
@@ -1307,4 +1543,12 @@ public class LittleMockTest extends TestCase {
}
};
}
+
+ private static <T> List<T> newList(T... things) {
+ ArrayList<T> list = new ArrayList<T>();
+ for (T thing : things) {
+ list.add(thing);
+ }
+ return list;
+ }
}