Search

Saturday 13 June 2015

Intro to Unit Test Data Creation Framework continued…….



In my last blog http://stevefouracre.blogspot.co.uk/2015/06/intro-to-unit-test-data-creation.html I gave examples of how the framework can be used. I also introduced rapidProcessing. Now we will expand the framework making use of rapidProcessing.

Reminder:
            rapidProcessing allows you to bypass the code in triggers, allowing the test            data to be created much faster ( this can also be useful when you are migrating          data into Salesforce ). In both of these situations you are telling Salesforce         exactly what data to create and you don't want the system to manipulate the          data further or to perform any actions within the triggers that may do all kind    of things such as creating additional business process data like Tasks, Events           and Cases etc, or sending out emails to customers etc ( however the latter wont             happen in unit tests as emails are not sent from unit tests ).

Lets take our previous example which will bypass both the Account and Contact triggers:


KeyValue[] kvsA = new KeyValue[]{};
KeyValue[] kvsC = new KeyValue[]{};

Map<System.Type, KeyValueBulk> keyMap = new Map<System.Type, KeyValueBulk>();
keyMap.put(Account.class, new KeyValueBulk(1, kvsA));
keyMap.put(Contact.class, new KeyValueBulk(5, kvsC));

TriggerController.rapidProcessing = new Map<System.Type, Boolean>{ Account.class => true, Contact.class => true};

//now rapidProcessing has been turned on for both objects the code in the triggers will be bypassed. You will need to build into your triggers the Trigger Control Framework:

TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertAccountAndContacts(keyMap);



Ok for the above example for triggers to be bypassed we need to first create 2 Hierarchical custom settings:

Triggers_Off__c
            This custom settings needs to have the following fields:
           
Field Name
Data Type
value
Boolean


Trigger_Per_Object__c
            This custom settings needs to have the following fields:

Field Name
Data Type
Account
Boolean
Contact
Boolean

            For each additional trigger you create you will need to create an additional             field in this custom setting for that trigger and you will need to add a new else if {} statement to the globalTriggerControlSetting() function in the       TriggerController class. An example of this will be shown next for the        Account and Contact

The 2 custom settings above can be used to bypass the triggers typically when either you are making a deployment to Production or if you are performing a data migration. They will allow you to disable individual or all triggers per person, per Profile or the entire system.


In the TriggerController class we created a number of variables to bypass the Account trigger, now create a similar set of variables for the Contact trigger. We also need to add 2 functions into the class globalTriggerControlSetting() and globalTriggerPerObjectControlSetting():



            //Contact - Only for testing to check if the code ran or not
            public static boolean Contact_Update_Succeeded = false;
            public static boolean Contact_Insert_Succeeded = false;
            public static boolean Contact_Delete_Succeeded = false;
            public static boolean Contact_UnDelete_Succeeded = false;

            //Contact - Disable / Enable parts of trigger
            public static boolean Contact_DisableAllTypes = false;
            public static boolean Contact_DisableInsert = false;
            public static boolean Contact_DisableUpdate = false;
            public static boolean Contact_DisableDelete = false;
            public static boolean Contact_DisableUnDelete = false;

public static boolean globalTriggerControlSetting(){
            return (((Triggers_Off__c.getOrgDefaults() != null) ? Triggers_Off__c.getOrgDefaults().value__c : false) || Triggers_Off__c.getInstance(UserInfo.getUserId()).value__c  || Triggers_Off__c.getInstance(UserInfo.getProfileId()).value__c) ;
}

public static boolean globalTriggerPerObjectControlSetting(String obj){
Trigger_Per_Object__c.getInstance(UserInfo.getProfileId()));
            if (obj == 'Account__c') return (((Trigger_Per_Object__c.getOrgDefaults() != null) ? (boolean)Trigger_Per_Object__c.getOrgDefaults().Account__c  : false) || (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getUserId()).Account__c || (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getProfileId()).Account__c) ;
            else if (obj == 'Contact__c') return (((Trigger_Per_Object__c.getOrgDefaults() != null) ? (boolean)Trigger_Per_Object__c.getOrgDefaults().Contact__c  : false) || (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getUserId()).Contact__c || (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getProfileId()).Contact__c) ;
            else return false;
}



The Disable variables eg: Contact_DisableAllTypes allows you to disable all of a trigger or individual parts of a trigger, commonly will be used within the body of the code including unit tests. We could have used the custom settings but that would involve using DMLs to turn the triggers on and off.



In the next blog we will create the code for the trigger.

Friday 5 June 2015

Intro to Unit Test Data Creation Framework continued…….





In my last blog I laid out the codebase required for the framework.

Now you have created the structure of the framework, lets run some examples.
But before we do I'd like to point out 1 thing, in the framework you have various options depending how you personally feel you would like to structure the framework. You can make the TestDataComplexData class extend the TestDataBulkData class instead of the TestDataInsertData and you may want to rename the TestDataComplexData class to something like TestDataCreation class and so it is more generic. These are just options for you to play around with.


To perform no DML and just return a new Contact using the standard json string you can add a returnContact function in the TestDataReturnData class:


The new function will look like:

    public Contact returnContact(KeyValue[] kVals){
            
        return (Contact) (super.returnAnyObject(new TestDataFramework_JsonLibrary.Standard().M.get('CONTACT'), kVals, Contact.class)[0]);
    }



An example of using this new function:


TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.returnContact(null);



To simply create a new Contact using the standard json string:


TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertContact(null, null);



To add some values into certain fields:


KeyValue[] kvs = new KeyValue[]{};
//this will overwrite the Email field with a new email
kvs.add(new KeyValue('Email', 'myemail@yahoo.com', 'String'));

TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertContact(null, kvs);



To simply create a new Account and Contact using the standard json string and to add a value into a field:

Map<System.Type, List<KeyValue>> keyMap = new Map<System.Type, List<KeyValue>>();
KeyValue[] kvs = new KeyValue[]{};
//this will overwrite the Email field with a new email
kvs.add(new KeyValue('Email', 'myemail@yahoo.com', 'String'));

kMaps.put(Contact.class, kvs);

TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertContactAndAccount(kMaps);



To create 1 Account and 5 Contacts linked using the standard json string#


KeyValue[] kvsA = new KeyValue[]{};
KeyValue[] kvsC = new KeyValue[]{};

Map<System.Type, KeyValueBulk> keyMap = new Map<System.Type, KeyValueBulk>();
keyMap.put(Account.class, new KeyValueBulk(1, kvsA));
keyMap.put(Contact.class, new KeyValueBulk(5, kvsC));

TestDataBulkData dataCl = new TestDataBulkData ();
dataCl.insertAccountAndContacts(keyMap);



If your framework is setup so that TestDataComplexData class extends the TestDataBulkData class instead just change the code above to


KeyValue[] kvsA = new KeyValue[]{};
KeyValue[] kvsC = new KeyValue[]{};

Map<System.Type, KeyValueBulk> keyMap = new Map<System.Type, KeyValueBulk>();
keyMap.put(Account.class, new KeyValueBulk(1, kvsA));
keyMap.put(Contact.class, new KeyValueBulk(5, kvsC));

TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertAccountAndContacts(keyMap);



If you now want to bypass the triggers to speed up processing time because you want to upload a lot of records to be used in your testmethod:


KeyValue[] kvsA = new KeyValue[]{};
KeyValue[] kvsC = new KeyValue[]{};

Map<System.Type, KeyValueBulk> keyMap = new Map<System.Type, KeyValueBulk>();
keyMap.put(Account.class, new KeyValueBulk(1, kvsA));
keyMap.put(Contact.class, new KeyValueBulk(5, kvsC));

TriggerController.rapidProcessing = new Map<System.Type, Boolean>{ Account.class => true, Contact.class => true};

//now rapidProcessing has been turned on for both objects the code in the triggers will be bypassed. You will need to build into your triggers the Trigger Control Framework which will be the next thing I will cover in my next post:

TestDataComplexData dataCl = new TestDataComplexData ();
dataCl.insertAccountAndContacts(keyMap);



If you want to insert an Account using the standard json string and then update that Account:


TestDataUpdateData dataCl = new TestDataUpdateData();
KeyValue[] kvsUpdate = new KeyValue[]{};
//this will overwrite the BillingPostalCode field with a new post code
kvsUpdate.add(new KeyValue('BillingPostalCode', 'EC1 2CV', 'String'));

//first argument is null because this overrides the fields for the insert part
dataCl.updateAccount(null, kvsUpdate);


If you want to insert an Account using the standard json string and then update that Account:


TestDataUpdateData dataCl = new TestDataUpdateData();

KeyValue[] kvsInsert = new KeyValue[]{};
//this will overwrite the BillingPostalCode field with a new post code
kvsInsert.add(new KeyValue('BillingPostalCode', 'SE1 2SG', 'String'));

KeyValue[] kvsUpdate = new KeyValue[]{};
//this will overwrite the BillingPostalCode field with a new post code
kvsUpdate.add(new KeyValue('BillingPostalCode', 'EC1 2CV', 'String'));

dataCl.updateAccount(kvsInsert, kvsUpdate);



If you want to create a new insert function and add this to the framework, here are the steps you will need to follow:


  1. Create a new constant in the Constant class
  2. Add a new json string to the libraryMap variable in the json library class
  3. Create a new insert function in the TestDataInsertData class similar to the insertContact() function just replacing with the new Sobject type
  4. You can now use this function in the other classes TestDataComplexData, TestDataBulkData and TestDataUpdateData to build more complex data structures if you require