A Pattern for Portable Apex Unit Tests

Friday, August 5, 2011

Force.com is a multi-tenant platform, so if your code is running rampant, your neighbors will feel its wrath. It only makes sense that Salesforce requires developers on its platform to write unit tests. When refactoring code, unit tests will tell you when something breaks. You may find that writing your unit tests for a piece of code is too difficult, which is a sign that your design is too complex and will be unwieldy when debugging at later time or by someone else. There are pitfalls when using any unit test framework, but I want to take some time to highlight a few of the more sinister pitfalls that will be invisible to you until you fall into them head-first.

Field requirements change.
The first common design oversight is not keeping in mind the fact that field requirements may change. It's inevitable during the growth and refining of any Salesforce org, if not every software application, that requirements change. This could be that older objects have new fields, new required fields, or changed field definitions. This is fine, right? You aren't hard-coding object creation into each and every unit test, are you? What's that? To shreds, you say? Well, if you do hard-code sObject creation in your unit tests, then you're probably in the majority, so don't worry, it's definitely the most direct and simple way to write them. Let's see how that would look:

static testMethod void testCreateMyObject_shouldSucceed() {
    //Create test data
    MyObject__c object1 = new MyObject__c(Name = 'TestName', MyField__c = 50);
    
    //Invoke functionality
    Test.startTest();
    String errorMessage = '';
    try {
        Database.insert(object1);
    catch (DmlException e) {
        errorMessage = e.getMessage();
    }
    Test.stopTest();
    
    //Check results
    System.assertEquals('', errorMessage);
}

This looks fine, right? It sure does, so let's copy and paste this unit test about thirty times to test for the various edge-cases. Cool. Now fast-forward a few months to when MyObject__c gets MyField2__c added to it and made to be a required field. Ka-boom! Time to rewrite thirty unit tests!

Moving code to a different org.
Some developers will have to package their code and move it to a different org. If you are one of these developers, I'm sure you've had a multitude of issues with this before. You've got your code working great in your sandbox org, and then applied some fixes after discovering that your code doesn't account for running in an empty org and can't handle a few measly null references. After that experience, you'll be a little nervous when it becomes time to install it into a production org, or worse, a production org with complex workflows and triggers.

Fears coming true, when installing your wonderful package of joy into the production org, you find that your code may be trying to insert a standard Contact record with the bare minimum number of fields and the Contacts that are being inserted by your unit tests are being rejected. How could you know that the installing org decided to make Contact.FavoriteCRMSoftware__c a required field! Time to rewrite some unit tests!

Force.com Portable Tests Pattern
So what's a dev to do? Now that we have the foresight, what can we do to evade these errors and save ourselves some unit test re-writing? Thinking about it, we find the crux to be this question: How can we successfully insert data when we don't know the conditions for successful insertion at design time?

The suggested answer: For most cases, isn't it enough to query for an existing record in the database to use? That record is in the org, so it must already have the information required by the org. So grab one, modify the record to the state that your test needs, and go with it. If you need to test insertion, this probably won't work (though maybe you could query for a record, and set the ID of the returned record to null, then insert it as a new record. Hmm...).

I postulate that the best solution for these issues is to use the following Force.com Portable Tests Pattern (please suggest a better name). It uses a TestObjects class that acts as a kind of record factory that abstracts away an individual unit test's responsibility for creating/querying for a record to use in your test. Check out how we would change the example above:

static testMethod void testCreateMyObject_shouldSucceed() {
    //Create test data
    
    //Invoke functionality
    Test.startTest();
    String errorMessage = '';
    try {
//This method creates and inserts it for us. We could also use the createMyObject method in this case.
MyObject__c object1 = TestObjects.getMyObject();
catch (DmlException e) { errorMessage = e.getMessage(); } Test.stopTest(); //Check results System.assertEquals('', errorMessage); }

And then use a TestObjects class that will handle the creation/querying for a record to use:

public with sharing class TestObjects {
//Use the get* method if you want to do the query-first object creation.
   public static MyObject__c getMyObject() {
 MyObject__c myObject = new MyObject__c();
 try { //Try to query for the desired record first.
  myObject = [SELECT Name, MyField__c FROM MyObject__c LIMIT 1];
 }
 catch (QueryException e) { // If that fails, then create one.
  myObject = TestObjects.createMyObject('TestFeature', 50);
 }
 return myObject;
}
//If you want to skip the query-first part, just call the create* method.
//If you discover a required field in the org, you only need to change this method, not every single unit test.
public static MyObject__c createMyObject(String name, integer myField) {
 MyObject__c myObject = new MyObject__c(
  Name = name,
  MyField__c = myField);
 Database.insert(myObject);
 return myObject;
   }
}

What do you think of this structure? Do you see anything I'm missing? Can it be made more robust? Should we change the name of the TestObjects class to something else? Leave a comment below.