/*
* util.mocks.MockSupport
*
* Copyright 2006 University of Dundee. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package util.mocks;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import junit.framework.Assert;
/**
* Provides mocks with the means to easily implement the life-cycle required by
* the {@link IMock} interface.
* <p>
* A mock object makes a new a <code>MockSupport</code> and links it to itself
* at creation time. The mock object is now in expectations set up mode (as
* required by the life-cycle contract) and so is its <code>MockSupport</code>.
* All the mock object has to do is to collect expectations in the form of
* {@link MockedCall}'s and append them to the <code>MockSupport</code>'s
* list (calling {@link #add(MockedCall)}), in the same order as they were
* collected. This is important as the order in which expectations are appended
* to the list is also the order in which calls will be verified.
* </p>
* <p>
* When the {@link IMock#activate() activate} method is called on the mock
* object, it simply has to forward this call to its <code>MockSupport</code>,
* which transitions to verification mode. Now it's not possible to collect
* expectations any more and the {@link #add(MockedCall) add} method will throw
* an exception if an attempt is made this means the mock object won't
* have to keep track of the state, its <code>MockSupport</code> already does
* that.
* </p>
* <p>
* At this point the mock object is in verification mode (as required by the
* life-cycle contract) and so is its <code>MockSupport</code>. Upon
* invocation, each mocked method within the mock object will first create a
* {@link MockedCall} to reflect the current invocation, then pass this new
* object to the {@link #verifyCall(MockedCall) verifyCall} method, and finally
* get back the original {@link MockedCall} that was expected this is
* done so that the method may extract the return value, if any, from the
* original expected call (the one set by means of {@link #add(MockedCall)})
* and return it. If the {@link MockedCall} object passed to
* {@link #verifyCall(MockedCall) verifyCall} doesn't match the current call
* expectation (as set in the original sequence), the test fails.
* </p>
* <p>
* The {@link #verifyCallSequence() verifyCallSequence} method is meant to be
* invoked at the end of the test to verify that all expected calls were
* performed. If less calls were performed, then the test fails. So all the mock
* object has to do is to call this method when its
* {@link IMock#verify() verify} method is invoked.
* </p>
*
* @author Jean-Marie Burel <a
* href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author <br>
* Andrea Falconi <a
* href="mailto:a.falconi@dundee.ac.uk"> a.falconi@dundee.ac.uk</a>
* @since OME2.2
*/
public class MockSupport {
/** The sequence of calls that the mock object is expecting. */
private List expectedCalls;
/** Iterates through {@link #expectedCalls} when in verification mode. */
private Iterator expectationsIterator;
/**
* Counts the calls that have been performed. This counter is only updated
* in verification-mode.
*/
private int performedCalls;
/**
* Tells whether we're in expectations set up mode (<code>false</code>)
* or verification mode (<code>true</code>).
*/
private boolean isActive;
/**
* Creates a new instance. The newly created object will be in expectations
* set up mode.
*/
public MockSupport() {
expectedCalls = new ArrayList();
isActive = false;
performedCalls = 0;
expectationsIterator = null;
}
/**
* Appends the specified call to the expectations list. This method can only
* be invoked in expectations set up mode, that is, any time after creation
* up until a call to {@link #activate()}.
*
* @param mc
* The next call in the sequence of expected calls.
*/
public void add(MockedCall mc) {
if (isActive) {
throw new IllegalAccessError(
"Can't set expectations while they're verified.");
}
if (mc == null) {
throw new NullPointerException("No mocked call was specified.");
}
expectedCalls.add(mc);
}
/**
* Transitions this object into verification mode. The expecations list will
* be frozen (no further additions allowed) and only the verify methods can
* be invoked from now on.
*/
public void activate() {
if (isActive) {
throw new IllegalAccessError("Can't re-activate.");
}
isActive = true;
expectationsIterator = expectedCalls.iterator();
}
/**
* Tells whether this object is in expectations set up mode. That is, if
* {@link #activate()} has not been called yet.
*
* @return <code>true</code> if in expectations set up mode,
* <code>false</code> otherwise.
*/
public boolean isSetUpMode() {
return !isActive;
}
/**
* Tells whether this object is in verification mode. That is, if
* {@link #activate()} has been called.
*
* @return <code>true</code> if in verification mode, <code>false</code>
* otherwise.
*/
public boolean isVerificationMode() {
return isActive;
}
/**
* Verifies the passed call against the current call expectation. If the
* expectation is not met, the test fails.
*
* @param actual
* The actual call.
* @return The expected call, so that the original return value, if any, can
* be extracted.
*/
public MockedCall verifyCall(MockedCall actual) {
if (!isActive) {
throw new IllegalAccessError(
"Can't verify expectations while they're set.");
}
if (!expectationsIterator.hasNext()) {
failTooManyCalls(actual);
}
performedCalls++;
MockedCall expected = (MockedCall) expectationsIterator.next();
if (!expected.isSameCall(actual)) {
failUnexpectedCall(expected, actual, performedCalls);
}
return expected;
}
/**
* Verifies that all the expected calls were actually performed. If this
* condition is not met, the test fails.
*/
public void verifyCallSequence() {
if (!isActive) {
throw new IllegalAccessError(
"Can't verify expectations while they're set.");
}
if (expectationsIterator.hasNext()) {
failMoreCallsExpected();
}
}
/**
* Helper method to fail the test if not all calls in the expected sequence
* were performed.
*/
private void failMoreCallsExpected() {
StringBuffer buf = new StringBuffer();
buf.append("Uncompleted call sequence. Expected calls: ");
buf.append(expectedCalls.size());
buf.append(" - performed: ");
buf.append(performedCalls);
buf.append(".");
Assert.fail(buf.toString());
}
/**
* Helper method to fail the test if all expected calls have already been
* performed and a new call is carried out.
*
* @param unexpected
* The exceeding call.
*/
private void failTooManyCalls(MockedCall unexpected) {
StringBuffer buf = new StringBuffer();
buf.append("Exceeding call: ");
buf.append(unexpected);
buf.append(".");
Assert.fail(buf.toString());
}
/**
* Helper method to fail the test if the current call doesn't match the
* expected call in the sequence.
*
* @param expected
* The expected call.
* @param unexpected
* The last performed out call.
* @param index
* The index of the expected call in the sequence.
*/
private void failUnexpectedCall(MockedCall expected, MockedCall unexpected,
int index) {
StringBuffer buf = new StringBuffer();
buf.append("Unexpected call: ");
buf.append(unexpected);
buf.append(". ");
buf.append("Was expecting (sequence=");
buf.append(index);
buf.append("): ");
buf.append(expected);
buf.append(".");
Assert.fail(buf.toString());
}
}