/*
* util.mocks.MockSupport
*
*------------------------------------------------------------------------------
* Copyright (C) 2006 University of Dundee. All rights reserved.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
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());
}
}