Saturday, 30 May 2015

Intro to Unit Test Data Creation Framework continued.......

Read the first blog at

In my last blog I introduced my Unit Test Data Creation Framework, now we will start building the classes of the framework.

First create the Constants class

public class Constants {

public static final String CONST_Account = 'ACCOUNT';
public static final String CONST_Contact = 'CONTACT';


Now, in the ITestData class add

public interface ITestData {
    List<sObject> returnAnyObject(String jsonStr, KeyValue[] kVals);                                                 

Next, in the TestDataJsonLibrary class add

public class TestDataJsonLibrary {

public static String referenceKey = 'ReferenceID';
public class Standard{

public final Map<String, String> libraryMap = new Map<String, String>{
Constant.CONST_Account                            => '{"attributes":{"type":"Account"},"Field1__c":"Value 1","Field2__c":"Value 2"}',
Constant.CONST_Contact     => '[{"attributes":{"type":"Contact"},"'+referenceKey+'":"Reference Value","Field1__c":"Value 1"}



In the Return Data class add

public abstract class TestDataReturnData implements ITestData{

public Boolean bulkModeOn = false;

public Map<System.Type, String> overrideJson = new Map<System.Type, String>();

    public List<sObject> returnAnyObject(String jsonStr, KeyValue[] kVals){

        List<sObject> sobj;
                                    jsonStr = getFilteredJsonString(jsonStr, kVals);
            sobj = (List<sObject>) System.Json.deserialize(jsonStr, List<sObject>.class);
            sobj = (List<sObject>) System.Json.deserialize('['+jsonStr+']', List<sObject>.class);
        if(kVals != null){
                    for(sObject obj : sobj)
                       obj = UtilDML.setObjData(obj, kVals);
        return sobj;

    private String deserialJson(String jsonStr, KeyValue[] kVals){   
            List<Object> deserialLst = (List<Object>) JSON.deserializeUntyped(jsonStr.unescapeEcmaScript());
            String aReferenceKey;
            //when setting the fields from the KeyValues if 1 is the lookup field set to  aReferenceKey          
            for(KeyValue kv : kVals){                
                        if(kv.key == TestDataJsonLibrary.referenceKey){                           
                                    aReferenceKey = kv.value;
            List<sObject> serialLst = new List<sObject>();
            for(Object obj : deserialLst){
                        Map<String, Object> objMap = (Map<String, Object>) obj;

                        if(aReferenceKey == objMap.get(TestDataJsonLibrary.referenceKey)){                                       
            return JSON.serialize(serialLst);


In the Insert Data class add

public virtual class TestDataInsertData extends TestDataReturnData{

    private sObject insertAnyObject(String jsonStr, KeyValue[] kVals, System.Type objType){
        if(overrideJson != null && overrideJson.containsKey(objType))
            jsonStr = overrideJson.get(objType);
        sObject sobj = super.returnAnyObject(jsonStr, kVals)[0];
        // set to true if inserting multiple records
        if(bulkModeOn == false)
            insert sobj;
        return sobj;

    public Contact insertContact(String jsonstr, KeyValue[] kVals){       
        return (Contact) insertAnyObject((jsonstr != null && jsonstr != '') ? jsonstr : new TestDataFramework_JsonLibrary.Standard().M.get(Constants.CONST_Contact), kVals, Contact.class);


In the Update Data class add

public virtual class TestDataUpdateData extends TestDataInsertData{

    public Account updateAccount(KeyValue[] insertkVals, KeyValue[] updatekVals){      
        Account acc = super.insertAccount(insertkVals);
        if(updatekVals != null)
            acc = (Account) UtilDML.setObjData(acc, updatekVals);
        update acc;
        return acc;


In the ComplexData class add

public virtual class TestDataComplexData extends TestDataInsertData{
    public Account acc{get;set;}
    public Contact cont{get;set;}
    public Opportunity opp{get;set;}

    public Account insertContactAndAccount(Map<System.Type, List<KeyValue>> keyMap){
        //inserts just 1 Account and 1 Contact and links them together, using the map in the argument means you only need to use 1 argument

        //stops a null exception occurring later in the code
        if(keyMap == null){
            keyMap = new Map<System.Type, List<KeyValue>>();
            kMaps.put(Contact.class, new List<KeyValue>());
        }else if(keyMap.containsKey(Contact.class) == false)
            keyMap.put(Contact.class, new List<KeyValue>());
        this.acc = super.insertAccount(keyMap.get(Account.class));

        // now provide the Id into the KeyValues to link the Objects together
        keyMap.get(Contact.class).add(new KeyValue('AccountId',, 'ID'));

        this.cont = super.insertContact(keyMap.get(Contact.class));
        return this.acc;

    public Account insertContactOpportunityAndAccount(Map<System.Type, List<KeyValue>> keyMap){


            if(keyMap.containsKey(Opportunity.class) == false)
                        keyMap.put(Opportunity.class, new List<KeyValue>());

        keyMap.get(Opportunity.class).add(new KeyValue('AccountId',, 'ID'));
        this.opp = super.insertOpportunity(keyMap.get(Opportunity.class));
        return this.acc;


In the BulkData class add

public virtual class TestDataBulkData extends TestDataInsertData{

    public Account insertAccountAndContacts(Map<System.Type, KeyValueBulk> keyMap){
        //This inserts 1 Account and a number of Contacts

        //stops a null exception occurring later in the code
        if(keyMap == null){
            keyMap = new Map<System.Type, KeyValueBulk>();
            keyMap.put(Contact.class, new KeyValueBulk());
        }else if(kMaps.containsKey(Contact.class) == false)
            kMaps.put(Contact.class, new KeyValueBulk());
        this.conts = new List<Case>();
        Account acc = super.insertAccount(keyMap.get(Account.class) .keyValueBulkLst);
        bulkModeOn = true; // stops records being inserted
                    //link records together
                    (keyMap.get(Contact.class)).keyValueBulkLst.add(new KeyValue('AccountId',, 'ID'));
                    List<KeyValueBulk> kVals = (keyMap.get(Contact.class)).keyValueBulkLst;
                    for(Integer i = 0; i < (keyMap.get(Contact.class)).insertRecs; i++)
                    insert this.conts;
        bulkModeOn = false; //resets flag
        return this.acc;

KeyValue class looks like this

public class KeyValue{

            public String key{get; set;}
            public String value{get; set;}
            public String fieldType{get; set;}

            public KeyValue(String key, String value, String fieldType){
                        this.key = key;
                        this.value = value;
                        this.fieldType = fieldType.toUpperCase();

            public KeyValue(String key, String value){
                        this.key = key;
                        this.value = value;

            public KeyValue(){

KeyValueBulk class looks like this

public class KeyValueBulk{

public integer insertRecs;
public KeyValue[] keyValueBulkLst;

public KeyValueBulk(integer insRecs, KeyValue[] kys){
            insertRecs  = (insertRecs != null && insertRecs > 0) ? insertRecs : 1;
            keyValueBulkLst = kys;


Also if you have any triggers, for rapid transactional processing create the TriggerController class

To bypass code in triggers which often fire workflows and process builders, which in turn fire triggers again; all of which takes extra processing time. But when you are simply creating test data you are implicitly telling the system to create an exact data set and if you are not actually testing the trigger there is no need when you are creating the test data to run through the code in the trigger.

In the TriggerController class add

public class TriggerController {

            //used specifically in unit test data framework
            public static Map<System.Type, Boolean> rapidProcessing;

            //Account - Disable / Enable parts of trigger
            public static boolean Account_DisableAllTypes = false;
            public static boolean Account_DisableInsert = false;
            public static boolean Account_DisableUpdate = false;
            public static boolean Account_DisableDelete = false;
            public static boolean Account_DisableUnDelete = false;

            //used to unit test the trigger control to ensure the correct parts of the trigger were triggered
            public static boolean Account_Insert_Succeeded = false;
            public static boolean Account_Update_Succeeded = false;
            public static boolean Account_Delete_Succeeded = false;
            public static boolean Account_UnDelete_Succeeded = false;


In the UtilDML class add

public class UtilDML {

            public static Sobject setObjData(Sobject aobj, KeyValue[] kVals){
                        Sobject thisobj;
        if (kVals != null){
            for (KeyValue eachval : kVals){
                                    thisobj = setFieldVal(aobj, eachval);
                        catch(Exception ex){system.debug('## ex ' + ex); }
        return thisobj;

            public static Sobject setFieldVal(Sobject obj, KeyValue thisKeyVal){
                        system.debug('## thisKeyVal ' + thisKeyVal);
                        if (thisKeyVal.fieldtype == 'DATE'){
                                    String tmpDt = thisKeyVal.value;
                                    Date dt = Date.valueOf(tmpDt.substring(0,9).trim());
                                    system.debug('## dt ' + dt);
                                    obj.put(thisKeyVal.key, dt);
                        }else if (thisKeyVal.fieldtype == 'DATETIME'){
                                    system.debug('## Datetime.valueOf(tmpDt) ' + Datetime.valueOf(thisKeyVal.value));
                                    obj.put(thisKeyVal.key, Datetime.valueOf(thisKeyVal.value));
                                    system.debug('## obj ' + obj);
                        }else if (thisKeyVal.fieldtype == 'DECIMAL')
                                    obj.put(thisKeyVal.key, decimal.valueof(thisKeyVal.value));
                        else if (thisKeyVal.fieldtype == 'INTEGER')
                                    obj.put(thisKeyVal.key, integer.valueof(thisKeyVal.value));
                        else if (thisKeyVal.fieldtype == 'LONG')
                                    obj.put(thisKeyVal.key, long.valueof(thisKeyVal.value));
                        else if (thisKeyVal.fieldtype == 'DOUBLE')
                                    obj.put(thisKeyVal.key, Double.valueof(thisKeyVal.value));
                        else if (thisKeyVal.fieldtype == 'BOOLEAN')
                                    obj.put(thisKeyVal.key, ((thisKeyVal.value).toUpperCase() == 'TRUE') );
                        else if (thisKeyVal.fieldtype == 'BLOB')
                                    obj.put(thisKeyVal.key, Blob.valueof(thisKeyVal.value));
                        else if (thisKeyVal.fieldtype == 'ID'){
                                    obj.put(thisKeyVal.key, ((ID)thisKeyVal.value));
                                    obj.put(thisKeyVal.key, thisKeyVal.value);//String
                        system.debug('## obj ' + obj);
                        return obj;

 public static sObject convertToSobject(Map<String, Object> objMap){

     sObject sObj = Schema.getGlobalDescribe().get((String)((Map<String, Object>)objMap.get('attributes')).get('type')).newSObject();
     Map<String, Schema.SObjectField> sObjMap = sObj.getSObjectType().getDescribe().fields.getMap();

     for(String key : objMap.keySet()){
     Schema.DescribeFieldResult field = sObjMap.get(key).getDescribe();
     String fieldType = field.getType().Name();
     String value = (String) objMap.get(key);
if(fieldType == 'DATE'){ 
sObj.put(key, Date.valueOf(value));
}else if(fieldType == 'DATETIME'){
sObj.put(key, Datetime.valueOf(value));
}else if(fieldType == 'DECIMAL'){
//sObj.put(key, Decimal.valueof(value));
sObj.put(key, Decimal.valueOf(value));
}else if(fieldType == 'INTEGER'){
sObj.put(key, Integer.valueOf(value));
}else if(fieldType == 'LONG'){
sObj.put(key, Long.valueOf(value));
}else if(fieldType == 'DOUBLE'){
sObj.put(key, Double.valueOf(value));
}else if(fieldType == 'BOOLEAN'){
sObj.put(key, (value.toUpperCase() == 'TRUE'));
}else{// String
sObj.put(key, value);
     return sObj;


 In my next blog we will start building some examples of using the framework. If you have any questions about the framework please add comments on my blog

No comments:

Post a Comment

Note: only a member of this blog may post a comment.