51

I have some code that relies on field history tracking by querying the History for a custom object. This works fine, but it appears that the history records aren't created during the test so none of my tests will pass.

1

6 Answers 6

38

I had a suspicion (remember reading somewhere) that Field History Tracking cannot be tested in a Unit Test Class.

A bit of Developer Board surfing threw up this post which confirmed that since none of the data is actually committed during a Unit Test, its not possible to Unit Test Field History Tracking. http://boards.developerforce.com/t5/Visualforce-Development/Unit-Testing-and-History-Table/td-p/142422

The History tables are ReadOnly, coz I'm guessing they're a little bit like the Salesforce System Fields. To maintain the integrity of the audit trail, they're locked to writes. To be able to write our own history records, we'd need to roll our own History Mechanism, with an object and a trigger

To confirm this suspicion, I quickly wrote this test class : (after turning on Field History Tracking on the Type field)

@isTest
public class AccountTest {
@isTest
    private static void  testOne(){
     Account acc = new Account(Name = 'Hello', Type='Other');
     insert acc;
     Test.startTest();
acc.Type = 'Prospect';
update acc;
Test.stopTest();

AccountHistory[] ah =  [Select AccountId, Field, OldValue, NewValue from AccountHistory where AccountId = :acc.Id];
System.debug('ACC HIST IS ' + ah);
System.assertEQuals(1, ah.size());   //ASSERTION FAILS              
    }

}

(As an aside, this also fails when executed from Execute Anonymous or when run on load from a VF Page. It would seem that you can't query for the History from the same execution context. Inserting the account first, and then separately updating the Type seems to yield History from execute anonymous / vf)

To make your tests with work sufficient coverage, you can make use of the Test.isRunningTest to return a mock AccountHistory response as below (although OldValue and NewValue will be null as they are not writeable)

public class AccountHistoryUtil {

@isTest
    private static void  testOne(){
    Account acc = new Account(Name = 'Hello World' + Datetime.now());
    insert acc;

    Test.startTest();

    AccountHistoryUtil accHistClass = new AccountHistoryUtil();

    accHistClass.processAccountUpdate(acc.Id);

    AccountHistory[] accHist = accHistClass.retrieveAccountHistory(acc.Id);
    Test.stopTest();
    System.assertEQuals(1, accHist.size());                
    }


    public void processAccountUpdate(Id accountId){
       Account acc = new Account(Id = accountId, Type = 'Other');
       update acc; 

    }

    public AccountHistory[] retrieveAccountHistory(Id accountId){
     List<AccountHistory> ah;
     if(Test.isRunningTest()){  //if TEST, create dummy AccountHistory
         ah = new List<AccountHistory>{}; //OldValue, NewValue not writeable
         ah.add(new AccountHistory(AccountId=accountId, Field='Type'));
     }
     else 
         ah=  [Select AccountId, Field, OldValue, NewValue from AccountHistory where AccountId = :accountId];


     return ah;
    }
}
3
  • Interesting... Is the field history object writeable? Could we create our own field history records? Nov 3, 2012 at 2:39
  • The History tables are ReadOnly, coz I'm guessing they're a little bit like the Salesforce System Fields. To maintain the integrity of the audit trail, they're locked to writes. To be able to write our own history records, we'd need to roll our own History Mechanism, with an object and a trigger. Nov 3, 2012 at 2:48
  • More information on mocking history records can be found here jamessimone.net/blog/joys-of-apex/mocking-apex-history-records/….
    – dannymorty
    Jan 26 at 14:38
11

You can't populate the history tables from unit tests as this happens after a transaction is committed to the database, which doesn't happen in the test context, rather the entire transaction is rolled back at the end of the test. The history table is only writeable by the system, not by your code, so you can't go that route either.

The one thing that you can do is instantiate the objects that would be stored in the history table if you were lucky enough to be able to write to it, although you can only write to a subset of fields (oldValue and newValue are not writeable for example - you can still access them but they will be null). Thus the way that I've handled this in the past is to put the SOQL query that extracts the data from the history table into its own method, which simply executes the query and returns the list of matching history objects. Then a different method is used to process that list. My unit test can create the list of objects to be processed independently of the SOQL, although I can still execute that (and get an empty result) in order to get coverage.

Here's an example using accounts:

public with sharing class HiistoryExample {
    public List<AccountHistory> queryDatabase(id accountId)
    {
        return [select AccountId, OldValue, NewValue, IsDeleted, Id, Field, CreatedBy.Name 
                from AccountHistory
                where AccountId=:accountId order by createddate desc];
    }

    public List<String> ProcessRows(List<AccountHistory> historyList)
    {
        List<String> results=new List<String>();
        for (AccountHistory hist : historyList)
        {
            String field=hist.field;
            Object oldValue=hist.oldValue;
            Object newValue=hist.newValue;

            results.add(field + '|' + oldValue + '|' + newValue);
        }

        return results;
    }

    public static testMethod void testExample()
    {
        Account acc=new Account(Name='Unit Test');
        insert acc;

        HiistoryExample example=new HiistoryExample();
        example.queryDatabase(acc.id);

        List<AccountHistory> histList=new List<AccountHistory>();
        AccountHistory hist=new AccountHistory(Field='Name');
        histList.add(hist);

        List<String> results=example.processRows(histList);
        System.assertEquals(1, results.size());
        System.assertEquals('Name|null|null', results[0]);
    }
}

and when I execute the test method from eclipse, I get 100% coverage. If you need to test code that relies on real values for old/new, I tend to create a custom class, populate this with the new/old values and process that. Then my unit tests can create their own instances of the custom class for processing.

1
  • Seems we can now write to the history table for custom objects although cannot find documentation that states such.
    – Eric
    Sep 3, 2016 at 17:11
9

Revive a nearly 5-year old question? Sure! Why not?

This recently came up again in a question that I was working on answering, when that question was closed as a duplicate of this one.

In addition to the methods already mentioned, it is possible to use Test.loadData() to create <obj name>__History records (this also works for history objects that don't have the __History suffix like OpportunityFieldHistory). At this time, the salesforce docs go back to API v30.0 (Spring '14), and I see Test.loadData() in the docs for that version. I haven't done a lot of searching/testing to see exactly when this became possible...but if you're reading this, then you should be able to use it.

This requires a csv (comma-separated variable) file to be uploaded as a static resource. As far as I can tell, the only field of history objects that is absolutely required is the ParentId field. The advantage of this approach is that you are able to specify values for OldValue and NewValue.

The following is an example using a custom object from my org, Circuit__c, with a custom field Card__c. The Id prefix for my object is a0R.

ParentId,Field,OldValue,NewValue
a0R000000000000,Card__c,123,456
a0R000000000000,Card__c,456,abc

Uploaded as a static resource named 'TestCircuitHistoryData', you would load it in a test like so

// Pretend that there's a test class definition out here somewhere...
@isTest
static void testLoadHistory(){
    // loadData takes 2 parameters, an SObject type, and the name of a static resource
    //   (as a string)
    List<Circuit__History> cs = Test.loadData(Circuit__History.SObjectType, 'TestCircuitHistoryData');

    // When debugged, you can see that OldValue and NewValue are indeed populated
    system.debug(cs);
    // Rows of the CSV file are created/inserted top-down
    // 08:39:45:923 USER_DEBUG [15]|DEBUG|(Circuit__History:{Id=0170v00000KAxZxAAL, IsDeleted=false, ParentId=a0R000000000000EAA, CreatedById=null, CreatedDate=null, Field=Card__c, OldValue=123, NewValue=456}, Circuit__History:{Id=0170v00000KAxZyAAL, IsDeleted=false, ParentId=a0R000000000000EAA, CreatedById=null, CreatedDate=null, Field=Card__c, OldValue=456, NewValue=abc})
}

There are two big caveats with this approach:

  • You'll probably need to use 'fake' Ids for the ParentId field. You could extract some actual history data from a production org, and put it in a csv file uploaded as a static resource...but that doesn't change the fact that the ParentId you have in your static resource will (practically speaking) never match the Id for records you'll create in a unit test.
  • The records that are loaded are inserted, and available through queries in a test method. However, the OldValue and NewValue fields will come back as null if you try to query them. Test.loadData() can only provide values to those fields in the 'in memory' instances that you get as a result of calling Test.loadData()

That means that if you have logic that depends on the values stored in OldValue and/or NewValue, and you want to test it, the method you're trying to test would need to have its history data injected into it (as opposed to directly querying for it in the method). Data can be injected via static variables, class constructors, method parameters, etc... The important point is that you want to separate the job of fetching the data from the job of operating on it.

// Testing this method using Test.loadData() is not possible because it depends on 
//   OldValue/NewValue, and there is not a way to have those fields be anything other
//   than null (from a query) at time of writing
public void directlyQueryHistory(){
    List<Circuit__History> chList = [SELECT Id, ParentId, Field, OldValue, NewValue FROM Circuit__History WHERE ParentId IN :myCircuits];

    for(Circuit__History ch :chList){
        if(ch.Field == 'Special_Field__c' && ch.OldValue != 'trouble'){
            // Do interesting things here
        }
    }
}

// If we break the above method into two methods (both public, or at least @testVisible),
//   that will allow us to 'inject' test data, which will allow us to test logic that 
//   depends on OldValue or NewValue.
// As is, there isn't much for us to test in this particular method (other than
//   making sure it gets called so it's marked as covered).
public void QueryHistory(){
    List<Circuit__History> chList = [SELECT Id, ParentId, Field, OldValue, NewValue FROM Circuit__History WHERE ParentId IN :myCircuits];
    operateOnHistory(chList);
}

// This method only cares about operating on data.
// It does not fetch (i.e. query) for anything on its own, everything it needs to 
//   run needs to be set up for it by other code prior to running this method.
// Because the data is injected, we can use Test.loadData() in a unit test and have
//   OldValue available.
public void operateOnHistory(List<Circuit__History> history){
    for(Circuit__History ch :history){
        if(ch.Field == 'Special_Field__c' && ch.oldValue != 'trouble'){
            // Do interesting things here
        }
    }
}

An example test class (which makes some additional assumptions)

@isTest
private class HistoryTest{
    static List<Circuit__History> circuitHistory;

    static void setup(){
        // This cannot be done inside a @testSetup method because static variables
        //   set in such a method are erased before each unit test is run
        circuitHistory = Test.loadData(Circuit__History.SObjectType, 'TestCircuitHistoryData');
    }

    @isTest
    static void testLoadedHistory(){
         MyCircuitClass mcc = new MyCircuitClass();

         // Operating on the Circuit History probably results in changes to
         //   those circuits (or changes on objects related to those circuits).
         // Thus, it's probably a good idea to make sure those object records exist
         Map<Id, Circuit__c> circuitsMap = new Map<Id, Circuit__c>();
         for(Circuit__History ch: circuitHistory){
             circuitsMap.put(ch.ParentId, new Circuit__c(Id = ch.ParentId));
         }

         // We'll also likely need to inject these circuits to do anything useful.
         mcc.circuitsMap = circuitsMap;

         test.startTest();
         mcc.operateOnHistory(circuitHistory);
         test.stopTest();
    }
}
4
  • I found that this is the most elegant way to accomplish loading data without creating unnecessary apex overhead. Sep 15, 2017 at 12:03
  • It does not seem to be possible to create test history data using Test.loadData. Reference: salesforce.stackexchange.com/questions/198260/… Nov 13, 2017 at 16:17
  • 2
    @battery.cord Well, I ran my example scenario again, and it does work for my custom object's history object as well as for OpportunityFieldHistory. I tried the same for AccountHistory and am getting Validation Errors While Saving Record(s) with no stack trace. So it would be more accurate to say that this technique works for some but not all History objects. In those cases, combining dependency injection with a wrapper class to abstract out a standard History object would be a way to work around this (good luck testing that wrapper class though).
    – Derek F
    Nov 13, 2017 at 16:57
  • Just tested the same method using a custom object's history data, and was able to get the records to insert (with the NewValue and OldValue fields showing null when queried). Nov 13, 2017 at 18:27
3

It now seems that you can insert EntityHistory objects during testing, although you cannot write the old and new values. I have not found documentation that says you can so maybe it came along by accident with the ability to set system fields etc, who knows.

Documentation on EntityHistory object does not state that it can be created:

https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_entityhistory.htm

However this question here got me to try it myself and I could indeed insert a history object as outlined in the OP answer here:

Test Class Coverage for Custom Object History

Reproducible example

@isTest
private class historyTest{


    public static testmethod void doit(){
        Account a = new account(name='test');
        insert a;

        Opportunity o = New Opportunity(accountid = a.id,closedate=date.today(),name='test',stagename='test');
        insert o;

        [Custom Object] cObj = new [CustomObject](Name='test',Opportunity__c = o.id);
        insert cObj;

        [CustomObject]__history objectHistory = new [CustomObject__history(ParentId = s.Id, Field = 'Hours_Allocated__c');
        insert objectHistory;

        system.debug([Select ParentID From {CustomObject}__History]);

    }

}
3
  • Actually this doesn't seem to work. when I run code @isTest class TestClass { static testMethod void testMetod() { Account a = new Account(Name = 'a'); insert a; insert new AccountHistory(AccountId=a.Id, Field = 'Name'); } } I see internal error [![enter image description here][1]][1] [1]: i.stack.imgur.com/qyttF.gif
    – Patlatus
    Nov 11, 2017 at 16:51
  • According to the comment on the answer above, this seems to be specific to custom objects - but when I try to insert a custom history object record, I get an error saying that "insert is not allowed on CustomObject__History" Nov 13, 2017 at 14:43
  • It worked as of the writing of the answer. Must have been a bug that they fixed and is a good example of why should not implement undocumented features. I have not tried it today as I trust in your tests
    – Eric
    Nov 13, 2017 at 14:45
0

You can also do this:

1) Before you call your target class create a list of strings and populate with the values you would like, here have used create date, time of history row but can be anything. The list should have the values you want to test for.

// create list to substitute when target is running in test mode     
List<List<String>> testOfferHistoryList = new List<List<String>>();
for (integer i=0; i<3; i++) { 
     List<string> tempList = new List<string>();
     tempList.add(string.ValueOf(date.today()-i));  
     tempList.add('15:00');  
     tempList.add(offer.id);  // id of test data row created previously
     testOfferHistoryList.add(tempList);
}   

2) Assign it to a variable in your target class:

VocCollection vocData = new VocCollection(); // instantiate target class vocData.testOfferHistoryList.addall(testOfferHistoryList);

3) In your target class you, after you read the relevant history object you put the results into a list of strings just like the one above. So rather than use the list of yourObject_History objects in subsequent processing, you use the list of strings you have just created (should have exactly the same structure).

4) Lastly test if running in test mode and if so then substitute the test list of string for the real list of strings, ie

 if (Test.isRunningTest()) {
     offerHistoryList.addAll(testOfferHistoryList);   
 }

5) Carry on processing as per normal using substituted list instead

-1

Thanks for the if(Test.isRunningTest()){ idea. Instead of restricting to the account.id, I opened it up and limited the results to 10. This gave me enough code coverage to deploy. I made the change in the Class, so that the test class when running will use the history list without the account.id restriction:

 List<AccountHistory> ahistorylist;
 if(Test.isRunningTest()){  //if TEST, don't limit by a.id
     ahistorylist = [select 
      AccountId, 
      OldValue, 
      NewValue, 
      IsDeleted, 
      Id, 
      Field, 
      CreatedDate, 
      CreatedById,
      CreatedBy.Name
      From AccountHistory
      order by CreatedDate desc
   Limit 10 ];
 } else {
 ahistorylist = [select 
      AccountId, 
      OldValue, 
      NewValue, 
      IsDeleted, 
      Id, 
      Field, 
      CreatedDate, 
      CreatedById,
      CreatedBy.Name
      From AccountHistory
      where AccountId= :a.id
      order by CreatedDate desc
   Limit 100 ]; 
 }
 // Loop through all field history records
 for (AccountHistory fh: ahistorylist ) {

    String toText = '';
    String fromText = '';
    String skipthis = 'no';

You must log in to answer this question.

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