Last active
August 29, 2015 13:57
-
-
Save alan-morey/8cf6d0e24b1aa713a61c to your computer and use it in GitHub Desktop.
Apex Test Spy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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