Apex Testing for Humans

and dumb robots

Who I am

Patrick Connelly

@aelst

Why testing?

Because you have to, and you should anyway!

3 Testing Rules

Three main rules for testing

Rule 1

Test.startTest()Test.stopTest()

Code run between these two causes all limits to be reset. Should be used to make sure your code doesn't hit limits. Test setup can interfere with those limits.

Rule 2

Generate testing data in the test

Rule 3

Tests only evaluate a single part of functionality

Testing No-Nos

Things you should never do:

* Unless it is absolutely, positively 100% required

No-Op AKA Null Operations

We get it. Testing somethings can be hard. The test code below would only return a code coverage of 66%

public static myMethod() {
	if (reallyDifficultToReproduceBoolean) {
		doSomething();
	}

	doSomethingElse();
}

static testMethod void myMethod_test() {
	myMethod();
}

No-Op AKA Null Operations

The code below will increase your coverage to 83% but it will also kill at least 9 kittens.

DON'T EVER DO THIS! THINK OF THE KITTENS!!

public static myMethod() {
	Integer i = 0;

	if (i = 0) {
		i = i;
	}

	if (reallyDifficultToReproduceBoolean) {
		doSomething();
	}

	doSomethingElse();
}

static testMethod void myMethod_test() {
	myMethod();
}

TestUtils

What do you mean TestUtils? Why?

TestUtils — getters & setters

Let TestUtils generate your objects for you

//Keeps us from having duplicate names
static private NAME_COUNT = 10000;
static private NAME_INC = 1;

//Lets us set the name manually if we want
public static MyObject__c getMyObject(Account a, String name) {
	return new MyObject__c(
		Account__c = a.Id,
		Name = name,
		...
	);
}

//Gives us an easy way to get a unique object.
//   This will make bulk operations easier
public static MyObject__c getMyObject(Account a) {
	MyObject__c result = getMyObject(a, '_unittest_name_: ' + NAME_COUNT);
	NAME_COUNT += NAME_INC;
	return result;
}

TestUtils — fetch

Let TestUtils fetch the stored object for you

public static MyObject__c fetchMyObject(MyObject__c obj) {
	return [
		select Account__c,
			Name,
			...
		from MyObject__c
		where Id = :obj.Id
	];
}

public static List<MyObject__c> fetchMyObjects(Account a) {
	return [
		select Account__c,
			Name,
			...
		from MyObject__c
		where Account__c = :a.Id
	];
}

Exceptions

When doing samurai programming,* you rely heavily on exceptions

Exceptions in SalesForce can kinda be a pain to test:

*Return successful or don't return at all

Exceptions — The known exception

When you have a known path to generate an exception

public class MyClass {
	public static String RESULT_STRING = 'Result data: ';
	public static String NONZERO_MSG = 'Parameter must be non-zero';
	public class BadParameterException extends Exception {}
				
	public static String myMethod(Integer i) {
		if (i == 0) {
			throw new BadParameterException(NONZERO_MSG);
		}

		return RESULT_STRING + i;
	}
}

Exceptions — Known exception testing

static testMethod void positiveTest() {
	Integer i = 10;
	String result = MyClass.myMethod(i);
	String expectedResult = MyClass.RESULT_STRING + i;

	System.assertEquals(expectedResult, result, 'Wrong result');
}

static testMethod void negativeTest() {
	Integer i = 0;

	try {
		String result = MyClass.myMethod(i);
		System.assert(false, 'We should have thrown an exception');
	} catch (MyClass.BadParameterException e) {
		System.assertEquals(
			MyClass.NONZERO_MSG, e.getMessage(),
			'Right exception type, wrong message'
		);
	}
}

Exceptions — Unknown exceptions

When you could have an exception but can't reproduce it

global with sharing class MyAPI {
	public static String ERR_MSG = 'OH NOES!';

	WebService static String myWebservice(Integer i) {
		String result = i;

		try {
			MyOtherClass.somePotentiallyDangerousMethod(i);
		} catch (Exception e) {
			result = ERR_MSG;
		}

		return result;
	}
}

global with sharing class MyAPI {
	public static Boolean THROW_EXCEPTION = false;
	public class UnhandledException extends Exception {}

	public static void generateExceptionForTesting() {
		if (Test.isRunningTest() && THROW_EXCEPTION) { 
			throw new UnhandledException('Exception for testing');
		}
	}

	public static String ERR_MSG = 'OH NOES!';

	WebService static String myWebservice(Integer i) {
		String result = i;

		try {
			generateExceptionForTesting();
			MyOtherClass.somePotentiallyDangerousMethod(i);
		} catch (Exception e) {
			result = ERR_MSG;
		}

		return result;
	}
}

Exceptions — Unknown exceptions testing

static testMethod void negativeTest() {
	Integer i = 0;

	Test.startTest();

	MyAPI.THROW_EXCEPTION = true;
	String result = MyAPI.myWebservice(i);

	Test.stopTest();

	System.assertEquals(MyAPI.ERR_MSG, result, 'Wrong result');
}

Bulk data operations

When writing tests, don't forget to test in bulk

You may think that you will never do anyting in bulk with your data, but you might, and you'll spend lots of time debugging and cursing.

Things to consider when making your triggers to make them bulk ready:

TestUtils & bulk data

Adding bulk data operations to your TestUtils class will allow you to easily create large sets of data

public static Integer DEFAULT_BULK_COUNT = 200;

List<MyObject__c> getMyObjects(Integer count) {
	List<MyObject__c> result = new List<MyObject__c>();

	for (Integer i = 0; i < count; i++) {
		result.add(getMyObject());
	}

	return result;
}

List<MyObject__c> getMyObjects() {
	return getMyObjects(DEFAULT_BULK_COUNT);
}

Bulk data testing

By defining our expected results we can ensure we get back all the ones we expect and no results we don't

Set<Id> expectedResults = new Set<Id> {
	testObj1.Id, testObj2.Id, testObj3.Id
};

Test.startTest();
List<MyObject__c> results = MyClass.someMethod();
Test.stopTest();

for (MyObject__c obj: results) {
	if (!expectedResults.contains(obj.Id)) {
		System.assertEquals(false, 'Got an object we did not expect');
	}

	expectedResults.remove(obj.Id);
}

System.assertEquals(expectedResults.isEmpty(),
	'Did not get back all results [' + expectedResults + ']');

Test Visible

Introduced Winter '14 — Allows tests to directly access private variables and methods in your test

public class MyClass {
	@testVisible private String data;

	public MyClass() {}

	public setString(String s) {
		this.data = s;
	}
}

static testMethod void myTest() {
	String testData = '_unittest_string_: 001';
	MyClass mc = new MyClass();

	mc.setString(testData);

	System.assertEquals(testData, mc.data, 'Wrong data');
}

Questions?

/

#