summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Hudson <hugohudson@google.com>2012-02-06 03:02:04 +0000
committerHugo Hudson <hugohudson@google.com>2012-02-06 03:02:04 +0000
commit40d40a3f2ebf988f36b828157be56cc12c9c70ac (patch)
tree004ffe8aecfe517f4fd95856330b84c5b1d63e6a
parent2a267dd8513e727846c03395429f69e4ab17f1c6 (diff)
downloadlittlemock-40d40a3f2ebf988f36b828157be56cc12c9c70ac.tar.gz
Update to r15 of LittleMock.
Change-Id: Ia9f4bba7db7e6b7e2afc7277914ac95be3d3246b
-rw-r--r--src/com/google/testing/littlemock/LittleMock.java99
-rw-r--r--tests/com/google/testing/littlemock/LittleMockTest.java120
2 files changed, 207 insertions, 12 deletions
diff --git a/src/com/google/testing/littlemock/LittleMock.java b/src/com/google/testing/littlemock/LittleMock.java
index 9946798..d13150a 100644
--- a/src/com/google/testing/littlemock/LittleMock.java
+++ b/src/com/google/testing/littlemock/LittleMock.java
@@ -30,6 +30,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -146,14 +147,48 @@ public class LittleMock {
/** Begins a verification step on a mock: the next method invocation on that mock will verify. */
public static <T> T verify(T mock, CallCount howManyTimes) {
+ return verify(mock, howManyTimes, null);
+ }
+
+ private static final class OrderChecker {
+ private MethodCall mLastCall;
+
+ public void checkOrder(List<MethodCall> calls, String fieldName) {
+ MethodCall lastTrial = null;
+ for (MethodCall trial : calls) {
+ if (mLastCall == null || mLastCall.mInvocationOrder < trial.mInvocationOrder) {
+ mLastCall = trial;
+ return;
+ }
+ lastTrial = trial;
+ }
+ fail(formatFailedVerifyOrderMessage(mLastCall, lastTrial, fieldName));
+ }
+
+ private String formatFailedVerifyOrderMessage(MethodCall lastCall, MethodCall thisCall,
+ String fieldName) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("\nCall to:");
+ appendDebugStringForMethodCall(sb, thisCall.mMethod, thisCall.mElement, fieldName, false);
+ sb.append("\nShould have happened after:");
+ appendDebugStringForMethodCall(sb, lastCall.mMethod, lastCall.mElement, fieldName, false);
+ sb.append("\nBut the calls happened in the wrong order");
+ sb.append("\n");
+ return sb.toString();
+ }
+ }
+
+ private static <T> T verify(T mock, CallCount howManyTimes, OrderChecker orderCounter) {
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.mOrderCounter == null, "Unfinished verify() statements");
checkState(handler.mStubbingAction == null, "Unfinished stubbing statements");
checkNoMatchers();
handler.mHowManyTimes = howManyTimes;
+ handler.mOrderCounter = orderCounter;
sUnfinishedCallCounts.add(howManyTimes);
return handler.<T>getVerifyingMock();
}
@@ -422,6 +457,7 @@ public class LittleMock {
public <T> T when(T mock) {
DefaultInvocationHandler handler = getHandlerFrom(mock);
checkState(handler.mHowManyTimes == null, "Unfinished verify() statements");
+ checkState(handler.mOrderCounter == null, "Unfinished verify() statements");
checkState(handler.mStubbingAction == null, "Unfinished stubbing statements");
handler.mStubbingAction = mAction;
sUnfinishedStubbingActions.add(mAction);
@@ -450,12 +486,17 @@ public class LittleMock {
*/
private static final List<ArgumentMatcher> sMatchArguments = new ArrayList<ArgumentMatcher>();
+ /** Global invocation order of every mock method call. */
+ private static final AtomicLong sGlobalInvocationOrder = new AtomicLong();
+
/** Encapsulates a single call of a method with associated arguments. */
private static class MethodCall {
/** The method call. */
private final Method mMethod;
/** The arguments provided at the time the call happened. */
private final Object[] mArgs;
+ /** The order in which this method call was invoked. */
+ private final long mInvocationOrder;
/** The line from the test that invoked the handler to create this method call. */
private final StackTraceElement mElement;
/** Keeps track of method calls that have been verified, for verifyNoMoreInteractions(). */
@@ -465,6 +506,7 @@ public class LittleMock {
mMethod = method;
mElement = element;
mArgs = args;
+ mInvocationOrder = sGlobalInvocationOrder.getAndIncrement();
}
public boolean argsMatch(Object[] args) {
@@ -537,6 +579,7 @@ public class LittleMock {
* <p>It is reset to null once the verification has occurred.
*/
private CallCount mHowManyTimes = null;
+ private OrderChecker mOrderCounter = null;
/**
* The action to be associated with the stubbed method.
@@ -605,11 +648,12 @@ public class LittleMock {
ArgumentMatcher[] matchers = checkClearAndGetMatchers(method);
StackTraceElement callSite = new Exception().getStackTrace()[2];
MethodCall methodCall = new MethodCall(method, callSite, args);
- innerVerify(method, matchers, methodCall, proxy, callSite, mHowManyTimes);
+ innerVerify(method, matchers, methodCall, proxy, callSite, mHowManyTimes, mOrderCounter);
return defaultReturnValue(method.getReturnType());
} finally {
sUnfinishedCallCounts.remove(mHowManyTimes);
mHowManyTimes = null;
+ mOrderCounter = null;
}
}
});
@@ -664,6 +708,7 @@ public class LittleMock {
mRecordedCalls.clear();
mStubbedCalls.clear();
mHowManyTimes = null;
+ mOrderCounter = null;
mStubbingAction = null;
}
@@ -758,44 +803,47 @@ public class LittleMock {
}
private void innerVerify(Method method, ArgumentMatcher[] matchers, MethodCall methodCall,
- Object proxy, StackTraceElement callSite, CallCount callCount) {
+ Object proxy, StackTraceElement callSite, CallCount callCount, OrderChecker orderCounter) {
checkSpecialObjectMethods(method, "verify");
- int total = countMatchingInvocations(method, matchers, methodCall);
+ List<MethodCall> calls = countMatchingInvocations(method, matchers, methodCall);
long callTimeout = callCount.getTimeout();
+ checkState(orderCounter == null || callTimeout == 0, "can't inorder verify with a timeout");
if (callTimeout > 0) {
long endTime = System.currentTimeMillis() + callTimeout;
- while (!callCount.matches(total)) {
+ while (!callCount.matches(calls.size())) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
fail("interrupted whilst waiting to verify");
}
if (System.currentTimeMillis() > endTime) {
- fail(formatFailedVerifyMessage(methodCall, total, callTimeout, callCount));
+ fail(formatFailedVerifyMessage(methodCall, calls.size(), callTimeout, callCount));
}
- total = countMatchingInvocations(method, matchers, methodCall);
+ calls = countMatchingInvocations(method, matchers, methodCall);
}
} else {
- if (!callCount.matches(total)) {
- fail(formatFailedVerifyMessage(methodCall, total, 0, callCount));
+ if (orderCounter != null) {
+ orderCounter.checkOrder(calls, mFieldName);
+ } else if (!callCount.matches(calls.size())) {
+ fail(formatFailedVerifyMessage(methodCall, calls.size(), 0, callCount));
}
}
}
- private int countMatchingInvocations(Method method, ArgumentMatcher[] matchers,
+ private List<MethodCall> countMatchingInvocations(Method method, ArgumentMatcher[] matchers,
MethodCall methodCall) {
- int total = 0;
+ List<MethodCall> methodCalls = new ArrayList<MethodCall>();
for (MethodCall call : mRecordedCalls) {
if (areMethodsSame(call.mMethod, method)) {
if ((matchers.length > 0 && doMatchersMatch(matchers, call.mArgs)) ||
call.argsMatch(methodCall.mArgs)) {
setCaptures(matchers, call.mArgs);
- ++total;
+ methodCalls.add(call);
call.mWasVerified = true;
}
}
}
- return total;
+ return methodCalls;
}
private String formatFailedVerifyMessage(MethodCall methodCall, int total, long timeoutMillis,
@@ -1120,4 +1168,31 @@ public class LittleMock {
} catch (Exception ignored) {}
throw new IllegalStateException("unsafe create instance failed");
}
+
+ /** See {@link LittleMock#inOrder(Object[])}. */
+ public interface InOrder {
+ <T> T verify(T mock);
+ }
+
+ /**
+ * Used to verify that invocations happen in a given order.
+ * <p>
+ * Still slight experimental at the moment: you can only verify one method call at a time,
+ * and we ignore the mocks array you pass in as an argument, you may use the returned inorder
+ * to verify all mocks.
+ * <p>
+ * This implementation is simple: the InOrder you get from this call can be used to verify that
+ * a sequence of method calls happened in the order you specify. Every verify you make with
+ * this InOrder will be compared with every other verify you made, to make sure that all the
+ * original invocations happened in exactly that same order.
+ */
+ public static InOrder inOrder(Object... mocks) {
+ return new InOrder() {
+ private final OrderChecker mChecker = new OrderChecker();
+ @Override
+ public <T> T verify(T mock) {
+ return LittleMock.verify(mock, times(1), mChecker);
+ }
+ };
+ }
}
diff --git a/tests/com/google/testing/littlemock/LittleMockTest.java b/tests/com/google/testing/littlemock/LittleMockTest.java
index ad12f3e..c9b36be 100644
--- a/tests/com/google/testing/littlemock/LittleMockTest.java
+++ b/tests/com/google/testing/littlemock/LittleMockTest.java
@@ -37,6 +37,7 @@ import static com.google.testing.littlemock.LittleMock.doNothing;
import static com.google.testing.littlemock.LittleMock.doReturn;
import static com.google.testing.littlemock.LittleMock.doThrow;
import static com.google.testing.littlemock.LittleMock.eq;
+import static com.google.testing.littlemock.LittleMock.inOrder;
import static com.google.testing.littlemock.LittleMock.initMocks;
import static com.google.testing.littlemock.LittleMock.isA;
import static com.google.testing.littlemock.LittleMock.matches;
@@ -50,6 +51,7 @@ import static com.google.testing.littlemock.LittleMock.verifyNoMoreInteractions;
import static com.google.testing.littlemock.LittleMock.verifyZeroInteractions;
import com.google.testing.littlemock.LittleMock.ArgumentMatcher;
+import com.google.testing.littlemock.LittleMock.InOrder;
import junit.framework.TestCase;
@@ -1430,6 +1432,124 @@ public class LittleMockTest extends TestCase {
verify(mFoo, times(2)).add((String) matches(argumentMatcher));
}
+ public void testInorderExample_Success() {
+ @SuppressWarnings("unchecked")
+ List<String> firstMock = mock(List.class);
+ @SuppressWarnings("unchecked")
+ List<String> secondMock = mock(List.class);
+ firstMock.add("was called first");
+ secondMock.add("was called second");
+ InOrder inOrder = inOrder(firstMock, secondMock);
+ inOrder.verify(firstMock).add("was called first");
+ inOrder.verify(secondMock).add("was called second");
+ }
+
+ public void testInorderExample_Failure() {
+ @SuppressWarnings("unchecked")
+ List<String> firstMock = mock(List.class);
+ @SuppressWarnings("unchecked")
+ List<String> secondMock = mock(List.class);
+ firstMock.add("was called first");
+ secondMock.add("was called second");
+ InOrder inOrder = inOrder(firstMock, secondMock);
+ inOrder.verify(secondMock).add("was called second");
+ try {
+ inOrder.verify(firstMock).add("was called first");
+ throw new IllegalStateException();
+ } catch (AssertionError expected) {}
+ }
+
+ public void testInorderInterleave() {
+ @SuppressWarnings("unchecked")
+ List<String> firstMock = mock(List.class);
+ firstMock.add("a");
+ firstMock.add("b");
+ firstMock.add("a");
+
+ // Should be fine to verify a then b, since they happened in that order.
+ InOrder inOrder = inOrder(firstMock);
+ inOrder.verify(firstMock).add("a");
+ inOrder.verify(firstMock).add("b");
+
+ // Should also be fine to inorder verify the other way around, they happened in that order too.
+ inOrder = inOrder(firstMock);
+ inOrder.verify(firstMock).add("b");
+ inOrder.verify(firstMock).add("a");
+
+ // Should be fine to verify "a, b, a" since that too happened.
+ inOrder = inOrder(firstMock);
+ inOrder.verify(firstMock).add("a");
+ inOrder.verify(firstMock).add("b");
+ inOrder.verify(firstMock).add("a");
+
+ // "a, a, b" did not happen.
+ inOrder = inOrder(firstMock);
+ inOrder.verify(firstMock).add("a");
+ inOrder.verify(firstMock).add("a");
+ try {
+ inOrder.verify(firstMock).add("b");
+ throw new IllegalStateException();
+ } catch (AssertionError expected) {}
+
+ // "b, a, b" did not happen.
+ inOrder = inOrder(firstMock);
+ inOrder.verify(firstMock).add("b");
+ inOrder.verify(firstMock).add("a");
+ try {
+ inOrder.verify(firstMock).add("b");
+ throw new IllegalStateException();
+ } catch (AssertionError expected) {}
+
+ // "b" did not happen twice.
+ inOrder = inOrder(firstMock);
+ inOrder.verify(firstMock).add("b");
+ try {
+ inOrder.verify(firstMock).add("b");
+ throw new IllegalStateException();
+ } catch (AssertionError expected) {}
+ }
+
+ public void testInorderComplicatedExample() {
+ // TODO: I'm currently totally ignoring the parameters passed to the inorder method.
+ // I don't understand what the point of them is, anyway.
+ @SuppressWarnings("unchecked")
+ List<String> firstMock = mock(List.class);
+ @SuppressWarnings("unchecked")
+ List<String> secondMock = mock(List.class);
+
+ firstMock.add("1");
+ secondMock.add("2");
+ firstMock.add("3");
+ secondMock.add("4");
+
+ InOrder allInOrder = inOrder(firstMock, secondMock);
+ allInOrder.verify(firstMock).add("1");
+ allInOrder.verify(secondMock).add("2");
+ allInOrder.verify(firstMock).add("3");
+ allInOrder.verify(secondMock).add("4");
+
+ InOrder firstInOrder = inOrder(firstMock, secondMock);
+ firstInOrder.verify(firstMock).add("1");
+ firstInOrder.verify(firstMock).add("3");
+ try {
+ firstInOrder.verify(secondMock).add("2");
+ throw new IllegalStateException();
+ } catch (AssertionError expected) {}
+ firstInOrder.verify(secondMock).add("4");
+
+ InOrder secondInOrder = inOrder(firstMock, secondMock);
+ secondInOrder.verify(secondMock).add("2");
+ secondInOrder.verify(secondMock).add("4");
+ try {
+ secondInOrder.verify(firstMock).add("1");
+ throw new IllegalStateException();
+ } catch (AssertionError expected) {}
+ try {
+ secondInOrder.verify(firstMock).add("3");
+ throw new IllegalStateException();
+ } catch (AssertionError expected) {}
+ }
+
public static class Jim {
public void bob() {
fail();