Moved over static mock testing from Roo; added tests

This commit is contained in:
Ramnivas Laddad
2009-10-27 17:16:02 +00:00
parent 57afe3baab
commit 451bbce345
8 changed files with 583 additions and 20 deletions

View File

@@ -0,0 +1,182 @@
package org.springframework.mock.static_mock;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/**
* Abstract aspect to enable mocking of methods picked out by a pointcut.
* Sub-aspects must define the mockStaticsTestMethod() pointcut to
* indicate call stacks when mocking should be triggered, and the
* methodToMock() pointcut to pick out a method invocations to mock.
*
* @author Rod Johnson
* @author Ramnivas Laddad
*
*/
public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMethod()) {
protected abstract pointcut mockStaticsTestMethod();
protected abstract pointcut methodToMock();
private boolean recording = true;
static enum CallResponse { nothing, return_, throw_ };
// Represents a list of expected calls to static entity methods
// Public to allow inserted code to access: is this normal??
public class Expectations {
// Represents an expected call to a static entity method
private class Call {
private final String signature;
private final Object[] args;
private Object responseObject; // return value or throwable
private CallResponse responseType = CallResponse.nothing;
public Call(String name, Object[] args) {
this.signature = name;
this.args = args;
}
public boolean hasResponseSpecified() {
return responseType != CallResponse.nothing;
}
public void setReturnVal(Object retVal) {
this.responseObject = retVal;
responseType = CallResponse.return_;
}
public void setThrow(Throwable throwable) {
this.responseObject = throwable;
responseType = CallResponse.throw_;
}
public Object returnValue(String lastSig, Object[] args) {
checkSignature(lastSig, args);
return responseObject;
}
public Object throwException(String lastSig, Object[] args) {
checkSignature(lastSig, args);
throw (RuntimeException)responseObject;
}
private void checkSignature(String lastSig, Object[] args) {
if (!signature.equals(lastSig)) {
throw new IllegalArgumentException("Signature doesn't match");
}
if (!Arrays.equals(this.args, args)) {
throw new IllegalArgumentException("Arguments don't match");
}
}
}
private List<Call> calls = new LinkedList<Call>();
// Calls already verified
private int verified;
public void verify() {
if (verified != calls.size()) {
throw new IllegalStateException("Expected " + calls.size()
+ " calls, received " + verified);
}
}
/**
* Validate the call and provide the expected return value
* @param lastSig
* @param args
* @return
*/
public Object respond(String lastSig, Object[] args) {
Call call = nextCall();
CallResponse responseType = call.responseType;
if (responseType == CallResponse.return_) {
return call.returnValue(lastSig, args);
} else if(responseType == CallResponse.throw_) {
return (RuntimeException)call.throwException(lastSig, args);
} else if(responseType == CallResponse.nothing) {
// do nothing
}
throw new IllegalStateException("Behavior of " + call + " not specified");
}
private Call nextCall() {
if (verified > calls.size() - 1) {
throw new IllegalStateException("Expected " + calls.size()
+ " calls, received " + verified);
}
return calls.get(verified++);
}
public void expectCall(String lastSig, Object lastArgs[]) {
Call call = new Call(lastSig, lastArgs);
calls.add(call);
}
public boolean hasCalls() {
return !calls.isEmpty();
}
public void expectReturn(Object retVal) {
Call call = calls.get(calls.size() - 1);
if (call.hasResponseSpecified()) {
throw new IllegalStateException("No static method invoked before setting return value");
}
call.setReturnVal(retVal);
}
public void expectThrow(Throwable throwable) {
Call call = calls.get(calls.size() - 1);
if (call.hasResponseSpecified()) {
throw new IllegalStateException("No static method invoked before setting throwable");
}
call.setThrow(throwable);
}
}
private Expectations expectations = new Expectations();
after() returning : mockStaticsTestMethod() {
if (recording && (expectations.hasCalls())) {
throw new IllegalStateException(
"Calls recorded, yet playback state never reached: Create expectations then call "
+ this.getClass().getSimpleName() + ".playback()");
}
expectations.verify();
}
Object around() : methodToMock() {
if (recording) {
expectations.expectCall(thisJoinPointStaticPart.toLongString(), thisJoinPoint.getArgs());
// Return value doesn't matter
return null;
} else {
return expectations.respond(thisJoinPointStaticPart.toLongString(), thisJoinPoint.getArgs());
}
}
public void expectReturnInternal(Object retVal) {
if (!recording) {
throw new IllegalStateException("Not recording: Cannot set return value");
}
expectations.expectReturn(retVal);
}
public void expectThrowInternal(Throwable throwable) {
if (!recording) {
throw new IllegalStateException("Not recording: Cannot set throwable value");
}
expectations.expectThrow(throwable);
}
public void playbackInternal() {
recording = false;
}
}

View File

@@ -0,0 +1,57 @@
package org.springframework.mock.static_mock;
import javax.persistence.Entity;
import org.junit.Test;
/**
* JUnit-specific aspect to use in test build to enable mocking static methods
* on Entity classes, as used by Roo for finders.
* <br>
* Mocking will occur in JUnit tests where the Test class is annotated with the
* @MockStaticEntityMethods annotation, in the call stack of each
* JUnit @Test method.
* <br>
* Also provides static methods to simplify the programming model for
* entering playback mode and setting expected return values.
* <br>
* Usage:<ol>
* <li>Annotate a JUnit test class with @MockStaticEntityMethods.
* <li>In each @Test method, JUnitMockControl will begin in recording mode.
* Invoke static methods on Entity classes, with each recording-mode invocation
* being followed by an invocation to the static expectReturn() or expectThrow()
* method on JUnitMockControl.
* <li>Invoke the static JUnitMockControl.playback() method.
* <li>Call the code you wish to test that uses the static methods. Verification will
* occur automatically.
* </ol>
*
* @see MockStaticEntityMethods
*
* @author Rod Johnson
* @author Ramnivas Laddad
*
*/
public aspect JUnitStaticEntityMockingControl extends AbstractMethodMockingControl {
/**
* Stop recording mock calls and enter playback state
*/
public static void playback() {
JUnitStaticEntityMockingControl.aspectOf().playbackInternal();
}
public static void expectReturn(Object retVal) {
JUnitStaticEntityMockingControl.aspectOf().expectReturnInternal(retVal);
}
public static void expectThrow(Throwable throwable) {
JUnitStaticEntityMockingControl.aspectOf().expectThrowInternal(throwable);
}
// Only matches directly annotated @Test methods, to allow methods in
// @MockStatics classes to invoke each other without resetting the mocking environment
protected pointcut mockStaticsTestMethod() : execution(@Test public * (@MockStaticEntityMethods *).*(..));
protected pointcut methodToMock() : execution(public static * (@Entity *).*(..));
}

View File

@@ -0,0 +1,21 @@
package org.springframework.mock.static_mock;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to indicate a test class for whose @Test methods
* static methods on Entity classes should be mocked.
*
* @see AbstractMethodMockingControl
*
* @author Rod Johnson
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MockStaticEntityMethods {
}