Sunday, 3 January 2016

Salesforce Spring 16 Release - CreatedDate and Apex Unit Tests

Createddate

(Note that this blog post refers to functionality that is intended to be made available in the Spring 16 release of Salesforce, but features may be removed prior to the Spring 16 go-live date, so you may never see this in the wild!)

Introduction

Anyone who has written a reasonable amount of Apex unit tests is likely to have butted up against the shortcoming that the CreatedDate is set when a record is inserted into the Salesforce database. While at first glance this may not seem to be such a huge issue, consider the scenario of a custom Visualforce page, maybe for use in a dashboard, that displays the 10 most recent cases:

Screen Shot 2016 01 03 at 14 58 50

The controller is about as straightforward as it can be - simply querying back the records from the Salesforce database:

public class RecentCasesController
{
    public List<Case> getRecentCases()
    {
        return [select id, CaseNumber, Subject, Account.Name, Contact.Name,
                CreatedDate
                from Case
                order by CreatedDate desc
                limit 10];
    }
}

while the page is equally straightforward, simply iterating the records in a pageblocktable:

<apex:page controller="RecentCasesController">
	<apex:pageBlock title="10 Most Recent Cases">
        <apex:pageBlockTable value="{!recentCases}" var="cs">
            <apex:column value="{!cs.subject}"/>
            <apex:column value="{!cs.Account.Name}" />
            <apex:column value="{!cs.Contact.Name}" />
            <apex:column value="{!cs.CreatedDate}" />
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>

A Testing Problem

Testing this in the Winter 16 release (and earlier), testing this functionality meant a compromise, such as:

  • Inserting a number of records and ensuring that only 10 of them were returned, but being unable to predict which 10 this would be. This is because the CreatedDate field has second granularity, and if there are a number of records inserted in the same second, there is no guarantee of the order they will be returned in when the query is ordered by the CreatedDate. The Salesforce database will do the least amount of work to execute the query, and there’s nothing you can do to affect it. From a unit testing perspective, unless you can 100% guarantee the order of the results, asserting for a particular order makes a test fragile.
  • Adding an autonumber field to the case sobject so that you can rely on that to guarantee the ordering. This is a classic example of changing your implementation to satisfy your unit tests, which if nothing else is annoying to have to resort to.
  • Trying to kill some time to allow the second to tick over in between record inserts, by looping a lot and generating debug statements for example. There are a couple of problems with this approach. First, it is fragile, as you have no control over how much time would be burned this way. All you can do is hope that the performance you have seen in the past continues, which can’t give much confidence. More importantly, it is evil, as you are consuming shared resources for no good reason.
  • Provide some form of dependency injection, probably via a @TestVisible private property, to allow your test to inject the list of cases that should be returned as a result. This mechanism means that all you are really testing is that the controller returns the list of cases you asked it to, bypassing the query that will be executed in production. Writing code that solely allows your tests to complete successfully is usually an indication that something is awry.

One DOES Simply Set the CreatedDate

In the Spring 16 release this is all set to change (although it may not - see the note at the top of this blog). The new Test.setCreatedDate(Id, DateTime) method allows you to change the CreatedDate once a record is inserted.

For my recent cases page, I can now write a test case to verify that only the 10 most recent cases are returned, by setting the CreatedDate for each case record after I’ve inserted them in the database:

@isTest
private class RecentCasesController_Test
{
    @isTest
    static void TestGetRecentCases()
    {
        /* setup */
        Account acc=new Account(Name='Unit Test');
        insert acc;
        
        Contact cont=new Contact(FirstName='Unit',
                                 LastName='Tester');
        insert cont;
        
        List<Case> cases=new List<Case>();
        for (Integer idx=1; idx<=20; idx++)
        {
            Case cs=new Case(Subject='Test Case ' + idx,
                             AccountId=acc.Id,
                             ContactId=cont.Id);
            cases.add(cs);
        }
                             
        insert cases;
        
        // now set the created date for each case - the first case
        // in the list will be the most recent, the second case the
        // second most recent, and so on
        for (Integer idx=1; idx<=20; idx++)
        {
            DateTime created=System.now().addDays(-(idx*10));
            Test.setCreatedDate(cases[idx-1].Id, created);
        }
	
        /* execute */
        RecentCasesController ctrl=new RecentCasesController();
        List<Case> recentCases=ctrl.getRecentCases();

        /* verify */
 
        // should be 10 cases
        System.assertEquals(10, recentCases.size());
        
        // should be the first 10 inserted, in that order
        for (Integer idx=0; idx<10; idx++)
        {
            System.assertEquals(recentCases[idx].Id, cases[idx].Id);
        }
    }
}

Final Thoughts

The ability to set the created date is a much needed addition to the Salesforce platform testing framework, and one that will allow production code to be properly tested, and allow some workarounds/terrible hacks to be retired. 

It would be cool to be able to set the CreatedDate when constructing the sObject, prior to insertion, but I’d imagine this would require a ton of changes to the Salesforce database layer and isn’t going to happen. I foresee thousands of test fixture classes that insert a record and then change its CreatedDate!

Its a shame that at the moment you can only set the CreatedDate of a single record per call, but hopefully we’ll get some bulk capability in the future. The Salesforce platform approach is typically to make things doably difficult, then add the bells and whistles.

Finally, the documentation doesn’t mention any effects on limits and my testing with the code above bears this out, which is always good.

Related Posts

4 comments:

  1. Thanks Bob super helpful when testing processes based on record creation date.

    ReplyDelete
  2. Thanks Bob super helpful when testing processes based on record creation date.

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
  4. Great Post Keir. This will be super helpful functionality.

    ReplyDelete