package com.googlecode.gwt.test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.easymock.IExpectationSetters;
import org.junit.Before;
import com.google.gwt.dev.shell.JsValueGlue;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.googlecode.gwt.test.exceptions.GwtTestException;
import com.googlecode.gwt.test.exceptions.GwtTestPatchException;
import com.googlecode.gwt.test.exceptions.ReflectionException;
import com.googlecode.gwt.test.internal.BrowserSimulatorImpl;
import com.googlecode.gwt.test.internal.GwtFactory;
import com.googlecode.gwt.test.internal.utils.ArrayUtils;
import com.googlecode.gwt.test.utils.GwtReflectionUtils;
import com.googlecode.gwt.test.utils.GwtReflectionUtils.MethodCallback;
/**
* <p>
* Base class for test classes which make use of the {@link org.easymock.EasyMock EasyMock} mocking
* framework.
* </p>
*
* <p>
* Those classes can declare fields annotated with {@link Mock @Mock}, which will result in the
* injection of mock objects of the corresponding type.
*
* Mock objects not declared using this annotation (e.g. objects instantiated by calling directly
* the {@link EasyMock#createMock(Class)} should be added to the test context using the
* addMockedObject protected method.
* </p>
*
* <p>
* Mock objects can then be manipulated using the standard EasyMock API, or with the helper methods
* provided by GwtTestWithEasyMock.
* </p>
*
* @author Bertrand Paquet
* @author Gael Lazzari
*/
public abstract class GwtTestWithEasyMock extends GwtTestWithMocks {
private static class FailureAnswer<T extends Throwable> implements IAnswer<T> {
private final T result;
public FailureAnswer(T result) {
this.result = result;
}
@SuppressWarnings("unchecked")
public T answer() throws Throwable {
Object[] arguments = EasyMock.getCurrentArguments();
AsyncCallback<T> callback = (AsyncCallback<T>) arguments[arguments.length - 1];
callback.onFailure(result);
return null;
}
}
private static class SuccessAnswer<T> implements IAnswer<T> {
private final T result;
public SuccessAnswer(T result) {
this.result = result;
}
@SuppressWarnings("unchecked")
public T answer() throws Throwable {
final Object[] arguments = EasyMock.getCurrentArguments();
AsyncCallback<T> callback = (AsyncCallback<T>) arguments[arguments.length - 1];
callback.onSuccess(result);
return null;
}
}
@Before
public void beforeGwtTestWithEasyMock() {
for (Class<?> clazz : mockedClasses) {
Object mock = createMock(clazz);
addMockedObject(clazz, mock);
}
try {
for (Field f : mockFields) {
Object mock = mockObjects.get(f.getType());
GwtReflectionUtils.makeAccessible(f);
f.set(this, mock);
}
} catch (Exception e) {
if (GwtTestException.class.isInstance(e)) {
throw (GwtTestException) e;
} else {
throw new ReflectionException("Error during gwt-test-utils @Mock creation", e);
}
}
}
/**
* Creates a mock object for a given class, where all methods are mocked except the ones given as
* parameters.
*
* @param clazz The class for which a mock object will be created
* @param keepSetters False if setters should be mocked, true otherwise
* @param list List of methods that should not be mocked
*/
protected <T> T createMockAndKeepMethods(Class<T> clazz, final boolean keepSetters,
final Method... list) {
final List<Method> l = new ArrayList<Method>();
GwtReflectionUtils.doWithMethods(clazz, new MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
if (!ArrayUtils.contains(list, method)) {
if (!keepSetters || !method.getName().startsWith("set")
|| method.getReturnType() != void.class) {
l.add(method);
}
}
}
});
T o = EasyMock.createMockBuilder(clazz).addMockedMethods(l.toArray(new Method[]{})).createMock();
addMockedObject(clazz, o);
return o;
}
/**
* Creates a mock object for a given class, where all methods are mocked except the one with
* specifics name and parameter types.
*
* @param <T> The result mock type.
* @param clazz The result mock type.
* @param methodName The name of the method to keep.
* @param paramsTypes The parameter of the method to keep.
* @return The resulting mock object.
*/
protected <T> T createMockAndKeepOneMethod(Class<T> clazz, String methodName,
Class<?>... paramsTypes) {
return createMockAndKeepMethods(clazz, true,
GwtReflectionUtils.findMethod(clazz, methodName, paramsTypes));
}
/**
* Records a call to an asynchronous service and simulates a failure by calling the onFailure()
* method of the corresponding AsyncCallback object.
*
* @param exception The exception thrown by the stubbed remote service and passed to the callback
* onFailure() method.
* @return the value returned by the delegated call.
*/
@SuppressWarnings("unchecked")
protected <T extends Throwable> IExpectationSetters<T> expectServiceAndCallbackOnFailure(
final T exception) {
IAnswer<T> answer = new FailureAnswer<T>(exception);
Object o = EasyMock.expectLastCall().andAnswer(answer);
return (IExpectationSetters<T>) o;
}
/**
* Records a call to an asynchronous service and simulates a success by calling the onSuccess()
* method of the corresponding AsyncCallback object.
*
* @param object The object returned by the stubbed remote service and passed to the callback
* onSuccess() method.
* @return the value returned by the delegated call.
*/
@SuppressWarnings("unchecked")
protected <T> IExpectationSetters<T> expectServiceAndCallbackOnSuccess(final T object) {
IAnswer<T> answer = new SuccessAnswer<T>(object);
return (IExpectationSetters<T>) EasyMock.expectLastCall().andAnswer(answer);
}
/**
* Set all declared mocks to replay state.
*/
protected void replay() {
for (Object o : mockObjects.values()) {
EasyMock.replay(o);
}
}
/**
* Reset all declared mocks.
*/
protected void reset() {
for (Object o : mockObjects.values()) {
EasyMock.reset(o);
}
}
/**
* Verifies that all recorded behaviors for every declared mock has actually been used.
*/
protected void verify() {
// trigger commands
BrowserSimulatorImpl.get().fireLoopEnd();
for (Object o : mockObjects.values()) {
EasyMock.verify(o);
}
}
private Object createMock(Class<?> clazz) {
if (GwtFactory.get().getOverlayRewriter().isJsoIntf(clazz.getName())) {
try {
return EasyMock.createMock(Class.forName(JsValueGlue.JSO_IMPL_CLASS));
} catch (ClassNotFoundException e) {
// should never happen
throw new GwtTestPatchException("Error while creating a mock with EasyMock for "
+ clazz.getName(), e);
}
} else {
return EasyMock.createMock(clazz);
}
}
}