Is it possible to test apex that relies on field history tracking?
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;
}
}
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.
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 theParentId
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
andNewValue
fields will come back asnull
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 callingTest.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();
}
}