Abstract

This article shows how to craft good unit tests. It explores the proper structure of unit tests, the code scenarios that unit tests should cover, and the properties of well-written unit tests. There are plenty of code samples interspersed throughout the article to demonstrate and help illustrate the concepts.

Unit Test Introduction

A unit test is code that exercises a specific portion of your codebase in a particular context. Typically, each unit test sends a specific input to a method and verifies that the method returns the expected value, or takes the expected action. Unit tests prove that the code you are testing does in fact do what you expect it to do.

The Force.com platform requires that at least 75% of the Apex Code in an org be executed via unit tests in order to deploy the code to production. You shouldn’t consider 75% code coverage to be an end-goal though. Instead, you should strive to increase the state coverage of your unit tests. Code has many more possible states than it has lines of code. For example, the following method has 4,294,967,296 different states:

double getFraction(Integer a){ return 1/a;}

Clearly, you wouldn’t want to test all of the different states for this program. Instead, you should probably test a few different inputs for this method, even if it means that you will have achieved 100% code coverage several times over.

Three different states that you might consider testing are a positive input, a negative input and a 0 input. Testing with a positive input, and with a negative input will behave as expected. Testing with a 0 input however will yield a surprise System.MathException. This is just one example of why it makes sense to focus on testing the different states of your code instead of focusing on just the 75% code coverage requirement.

The Value of Unit Tests

One of the most valuable benefits of unit tests is that they give you confidence that your code works as you expect it to work. Unit tests give you the confidence to do long-term development because with unit tests in place, you know that your foundation code is dependable. Unit tests give you the confidence to refactor your code to make it cleaner and more efficient.

Unit tests also save you time because unit tests help prevent regressions from being introduced and released. Once a bug is found, you can write a unit test for it, you can fix the bug, and the bug can never make it to production again because the unit tests will catch it in the future.

Another advantage is that unit tests provide excellent implicit documentation because they show exactly how the code is designed to be used.

Many developers have discovered these advantages. Some developers, including me, believe so strongly in the value of unit tests that we write our tests first and our production code second. I’m going to show you an example of this test-first development practice a little later on in the article.

How to write and run unit tests

If you’re not yet familiar with writing and running Apex unit tests, read this excellent Introduction to Apex Code Test Methods.

Unit Test Structure

Let’s take a look at how unit tests are best structured. All unit tests should follow the same basic structure.

A unit test should:

  • Set up all conditions for testing.
  • Call the method (or Trigger) being tested.
  • Verify that the results are correct.
  • Clean up modified records. (Not really necessary on Force.com.)

Let’s discuss each of these in turn.

Set Up All Conditions for Testing

Typically, methods perform some sort of operation upon data. So in order to test your methods, you’ll need to set up the data required by the method. This might be as simple as declaring a few variables, or as complex as creating a number of records in the Force.com database. For example, if you have a trigger like this one that updates an Opportunity’s parent Account after the Opportunity is inserted:

trigger updateParentAccountWithOpportunityName on Opportunity (after insert) {

    // Create a Map from Account Ids to Opportunities.
    Map<Id, Opportunity> accountIdOpportunityMap = new Map<Id, Opportunity>();

    for(Opportunity o : Trigger.new){
        accountIdOpportunityMap.put(o.AccountId, o);
    }
	
    // Create a list of Accounts to Update.
    List<Account> accounts = new List<Account>();

    for(Account a : [SELECT Id, Most_Recently_Created_Opportunity_Name__c 
                     FROM Account 
                     WHERE Id IN :accountIdOpportunityMap.keySet()]){
        a.Most_Recently_Created_Opportunity_Name__c = 
                               ((Opportunity) accountIdOpportunityMap.get(a.Id)).Name;
        accounts.add(a);
    }
	
    update accounts;	
}


Then, your setup code could look something like this:

Account a = new Account(Name='My Account');
insert a;

Opportunity o = new Opportunity(AccountId=a.Id, Name='My Opportunity', 
                                StageName='Prospecting', CloseDate=Date.today());


Your unit tests should always create their own test data to execute against. That way, you can be confident that your tests aren’t dependent upon the state of a particular environment and will be repeatable even if they are executed in a different environment from which they were written.

If you find that many of your unit tests require very similar setup code, be sure to properly decompose the setup code so that you don’t repeat yourself.

Call the method (or Trigger) being tested

Once you have set up the appropriate input data, you still need to execute your code. If you are testing a method, then you will call the method directly. In this case, we’re testing a Trigger, so we’ll need to perform the action that causes the trigger to execute. In our sample, that means that we will need to insert the Opportunity:

insert o;


Verify the results are correct

Verifying that your code works as you expect it to work is the most important part of unit testing. It’s also one of the things that Force.com developers commonly neglect. Unit tests that do not verify the results of the code aren’t true unit tests. They are commonly referred to as smoke tests, which aren’t nearly as effective or informative as true unit tests.

A good way to tell if unit tests are properly verifying results is to look for liberal use of the System.assert() methods. If there aren’t any System.assert() method calls, then the tests aren’t verifying results properly. And, no, System.assert(true); doesn’t count.

Sample verification code for this trigger might look like this:

a = [SELECT Name, Most_Recently_Created_Opportunity_Name__c 
     FROM Account 
     WHERE Id = :a.Id];
		
System.assertEquals('My Opportunity', a.Most_Recently_Created_Opportunity_Name__c);


Notice that this unit testing code explicitly verifies that the trigger performed the action that we expected.

Clean up (is Easy!)

Cleaning up after unit tests is easy, because there’s nothing to do! Actions performed on records inside of a unit test are not committed to the database. This means that we can insert, delete, and modify records without having to write any code that will clean up our changes. Even the results of trigger executions that affect existing data are not committed!

Where to put tests?

Your Trigger files can never contain code other than the Trigger definition, so Trigger unit tests must always be located in a separate class file.

It also makes sense to put unit tests for your classes in a separate class file as well. The most important reason for this is that by separating your class implementation and your unit tests, you will automatically be prevented from testing private methods and private properties. You shouldn’t test private methods and private properties because doing so will cause your unit tests to become a barrier to refactoring. With your classes and unit tests separated in to different files, you will always have the option to change the internal implementation of your classes should the need arise. If you ever do find yourself compelled to test a private or protected method, this is probably a strong indication that the method should be refactored in to its own stand-alone class.

As an additional incentive, this best practice of separating unit tests and classes also allows you to take advantage of the @isTest annotation. The code inside of an @isTest annotated file does not count against the overall Apex code size limitation for your org.

Putting it all together

Here's the complete unit test for the trigger, which I've put in UpdateParentAccountWithOpportunityNameTest.cls.

@isTest
private class UpdateParentAccountWithOpptyNameTest{
  static testMethod void testUpdateParentAccount(){		

    // Set up the Account record.
    Account a = new Account(Name='Test Account');
    insert a;

    // Verify that the initial state is as expected.
    a = [SELECT Name, Most_Recently_Created_Opportunity_Name__c 
         FROM Account 
         WHERE Id = :a.Id];
    System.assertEquals(null, a.Most_Recently_Created_Opportunity_Name__c);

    // Set up the Opportunity record.
    String opportunityName = 'My Opportunity';
    Opportunity o = new Opportunity(AccountId=a.Id, Name=opportunityName, 
                                    StageName='Prospecting', CloseDate=Date.today());

    // Cause the Trigger to execute.
    insert o;

    // Verify that the results are as expected.
    a = [SELECT Name, Most_Recently_Created_Opportunity_Name__c 
         FROM Account 
         WHERE Id = :a.Id];
    System.assertEquals(opportunityName, a.Most_Recently_Created_Opportunity_Name__c);
  }
}


Note the use of the @isTest annotation, as well as the general structure of the test - first the setup, then the action, and finally the verification.

What to Test

In the previous section, we discussed how unit tests should be structured, and where they should be located. In this section, we will discuss what types of scenarios you should test.

Broadly speaking, you should test your custom business logic. How thoroughly you test that business logic will probably vary between situations. On one end of the spectrum, you might choose to implement just a few tests that only cover the code paths that you believe are most likely to contain a bug. On the other end of the spectrum, you might choose to implement a large suite of unit tests that are incredibly thorough and test a wide variety of scenarios. Wherever a given project falls on that spectrum, you should be sure to write unit tests that verify your code behaves as expected in "normal" scenarios as well as in more "unexpected" scenarios, like boundary conditions or error conditions.

In order to give you a better idea of the kinds of things that you should be testing, I'll walk through the process of writing unit tests for an implementation of a Stack data structure that can be used to store Strings.

Stack Interface

The first thing that I need to do is define the publicly accessible methods for our StringStack class:

public class StringStack {
    public void push(String s){}
    public String pop() { return null; }
    public String peak() { return null; }
    public Boolean isEmpty() { return true; }
}


You’ll notice that I haven’t written any real implementation code for these method definitions. That’s because I’m going to use Test Driven Development techniques in this example. In Test Driven Development, unit tests are written before any real implementation code is written. I’ve found that writing my unit tests first helps me better understand how clients of my code are going to use it.

Testing Normal Conditions

Next, I’m going to write a test that will exercise my code in the most basic way – I’m going to add just one String value to the Stack:

/* Verifies that push(), pop() and peak() work correctly
 * when there is only 1 object on the Stack. */
static testMethod void basicTest() {
    // Instantiate a StringStack.
    StringStack stack = new StringStack();
    
    // Verify the initial state is as expected.
    System.assert(stack.isEmpty());
    
    // Set up some test data.
    String onlyString = 'Only String';
    
    // Call the push() method and verify the Stack is no longer empty
    stack.push(onlyString);
    System.assert(!stack.isEmpty());
    
    // Verify that the value we pushed on the Stack is the one we expected
    String peakValue = stack.peak();
    System.assertEquals(onlyString, peakValue);
    System.assert(!stack.isEmpty());
    
    // Verify the Stack state after pop() is called.
    String popValue = stack.pop();
    System.assertEquals(onlyString, popValue);
    System.assert(stack.isEmpty());
}


If I run this test right now, my code is going to fail:

GoodTesting BasicTest.jpg


So, now that I know what I need to implement, I’m going to write the code needed to pass my first unit test:

public class StringStack {
	
	private List<String> stack;
	
	public StringStack(){
	    stack = new List<String>();
	}
	
	public void push(String s){ stack.add(s); }
	
	public String pop() { 
	    return stack.remove( lastItemIndex );
	}
	
	public String peak() { 
	    return stack.get( lastItemIndex );
	}

	public Boolean isEmpty() { return stack.isEmpty(); }
	
	// Helper Property
	private Integer lastItemIndex {
	    get { return stack.size() - 1; }
	}
}


Now if I run the unit test again, my code passes the test:

GoodTesting BasicTest Pass.jpg


In fact, you can see that I have 100% code coverage. I could stop here, but I really want to be confident in my code, so there are a few more conditions that I’m going to test. I haven’t yet verified that my Stack implementation handles multiple values, and that it can pop() them in the correct order, so my next test is going to verify those things:

/* Verifies that push(), pop() and peak() work correctly
 * when there are multiple objects on the Stack. */
static testMethod void verifyCorrectOrderTest() {
    // Instantiate a StringStack.
    StringStack stack = new StringStack();
    
    // Set up some test data.
    String bottomString = 'Bottom String';
    String middleString = 'Middle String';
    String topString = 'Top String';
    
    // Call the push() method with multiple objects
    stack.push(bottomString);
    stack.push(middleString);
    stack.push(topString);
    
    // Verify that the 'top' object is the object we expected 
    String peakValue = stack.peak();
    System.assertEquals(topString, peakValue);
    
    // Verify that the order of the objects is as we expected
    String popValue = stack.pop();
    System.assertEquals(topString, popValue);
    popValue = stack.pop();
    System.assertEquals(middleString, popValue);
    popValue = stack.pop();
    System.assertEquals(bottomString, popValue);
    System.assert(stack.isEmpty());
}


My code passes this test too, so I am pretty confident that it handles the "normal" conditions pretty easily. Next, let’s test some of the more "unexpected" scenarios.

Testing Unexpected Conditions

There are many scenarios that your code shouldn’t encounter. However, you can’t trust that clients of your code will always do the right thing, so you have to make sure that the code will still handle these unexpected scenarios appropriately. Let’s look at a few examples:

Bad Input Values

One potentially unexpected condition that the code might encounter is an unexpected value, like null, being passed to the push() method. You have a few implementation options for handling this scenario. Your code could ignore the null value, it could insert a special placeholder value, or it could not allow null values to be pushed on to the Stack at all. In my implementation, I’m not going to allow null values to be pushed on to the Stack at all, and I’m going to throw an exception if a client my code attempts to do so. My unit tests should explicitly test this design decision, so I’m going to write it first:

static testMethod void nullValueNotAllowedExceptionTest() {
    StringStack stack = new StringStack();

    try{
        stack.push(null);
    }catch (StringStack.NullValueNotAllowedException e){
        // Exit the test if the expected NullValueNotAllowedException is thrown.
        return;
    }
    // Fail the test if the expected NullValueNotAllowedException is not thrown.
    System.assert(false, 
            'A NullValueNotAllowedException was expected, but was not thrown.');
}


At this point, the code is still going to fail this new unit test, so I need to change my push() implementation to make it pass this new unit test:

public void push(String s){ 
	if (s == null) { throw new NullValueNotAllowedException(); }
	stack.add(s); 
}

public class NullValueNotAllowedException extends Exception {}


Now the implementation passes all unit tests again.

Bad input values are one of the most commonly encountered unexpected scenarios in development. A common scenario in Apex where you might encounter unexpected, or missing data is if a query doesn’t return a record, but your code expects one to be present:

Account a = [SELECT Name FROM Account WHERE Id = :accountId];
String accountName = a.Name;

If the record that you are querying for has gone missing due to a deletion, or some other action, you’re going to have a bug on your hands because a won't be pointing to a record, but will be null.

Boundary Conditions

Boundary conditions are another common source of bugs. Let’s verify that our StringStack implementation handles the overflow and underflow boundary conditions.

The Apex documentation indicates that a List can only contain 1,000 records. If a 1,001th object were to be added to our List-based Stack implementation, a System exception would be thrown. Instead of letting this generic exception be thrown, I’m going to design a test that expects a StringStack-specific exception be thrown when there is an overflow condition.

static testMethod void stackOverflowTest() {
    StringStack stack = new StringStack();
        
    try{        
        for(Integer i = 0; i < StringStack.MAX_STACK_DEPTH + 1; i++){
            stack.push('String ' + i);
        }
    }catch (StringStack.StackOverflowException e){
        // Exit the test if the expected StackOverflowException is thrown.
        return;
    }
    // Fail the test if the expected StackOverflowException is not thrown.
    System.assert(false, 
            'A StackOverflowException was expected, but was not thrown.');       
}


Notice that the above unit test illustrates a common, and useful, pattern for verifying that a piece of code does in fact throw an exception as expected. The unit test’s try-catch block only catches a StackOverflowException. If this StackOverflowException is not thrown, this unit test would fail.

Now that I have a unit test that expects a StackOverflowException to be thrown, I need to modify the code to actually throw this exception:

public void push(String s){ 
	if (s == null) { throw new NullValueNotAllowedException(); }
	if (this.isFull()) { throw new StackOverflowException(); }
	stack.add(s); 
}

public static final Integer MAX_STACK_DEPTH = 1000;
public Boolean isFull() { return MAX_STACK_DEPTH == stack.size(); }

public class StackOverflowException extends Exception {}


Now that I’ve handled the overflow boundary condition, I also need to test the underflow boundary condition where there is nothing in the stack to pop(). My current implementation of pop() will generate a System.ListException in this scenario. I want to hide the internal implementation of my StringStack class though, so I’m going to write a unit test that expects a StringStack-specific exception be thrown in this scenario too.

static testMethod void stackPopUnderflowTest() {
    StringStack stack = new StringStack();
        
    try{        
        stack.pop();
    }catch (StringStack.StackUnderflowException e){
        // Exit the test if the expected StackUnderflowException is thrown.
        return;
    }
    // Fail the test if the expected StackUnderflowException is not thrown.
    System.assert(false, 
            'A StackUnderflowException was expected, but was not thrown.');       
}


Here’s the modified version of the pop() method that passes this new unit test:

public String pop() { 
    if (this.isEmpty()) { throw new StackUnderflowException(); }
    return stack.remove( lastItemIndex );
}

public class StackUnderflowException extends Exception {}


Notice that this exception-expecting unit test follows the same pattern as the previous exception-expecting unit test. This is a very useful pattern.

Regression Tests

Fixing bugs is usually frustrating. It’s even more frustrating when you have to fix the same bug multiple times. That’s why it’s a good idea to add tests to your test suite every time you find a bug in your code. For example, if I were to release my code in to production as it is, a StringStack client might discover that the peak() method doesn’t handle the underflow boundary condition in the same way as the pop() method. In order to verify that this bug doesn’t get reintroduced in a future release, I’m going to write one last unit test:

static testMethod void stackPeakUnderflowTest() {
    StringStack stack = new StringStack();
        
    try{        
        stack.peak();
    }catch (StringStack.StackUnderflowException e){
        // Exit the test if the expected StackUnderflowException is thrown.
        return;
    }
    // Fail the test if the expected StackUnderflowException is not thrown.
    System.assert(false, 
            'A StackUnderflowException was expected, but was not thrown.');       
}


And finally, I’ll make the following change so that my code passes this new unit test:

	
public String peak() { 
    if (this.isEmpty()) { throw new StackUnderflowException(); }
    return stack.get( lastItemIndex );
}


The full StringStack code can be found here: StringStack.cls.

The corresponding unit test code can be found here: StringStackTest.cls.

Making use of Test.isRunningTest() method

There are some situations, when you will just not be able to test some code in a normal matter - eg. you are using objects that cannot be inserted in tests, or doing some http requests. In such case, if you think there is no other option, consider using Test.isRunningTest(). This static method allows you to discover whether the code was run from test method. Therefore, for example you might:

  • return hardcoded String instead of calling http request and parsing the body
  • return a fixed array of objects from a method

It might be actually used as a basic introduction to mocks, which are very widespread in other languages.

Force.com-specific Testing

In addition to verifying that your code executes properly in normal scenarios, testing edge cases and preventing regressions, you may find that you need to test certain Force.com-specific aspects of your application as well. Things like Apex Callouts, SOSL queries, Governor Limits, etc. To learn more about these topics you’ll want to read these articles:

Testing Apex Callouts

Testing SOSL

Verifying Large DataSets

Testing with RunAs

Properties of Well-Written Unit Tests

Well-written unit tests are thorough, repeatable, and independent. Let’s examine each of these properties in turn.

Thorough

The most important aspect of well-written unit tests is that your unit tests must be thorough. The Force.com platform requires 75% code coverage, but this is a minimum and should not be your end goal. You should only be satisfied with your unit tests when you’re confident that your unit tests thoroughly verify that your code works as we expect it to work. To do this, your tests must use the various System.assert() calls. Unit tests should exercise the expected as well as the unexpected conditions in your code. Special attention should be paid to areas of code that are particularly likely to break.

Repeatable

Every one of your unit tests should be able to be run repeatedly and continue to produce the same results, regardless of the environment in which the tests are being run. When your unit tests can be run in a repeatable and consistent manner, you can trust that your unit tests will only detect and alert you to real bugs, and not environmental differences.

One common mistake that might prevent you from writing repeatable unit tests is relying upon hard-coded information, like server urls. Your unit tests should not rely on hard-coded server urls because unit tests must be written in either a sandbox org or in a developer org. Sandbox orgs will always have different urls than the production orgs will have, and developer orgs will usually have different urls than the production org will have. If you hard-code the server urls, the unit tests may fail when you attempt to move your code to production. Another common mistake that might prevent your unit tests from being repeatable is relying upon hard-coded record ids. Record ids are entirely org-specific, and can not be used outside of a given org. Instead of relying upon hard-coded record ids, unit tests should create their own data, as we described in the Set Up All Conditions for Testing section.

Independent

Your unit tests should only test one aspect of your code at a time, so that it is easy to understand the purpose of each unit test. Properly written, and well-decomposed unit tests are an excellent source of documentation. This may mean that you will need to write multiple unit tests in order to fully exercise one method. I’ve shown you one example of this in the Testing Normal Conditions section.

Also, keep in mind that each of your unit tests must be independent from your other unit tests. Unit tests do not alter the state of the Force.com database, and they are not guaranteed to run in a particular order, so you cannot rely upon one unit test to do the setup work for another unit test.

Summary

This article provides instruction on crafting good test methods. It explores the proper structure of unit tests, the kinds of things your unit tests should test, and the properties of well-written unit tests. The unit tests that make up an automated test suite are valuable development tools that can help you ensure code quality and can simplify regression testing.

References

About the Author

Jesse Lorenz is a Technical Evangelist at salesforce.com, helping independent software vendors, product teams and other partners to architect and build innovative applications on Force.com.