Skip to content

Instantly share code, notes, and snippets.

@alan-morey
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alan-morey/8cf6d0e24b1aa713a61c to your computer and use it in GitHub Desktop.
Save alan-morey/8cf6d0e24b1aa713a61c to your computer and use it in GitHub Desktop.
Apex Test Spy
@isTest
public class TestSpy {
public static final String UNKNOWN = '<unknown>';
public static final String VOID_RETURN_TYPE = '<void>';
String methodName;
List<Object[]> callArgs;
Exception throwResult;
Boolean isVoidReturn = true;
Object returnResult;
public class TestSpyException extends Exception {}
public TestSpy(String methodName) {
this.methodName = String.isBlank(methodName) ? UNKNOWN : methodName;
reset();
}
public String name() {
return methodName;
}
public Object getReturnResult() {
return returnResult;
}
public Exception getThrowResult() {
return throwResult;
}
public void reset() {
callArgs = new List<Object[]>();
}
// FIXME refactor tests then remove this method
public Integer count() {
return callCount();
}
public Integer callCount() {
return callArgs.size();
}
public Object[] argsFor(Integer call) {
return callArgs.get(call);
}
public Object[] argsForLastCall() {
return callArgs.get(callCount() - 1);
}
public TestSpy andReturn(Object toReturn) {
returnResult = toReturn;
isVoidReturn = false;
return this;
}
public TestSpy andReturnVoid() {
returnResult = null;
isVoidReturn = true;
return this;
}
public Boolean isVoidReturn() {
return isVoidReturn;
}
public TestSpy andThrow(String exceptionMessage) {
return andThrow(new TestSpyException(exceptionMessage));
}
public TestSpy andThrow(Exception toThrow) {
throwResult = toThrow;
return this;
}
public Object call() {
return trackCall(new List<Object>());
}
public Object call(Object arg1) {
return trackCall(new List<Object> { arg1 });
}
public Object call(Object arg1, Object arg2) {
return trackCall(new List<Object> { arg1, arg2 });
}
public Object call(Object arg1, Object arg2, Object arg3) {
return trackCall(new List<Object> { arg1, arg2, arg3 });
}
public Object call(Object arg1, Object arg2, Object arg3, Object arg4) {
return trackCall(new List<Object> { arg1, arg2, arg3, arg4 });
}
public Object call(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {
return trackCall(new List<Object> { arg1, arg2, arg3, arg4, arg5 });
}
public Object call(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) {
return trackCall(new List<Object> { arg1, arg2, arg3, arg4, arg5, arg6 });
}
public Object call(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7) {
return trackCall(new List<Object> { arg1, arg2, arg3, arg4, arg5, arg6, arg7 });
}
public Object call(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8) {
return trackCall(new List<Object> { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 });
}
public Object call(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9) {
return trackCall(new List<Object> { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 });
}
public Object call(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, Object arg10) {
return trackCall(new List<Object> { arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 });
}
// Salesforce supports up to 32 arguments for methods
Object trackCall(List<Object> args) {
System.debug(LoggingLevel.DEBUG, String.format(
'TestSpy {0} called with {1} arguments: {2}', new String[] {
methodName, String.valueOf(args.size()), String.valueOf(args)
})
);
callArgs.add(args);
if (throwResult != null) {
System.debug(LoggingLevel.DEBUG, String.format(
'TestSpy {0} throws exception: {1}', new String[] {
methodName, String.valueOf(throwResult)
})
);
throw throwResult;
}
System.debug(LoggingLevel.DEBUG, String.format(
'TestSpy {0} returns: {1}', new String[] {
methodName, isVoidReturn ? VOID_RETURN_TYPE : String.valueOf(returnResult)
})
);
return returnResult;
}
public override String toString() {
return Objects.toStringBuilder(TestSpy.class)
.add('methodName', methodName)
.add('isVoidReturn', isVoidReturn)
.add('returnResult', returnResult)
.add('callCount', callCount())
.add('callArgs', callArgs)
.toString();
}
}
@isTest
public class TestSpyTests {
static TestSpy spy = new TestSpy('spy');
@isTest static void itShouldHaveUnknownMethodNameWhenBlankOrNullGivenForConsturctor() {
spy = new TestSpy(null);
System.assertEquals(TestSpy.UNKNOWN, spy.name());
spy = new TestSpy('');
System.assertEquals(TestSpy.UNKNOWN, spy.name());
spy = new TestSpy(' ');
System.assertEquals(TestSpy.UNKNOWN, spy.name());
}
@isTest static void itShouldHaveNameSameAsGivenToConstructor() {
String name = 'foo';
spy = new TestSpy(name);
System.assertEquals(name, spy.name());
}
@isTest static void itShouldHaveZeroCallsInitially() {
System.assertEquals(0, spy.count());
}
@isTest static void itShouldHaveVoidReturnInitially() {
System.assert(spy.isVoidReturn());
}
@isTest static void itShouldHaveZeroCallsAfterReset() {
spy.call();
spy.call(1);
spy.reset();
System.assertEquals(0, spy.count());
}
@isTest static void callShouldTrackCallWithNoArguments() {
spy.call();
System.assertEquals(1, spy.count());
System.assert(spy.argsForLastCall().isEmpty());
}
@isTest static void callShouldTrackCallWithArguments() {
Integer arg1 = 1;
String arg2 = 'two';
Boolean arg3 = false;
spy.call(arg1, arg2, arg3);
System.assertEquals(1, spy.count());
Object[] args = spy.argsForLastCall();
System.assertEquals(arg1, args[0]);
System.assertEquals(arg2, args[1]);
System.assertEquals(arg3, args[2]);
}
@isTest static void callShouldTrackCallWith1To10Arguments() {
Object[] expectedArgs = new Object[] {
1, 'two', false, 2.5, DateTime.now(), Date.today(),
new Set<Integer> { 1, 2 },
new List<String> { 'a', 'b', 'c' },
new Map<Integer, String> {
1 => 'a', 2 => 'b'
},
new Account(name = 'FooBar')
};
spy.call(expectedArgs[0]);
spy.call(expectedArgs[0], expectedArgs[1]);
spy.call(expectedArgs[0], expectedArgs[1], expectedArgs[2]);
spy.call(expectedArgs[0], expectedArgs[1], expectedArgs[2], expectedArgs[3]);
spy.call(expectedArgs[0], expectedArgs[1], expectedArgs[2], expectedArgs[3], expectedArgs[4]);
spy.call(expectedArgs[0], expectedArgs[1], expectedArgs[2], expectedArgs[3], expectedArgs[4], expectedArgs[5]);
spy.call(expectedArgs[0], expectedArgs[1], expectedArgs[2], expectedArgs[3], expectedArgs[4], expectedArgs[5], expectedArgs[6]);
spy.call(expectedArgs[0], expectedArgs[1], expectedArgs[2], expectedArgs[3], expectedArgs[4], expectedArgs[5], expectedArgs[6], expectedArgs[7]);
spy.call(expectedArgs[0], expectedArgs[1], expectedArgs[2], expectedArgs[3], expectedArgs[4], expectedArgs[5], expectedArgs[6], expectedArgs[7], expectedArgs[8]);
spy.call(expectedArgs[0], expectedArgs[1], expectedArgs[2], expectedArgs[3], expectedArgs[4], expectedArgs[5], expectedArgs[6], expectedArgs[7], expectedArgs[8], expectedArgs[9]);
System.assertEquals(10, spy.count());
for (Integer i = 0; i < spy.count(); i++) {
Object[] args = spy.argsFor(i) ;
System.assertEquals(i + 1, args.size());
System.assertEquals(expectedArgs[0], args[0]);
if (i > 0) System.assertEquals(expectedArgs[1], args[1]);
if (i > 1) System.assertEquals(expectedArgs[2], args[2]);
if (i > 2) System.assertEquals(expectedArgs[3], args[3]);
if (i > 3) System.assertEquals(expectedArgs[4], args[4]);
if (i > 4) System.assertEquals(expectedArgs[5], args[5]);
if (i > 5) System.assertEquals(expectedArgs[6], args[6]);
if (i > 6) System.assertEquals(expectedArgs[7], args[7]);
if (i > 7) System.assertEquals(expectedArgs[8], args[8]);
if (i > 8) System.assertEquals(expectedArgs[9], args[9]);
}
}
@isTest static void callShouldReturnObjectThatWasSetWithAndReturn() {
String expectedReturn = 'FooBar';
System.assertEquals(expectedReturn, spy.andReturn(expectedReturn).call());
}
class MyTestException extends Exception {}
@isTest static void callShouldThrowExceptionThatWasSetWithAndThrow() {
MyTestException expectedException = new MyTestException('foo');
spy.andThrow(expectedException);
try {
spy.call();
failExceptionExpected();
} catch (MyTestException e) {
System.assertEquals(expectedException.getMessage(), e.getMessage());
}
}
@isTest static void callShouldThrowTestSpyExceptionAsTheExceptionWhenAndThrowIsCalledWithExceptionMessageString() {
String expectedMessage = 'Foo';
spy.andThrow(expectedMessage);
try {
spy.call();
failExceptionExpected();
} catch (TestSpy.TestSpyException e) {
System.assertEquals(expectedMessage, e.getMessage());
}
}
static void failExceptionExpected() {
System.assert(false, 'Fail expected exception to have been thrown');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment