160

An example of the problem would be trying to test a trigger on a case which will fire on update, and look for time differences between when a case was created and now. Ideally in the unit tests I want to create a case with a CreatedDate in the past. The problem here is that CreatedDate is an audit field and not writeable[1]. Is there a standard work around to this problem?

[1] I know you can get audit fields turned on to be writeable temporarily when doing data migration. This is not a viable solution for every time you want to get your code coverage up for deployment.

0

8 Answers 8

157

Spring '16 Release Notes document a new system method supporting this:

Test.setCreatedDate(recordId, createdDatetime)

Sets CreatedDate for a test-context sObject.

This will definitely complement the loadData() and deserialize() techniques.

Account account = new Account(Name = 'Test');
insert account;

Datetime yesterday = Datetime.now().addDays(-1);
Test.setCreatedDate(account.Id, yesterday);

Test Class: Contains methods related to Apex tests.

setCreatedDate(recordId, createdDatetime)

Sets CreatedDate for a test-context sObject.

All database changes are rolled back at the end of a test. You can’t use this method on records that existed before your test was executed. You also can’t use setCreatedDate in methods annotated with @isTest(SeeAllData=true), because those methods have access to all data in your org. If you set CreatedDate to a future value, it can cause unexpected results. This method takes two parameters—an sObject ID and a Datetime value—neither of which can be null.

Insert your test record before you set its CreatedDate, as shown in this example.

@isTest 
private class SetCreatedDateTest {
    static testMethod void testSetCreatedDate() {
        Account a = new Account(name='myAccount');
        insert a;
        Test.setCreatedDate(a.Id, DateTime.newInstance(2012,12,12));
        Test.startTest();
        Account myAccount = [SELECT Id, Name, CreatedDate FROM Account 
                             WHERE Name ='myAccount' limit 1];
        System.assertEquals(myAccount.CreatedDate, DateTime.newInstance(2012,12,12));
        Test.stopTest();
    }
}
0
82

You can create sObjects in memory with arbitrary CreatedDate values by using JSON.deserialize. This doesn't enforce the normal read-only field attributes that prevent you from setting a createdDate value. However you can't commit arbitrary CreatedDate values to the database (or else it would be a serious security issue).

An example of doing so :

String caseJSON = '{"attributes":{"type":"Case","url":"/services/data/v25.0/sobjects/Case/500E0000002nH2fIAE"},"Id":"500E0000002nH2fIAE","CreatedDate":"2012-10-04T17:54:26.000+0000"}';
Case c = (Case) JSON.deserialize(caseJSON, Case.class );
System.debug(c.createdDate);

Note that I built the caseJSON string by creating a test case and serializing it, which is the easiest way to get JSON similar to what you want, then you can just tweak the values.

Edit: as of Spring16 you can use Test.setCreatedDate(Id,DateTime) to artificially set an SObject's CreatedDate in the database. JSON.deserialize is still handy for other read-only fields like LastModifiedDate though.

0
61
+50

It isn't available now, but I'm reviewing the Winter 13 release notes and came across a new feature that sounds like it might work. I'm assuming this is what Rich alluded to in his comment above. The new feature is called Loading Test Data from Static Resources.

You'd load test data like so:

List<sObject> ls = Test.loadData(Account.sObjectType, 'myResource');

I'm assuming that you can also specify the CreatedDate in the static resources. There might still be a problem when testing triggers as this looks like it just creates the objects in memory and doesn't add them to the database. Perhaps you could isolate the code in a separate class from the trigger and unit test that class with the object in memory.

0
16

Hmm... JSON deserialization, custom settings - all good and correct.

But... it's a test. You want the test code to execute if it detects a case that's older than a certain time. There's almost certainly no harm at all in "cheating" on the detection.

The question is, do you want to detect for every test, or just some tests?

If every test, you do something along the lines of:

//Untested psuedo-code
if(Case.CreatedDate + (Test.IsRunningTest())? 0: testtime < NOW()){
  //execute logic
}

Or some variation of setting the timespan based, at least in part, on the fact that it's a test.

If you want to set it for one particular test, use a static variable at the class level (which is where your "trigger" code should be implemented anyway) that lets you "override" the comparison time for the purposes of the test. This is similar to caleb's forcetest approach, which is also good.

This approach is cheap, efficient, and easy to understand and support over the long term.

As a computer scientist, I love the JSON deserialization approach. But I'd probably shoot any engineer who tried to use it in a real project due to the cost to implement and maintain.

0
13

When I find code that is "untestable" I often times create a flag in the Apex class public Boolean forceTest = false; and then around the code that is "untestable" I do

if(!forceTest) {
//normal code execution
} else {
//force some condition
}

Then in the testMethod you can simply set forceTest to true and force your condition.

MyApexClass helper = new MyApexClass();
helper.forceTest = true;
helper.doStuff();
assert(stuff happened properly);

You may need multiple variables....

Thoughts?

1
  • There's an out-the-box utility for that if you can make do with less control: Test.isRunningTest() - developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/… I'd also add that for best code coverage, it's best to avoid binary if/else with this stuff. I.e. instead of if (Test.isRunningTest) { myRecords = testData } else { myRecords = [SELECT * FROM etc]; }, where possible, run both: myRecords = [SELECT * FROM etc]; if (Test.isRunningTest) { myRecords = testData } Commented Jan 30 at 11:52
8

Here's a thought: Use a custom setting to reference the number of days (or unit of time) that you want to use in your trigger. E.g. 30 for Case.CreatedDate + 30 Days.

In your Unit Test, set that custom setting value to zero, to get your trigger logic to run.

Your trigger code would look something remotely like:

//Very Pseudo-y code
if(Case.CreatedDate + MyCustomSetting__c.getValues('MyDateOffset').Value__c < NOW()){
  //execute logic
}

Then before Test.startTest(), create your mock Case record. When you update the record after Test.startTest(), the if statement above should execute your logic and simulate x days after.

5

Why not use a temp DateTime field into which you copy CreatedDate upon creation - then you have the correct value for real world use but you can also edit it as you wish for test cases.

4

I know this issue has been around for a while, however with the introduction of Winter '16 there is now a permission to write into Audit fields which you could leverage in your tests.

First, enable the write access to audit fields permission by going to Setup -> User Interface and checking "Enable "Set Audit Fields upon Record Creation" and "Update Records with Inactive Owners" User Permissions".

In your tests, create a new permissionset with the appropriate permission and assign it to the running user. I created a System Admin user to create the permission set to avoid Mixed DML errors when setting up test data and Setup Objects (there may be a better way to do this).

        //Create a System Administator User
    Profile p = [SELECT id, Name FROM Profile where name = 'System Administrator' ].get(0);  
            User u = new User(firstname= 'System',
            lastname='Admin',
            Alias='AdminSA2',
            email = '[email protected]',
            username= '[email protected]', 
            profileId= p.id, 
            emailencodingkey='UTF-8',
            languagelocalekey='en_US',
            localesidkey='en_US',
            timezonesidkey='America/Los_Angeles');
            insert u;

    System.runAs(u) {    
    //Create the permission set
    PermissionSet ps = new PermissionSet();
    ps.Name = 'Write_Access_To_Audit_Fields';
    ps.Label = 'Write Access To Audit Fields';

    //Provide write access to audit fields
    ps.PermissionsCreateAuditFields = true;
    insert ps;

    //Assign the permission set to the current user   
    PermissionSetAssignment psa = new PermissionSetAssignment();
    psa.AssigneeId = UserInfo.getUserId();
    psa.PermissionSetId = ps.Id;
    insert psa;
    }

You will now be able to set your created date to System.Now() or earlier (not in the future). I hope this helps.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .