Showing posts with label Test class. Show all posts
Showing posts with label Test class. Show all posts

Friday, 13 December 2013

Salesforce Comparable Interface Using It to Sort Wrapper Class

Salesforce have an Interface named as "Comparable" , it is used to add List sorting support for your Apex class

To know more about the Comparable click on this link Comparable Interface

So I used it to compare created date of my history Account records , I have a picklist name Grade__c on Account, whenever user change it , saleforce created a history record for that change.

Note : You need to activate the history tracking for Account and for that field too, for which you want history tracking.

So now am going to tell you how my code works,

Whenever user Updates OR change value of Grade on Account object it will be added on the report chart of history

Here is the snapshot of that report chart


For that I have created a main class and as well as a wrapper class

Wrapper Class
 /**  
 * Description : Controller class for page of Account History of Grade  
 *  
 * Created Date : 12/10/2013  
 *  
 * Created By : Abhi Tripathi  
 *  
 * Version : V1.0   
 **/  
 global class AccountHistoryWrapper implements Comparable{  
   public Account account {get; set;}  
   public List<AccountHistory> accountHistories {get; set;}  
   //Calling Constructor  
   global AccountHistoryWrapper(Account account, List<AccountHistory> accountHistories) {  
     this.account = account;  
     this.accountHistories = accountHistories;   
   }  
   // Compare opportunities based on the opportunity amount.  
   global Integer compareTo(Object compareTo) {  
     // Cast argument to AccountHistoryWrapper  
     AccountHistoryWrapper aHW = (AccountHistoryWrapper)compareTo;  
     // The return value of 0 indicates that both elements are equal.  
     Integer returnValue = 0;  
     if ( aHW.account.CreatedDate > aHW.account.CreatedDate) {   
       // Set return value to a positive value.  
       returnValue = 1;  
     } else if ( aHW.account.CreatedDate < aHW.account.CreatedDate) {  
       // Set return value to a negative value.   
       returnValue = -1;   
     }  
     return returnValue;   
   }  
 }  

Here is the Controller Class

 /**   
 * Description : Controller class for page of Account History of Grade   
 *   
 * Created Date : 12/10/2013   
 *   
 * Created By : Abhi Tripathi   
 *   
 * Version : V1.0    
 **/   
 public class AccountHistoryController {   
   public List<AccountHistoryWrapper> accountHistoriesWrapList {get; set;}   
   //Calling cosnturctor   
   public AccountHistoryController() {   
     //Memory Allocation   
     accountHistoriesWrapList = new List<AccountHistoryWrapper>();   
     //Loop through Accounts   
     for(Account acc : [Select Id, Name, CreatedDate, (Select ID, Field, AccountId, CreatedDate, Createdby.Name, Oldvalue,    
                              Newvalue, Account.Name From Account.Histories   
                              Where Field = 'Grade__c' ORDER BY CreatedDate DESC LIMIT 200 )   
               FROM Account ORDER BY LastModifiedDate DESC]) {    
                 //Populate wrapper list with values   
                 if(acc.Histories.size() > 0)   
                   accountHistoriesWrapList.add(new AccountHistoryWrapper(acc, acc.Histories));    
               }   
     //Get List of wrapper and sort it   
     accountHistoriesWrapList.sort();    
   }   
 }   

Here is the Page

 <!--   
 /**  
 * Description : Custom VF page to add it on store page layout as an inline VF page.  
 *  
 * Created By : Abhi Tripathi  
 *  
 * Created Date : 12/10/2013  
 *  
 **/  
 -->  
 <apex:page controller="AccountHistoryController">  
   <apex:form >  
     <apex:pageBlock >  
       <table cellspacing="0" border="0" class="detailList list" cellpadding="0">  
         <thead class="rich-table-thead">  
           <tr class="headerRow">  
             <th class="headerRow" scope="col">Activity</th>  
             <th class="headerRow" scope="col">Date</th>  
             <th class="headerRow" scope="col">Change By</th>  
           </tr>  
         </thead>  
         <tbody>  
           <apex:repeat value="{!accountHistoriesWrapList}" var="accountHistoryWrap">  
             <tr class="dataRow even first" onmouseover="if (window.hiOn){hiOn(this);} " onmouseout="if (window.hiOff){hiOff(this);} " onblur="if (window.hiOff){hiOff(this);}" onfocus="if (window.hiOn){hiOn(this);}">  
               <td colspan="3" style="text-align:center"><B><font size="3"><em><apex:outputField value="{!accountHistoryWrap.account.Name}"/></em></font></B></td>  
             </tr>  
             <apex:repeat value="{!accountHistoryWrap.accountHistories}" var="aHW">  
               <tr class="dataRow even first" onmouseover="if (window.hiOn){hiOn(this);} " onmouseout="if (window.hiOff){hiOff(this);} " onblur="if (window.hiOff){hiOff(this);}" onfocus="if (window.hiOn){hiOn(this);}">  
                 <td class="dataCell">  
                   Changed Grade from <b>"{!aHW.oldvalue}"</b> to <b>"{!aHW.newvalue}"</b>   
                 </td>  
                 <td class="dataCell">  
                   <apex:outputField value="{!aHW.createddate}"/>   
                 </td>  
                 <td class="dataCell">   
                   <apex:outputField value="{!aHW.createdby.Name}"/>   
                 </td>  
               </tr>   
             </apex:repeat>  
           </apex:repeat>  
         </tbody>  
       </table>  
     </apex:pageBlock>  
   </apex:form>  
 </apex:page>  

Test class

 @isTest(seeAllData=false)  
 private class Test_AccountHistoryController {  
   //Test method  
   static testMethod void myUnitTest() {  
     //List of Account  
     List<Account> accountList = new List<Account>();  
     //Insert Account  
     Account account = new Account(Name = 'test', Grade__c = 'A');  
     insert account;  
     //Insert another account  
     Account account2 = new Account(Name = 'ATest', Grade__c = 'C');  
     insert account2;  
     //Update Account  
     account.Grade__c = 'B';  
     accountList.add(account);  
     //Update Account  
     account2.Grade__c = 'D';  
     accountList.add(account2);  
     //Update account  
     update accountList;  
     //Account History  
     List<AccountHistory> accHistory = [Select Id From AccountHistory Where AccountId =: accountList];  
     //Test starts here  
     Test.startTest();  
     //Calling wrapper class  
     AccountHistoryWrapper wrapper = new AccountHistoryWrapper(account, accHistory);   
     //Controller  
     AccountHistoryController controller = new AccountHistoryController();  
     //Test stops here  
     Test.stopTest();  
     //Assert  
     System.assertEquals(true, Controller.accountHistoriesWrapList != null);  
   }  
 }  


Happy Coding
CHEERS........!!!!!!!

Wednesday, 27 November 2013

Salesforce Account Record Merging With Deleting the Duplicate Records

Whenever we are working with bulk data, its a hard thing to check the duplicate records and then delete them without loosing data.

So I thought why shouldn't we go for a bulk process that helps us in Merging the duplicate records and them clearing them from database without any loss of data.

So here I have done something for my Account object, I have written a batch, whenever we execute it, it will start processing on Account records created in "LAST 15 MINUTES", takes those Account's Name and Billing Address, and then it will check in database that is there any record having same Name AND Billing Address or not.

This batch won't work if there is any slight change in between name or billing address.

for example I have created two Account records having same Name and Billing Address only the first record Phone was an extra field filled.


1st Account created



2nd Account without Phone but same Billing Address and Name




Now when I execute my batch in console




Now my batch deletes duplicate Account that have empty phone


Now the best thing is, no matter how many field you have in Account it will save your field values in the unique record, that means your data will never loose your data.

Here is the batch


/**
* Description : Batch class to merge duplicate transaction Account records.
*
* Created Date : 11-26-2013 
* 
* Revision Logs : V1.0 - Created
*
**/
global class Batch_MergeDuplicateTransactionAccounts implements Database.Batchable<sObject>, Database.Stateful {
    
    //Set to hold the account records name with billing address field values
    global Set<String> setAccountNameWithBillingAddress;
    
    //String to hold the Account object fields names
    String accountFieldsNamesString = '';
    
    //Set to hold the Account fields API name strings
    Set<String> setAccountFieldsNameString;
    
    //Calling Constructor 
    global Batch_MergeDuplicateTransactionAccounts() {
        
        //Memory Allocation to collections
        setAccountFieldsNameString = new Set<String>();
        setAccountNameWithBillingAddress = new Set<String>();
        
        //Describe Account object and get all the fields
        Map<String, Schema.SObjectField> accountFieldsMap = Account.sObjectType.getDescribe().fields.getMap();
        
        //Loop through Account fields Names through Schema Methods
        for(String fieldName : accountFieldsMap.keySet()) {
            
            //Describe field
            Schema.DescribeFieldResult field = accountFieldsMap.get(fieldName).getDescribe();
            
            //Filtering out the fields for getting only updatable non system fields
            if(!field.isCalculated() && field.isCreateable() && field.isUpdateable()
               && !field.getLocalName().equalsIgnoreCase(Constants.PARENTID)) {
                   
                   //Populate set with the Fields Names string values
                   setAccountFieldsNameString.add(field.getLocalName()); 
                   
                   //Account object fields name string
                   if(accountFieldsNamesString == '')
                       accountFieldsNamesString = field.getLocalName(); 
                   else
                       accountFieldsNamesString += ',' + field.getLocalName();
               }
        }
    }
    
    //Start method
    global Database.QueryLocator start(Database.BatchableContext BC) {
        
        //Varibale to hold the current date time value
        DateTime currentDateTime = DateTime.now();
        
        //Varibale to hold the 15 minute ago date time value
        DateTime fifteenMinuteAgoDateTime = DateTime.now().addMinutes(-15);
        
        //String variable to hold the Account records those were created in last 15 minute and so.
        String sOQLQuery = 'SELECT ID, Name FROM Account WHERE'
            + ' CreatedDate >: fifteenMinuteAgoDateTime AND CreatedDate <=: currentDateTime'
            + ' AND Name != null ORDER By CreatedDate ASC';
        
        //Fetching all the Account records from the database
        return Database.getQueryLocator(sOQLQuery);
    }
    
    //Exectue Method having logic for duplicate accounts finding's
    global void execute(Database.BatchableContext BC, List<Account> scope) {
        
        //Set to hold the account name with billing address strings
        Set<String> setAccountNameWithBillingAddressStrings = new Set<String>();
        
        //Map to hold the Account records corresponding the account name and billing address value as key
        Map<String, List<Account>> mapNameBillingAddressKeyWithAccounts = new Map<String, List<Account>>();
        
        //This map is to hold the Parent Id and List of List of Accounts. One list will have maximum 2 child records.
        //Standard merge statement allows to merge 3 records at a time, so List of list will hold list of 2 child records
        Map<Id, List<List<Id>>> mapParentAccountWithListOfChildrenAccounts = new Map<Id, List<List<Id>>>();
        
        //This mapis to hold the updated Parent data always
        Map<Id, Account> mapParentAccount = new Map<Id, Account>();
        
        //Loop through account records in scope
        for(Account acc : scope) {
            
            //Checking value in set
            if(!(setAccountNameWithBillingAddress.contains(acc.Name.trim().toLowerCase()))) {
                
                //Populate set with values
                setAccountNameWithBillingAddress.add(acc.Name.trim().toLowerCase());
                setAccountNameWithBillingAddressStrings.add(acc.Name.trim().toLowerCase());
            }
        }
        
        //Check set for size
        if(setAccountNameWithBillingAddressStrings != null) {
            
            //Loop through eligible account records
            for(Account account : Database.query('SELECT ' + accountFieldsNamesString + ' FROM Account WHERE Name IN : setAccountNameWithBillingAddressStrings AND Name != null ORDER By CreatedDate ASC')) {
                
                //Key String
                String keyString = '';
                String billingAddressString = '';
                
                //Appending account billing address field values in key string after performing validation on them
                if(account.BillingStreet != null)
                    billingAddressString += account.BillingStreet.trim().toLowerCase(); 
                else
                    billingAddressString += null;
                if(account.BillingCity != null)
                    billingAddressString += account.BillingCity.trim().toLowerCase(); 
                else
                    billingAddressString += null;
                if(account.BillingState != null)
                    billingAddressString += account.BillingState.trim().toLowerCase(); 
                else
                    billingAddressString += null;
                if(account.BillingCountry != null)
                    billingAddressString += account.BillingCountry.trim().toLowerCase(); 
                else
                    billingAddressString += null;
                if(account.BillingPostalCode != null)
                    billingAddressString += account.BillingPostalCode.trim().toLowerCase(); 
                else
                    billingAddressString += null;
                
                //Formation of key string with the help of account name and billing address string
                keyString = account.Name.trim().toLowerCase() + Constants.SEPERATOR + billingAddressString;
                System.debug('@@@@@ keyString ' + keyString);
                
                //Check for key value in map
                if(mapNameBillingAddressKeyWithAccounts.containsKey(keyString)) {
                    
                    //Get the Values of the Map and add Id to it.
                    mapNameBillingAddressKeyWithAccounts.get(keyString).add(account);
                    
                } else {
                    
                    //Creat a new Set at values and add Id to it.
                    mapNameBillingAddressKeyWithAccounts.put(keyString, new List<Account>{account}); 
                }
                
                System.debug('@@@@@ mapNameBillingAddressKeyWithAccounts ' + mapNameBillingAddressKeyWithAccounts);
                
                //Loop through map keys
                for(String key : mapNameBillingAddressKeyWithAccounts.keySet()) {
                    
                    //Checking if we have more than one account record in the list corresponding to the account name, billingaddress combined string key
                    if(mapNameBillingAddressKeyWithAccounts.get(key) != null && mapNameBillingAddressKeyWithAccounts.get(key).size() >= 1) {
                        
                        //Account record having oldest created date stamped on it will become parent of other dup recods
                        Account parentAccount = mapNameBillingAddressKeyWithAccounts.get(key)[0];
                        
                        //Set Parent in Map with latest Values
                        mapParentAccount.put(parentAccount.Id, parentAccount);
                        
                        //Add a default list
                        List<List<Id>> lstOfLst = new List<List<Id>>();
                        lstOfLst.add(new List<Id>());
                        mapParentAccountWithListOfChildrenAccounts.put(parentAccount.Id, lstOfLst);
                        
                        //Lopp through the child records
                        //Set all the null field in Parent with child data if child have not null value
                        for(Integer i=1; i<mapNameBillingAddressKeyWithAccounts.get(key).size(); i++) {
                            
                            //Dup Child Account record
                            Account childAccount = mapNameBillingAddressKeyWithAccounts.get(key)[i];
                            
                            //Loop through set having Account object fields API Name with it
                            for(String accountFieldAPIName : setAccountFieldsNameString) {
                                
                                //Checking for value in child with respect to Parent
                                if(parentAccount.get(accountFieldAPIName) == null && childAccount.get(accountFieldAPIName) != null) {
                                    
                                    //Populating Instance with value
                                    parentAccount.put(accountFieldAPIName, childAccount.get(accountFieldAPIName)); 
                                }
                            }
                            
                            //Put the latest innstance of Parent Account in Map
                            mapParentAccount.put(parentAccount.Id, parentAccount);
                            
                            //Get List from Marging Map
                            List<List<Id>> mergingAccounts = mapParentAccountWithListOfChildrenAccounts.get(parentAccount.Id);
                            
                            //Chekcif list size has been reached to 2, add a new List and add account in that
                            if(mergingAccounts[mergingAccounts.size() - 1].size() == 2) {
                                
                                //Add a new List
                                mergingAccounts.add(new List<Id>()); 
                            }
                            
                            //Add Child record in List
                            mergingAccounts[mergingAccounts.size() - 1].add(childAccount.Id);
                            
                            //Put this list back in original map
                            mapParentAccountWithListOfChildrenAccounts.put(parentAccount.Id, mergingAccounts);
                        }
                    }
                }
            }
            
            System.debug('@@@@@@ value in mapParentAccount ' + mapParentAccount);
            System.debug('@@@@@@ value in mapParentAccountWithListOfChildrenAccounts ' + mapParentAccountWithListOfChildrenAccounts);
            
            //Start Merging Process
            for(Account pAccount : mapParentAccount.values()) {
                
                //Get merging list and start merging process
                if(mapParentAccountWithListOfChildrenAccounts.containsKey(pAccount.Id)) {
                    
                    //Loop through the merging list
                    for(List<Id> accounts : mapParentAccountWithListOfChildrenAccounts.get(pAccount.Id)) {
                        
                        if(accounts != null && accounts.size() > 0) {
                            System.debug('###### accounts ' + accounts);
                            //Merge statement for merging of the child records with respect to Parent Account record
                            merge pAccount accounts;
                        }
                    } 
                }
            }
        }
    }
    
    //Finish Method
    global void finish(Database.BatchableContext BC) {
        
    }
}


Here is the Test Class

/**
* Description : Test Class for Batch_MergeDuplicateTransactionAccounts.
*
* Created Date : 11-27-2013
*
* Revisiion Logs : V_1.0 - Created
*
* Code Coverage : 100%
**/
@isTest
private class Test_Batch_MergeDuplicateTxnAccounts {
    
    //Test method
    static testMethod void myUnitTest() {
        
        //List to hold account records
        List listAccounts = new List();
        
        //Create Account with iteration of count
        for(integer i = 1 ; i <= 100 ; i++) { //Populating the list of Account records listAccounts.add(new Account(Name = 'Test1' , BillingCity = 'TestCity' , BillingState = 'TestState' , BillingPostalCode = '85004' , BillingStreet = 'TestStreet' , BillingCountry = 'US')); } listAccounts.add(new Account(Name = 'Test2' , BillingCity = 'TestCity' , BillingState = 'TestState' , BillingPostalCode = '85005' , BillingStreet = 'TestStreet' , BillingCountry = 'US')); listAccounts.add(new Account(Name = 'Test2'));                  //Insert accounts insert listAccounts; //List to hold contact records List listContacts = new List();
            
            //Populate the list with contact records
            listContacts.add(new Contact(FirstName = 'Test' , LastName = 'Contact' , AccountId = listAccounts[1].Id));
            listContacts.add(new Contact(FirstName = 'Test1' , LastName = 'Contact1' , AccountId = listAccounts[2].Id));
            
            //Insert contacts
            insert listContacts;
            
            //Test start from here
            Test.startTest();
            
            //Batch Initializing
            Batch_MergeDuplicateTransactionAccounts controller = new Batch_MergeDuplicateTransactionAccounts();
            
            //Execute Batch
            Database.executeBatch(controller , 200);
            
            //Test stop here
            Test.stopTest();
            
            //Query to get account records
            listAccounts = [SELECT ID , (SELECT ID From Contacts) FROM Account];
            
            //Assert for results
            System.assertEquals(listAccounts.size() , 3);
            System.assertEquals(listAccounts[0].contacts.size() , 2);
        }
    }   
}


Thanks & Cheers,
Hope helped someone.


Monday, 21 October 2013

360° With Test Class for Beginners #Salesforce

Here is have provided some of the basics to write perfect test class in salesforce



Here I'll explain with short snippets for the test classes
As the first one below

1:  @isTest(seeAllData=false)  
2:    private class Test_CountContactOfAccount {   
3:      //Method  
4:      static testMethod void CountContactOfAccountUnitTest() {  
5:      }  
6:    }  

(seeAllData=false)
You can set it true or false on your own, there are two condition for setting it true or false

1. If you are querying any object and fetching records from the database in the test class then set it to true as well there are few object we can't create in our test like "OpportunityLineItem" so to test with this object records, we need to query it with "seeAllData = true".

2. But if you are inserting test records of an object an then querying it in the test class, then you need to set it false otherwise test class won't be executed.


TESTING CUSTOM CONTROLLER AND STANDARD CONTROLLERr
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

How to cover custom controller in the test class?
If you need to cover constructor in the test class then simply define it in the test class, like CountContactOfAccount is the name of the class, just define it in the test class as in below, it will cover you whole constructor.

1:  //Calling contructor  
2:  CountContactOfAccount controller = new CountContactOfAccount();  

How to call StandardController (ApexPages.StandardController stdController)?
If you are using standard controller in the class then you need to define it in the test class too

1:  //Insert you object that you are using in your class  
2:  Account acc = new Account(Name = 'test');  
3:  insert acc;  
4:  //Define standard controller and pass inserted object   
5:  ApexPages.StandardController stdController = new ApexPages.StandardController stdController(acc);  
6:  //Now define your controller then pass standard controller you've just defined  
7:  CountContactOfAccount controller = new CountContactOfAccount(stdController );  

TESTING WITH STATIC AND VOID METHODS
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

How to call methods in the test class?
As you have seen above we have called controller , by using controller we can call static and void methods easily

So if the method is void like "public void something() {"

1:    //Calling contructor  
2:    CountContactOfAccount controller = new CountContactOfAccount();  
3:    //Calling void method  
4:    controller.something();  

But if the method is static like "public static list something() {"

then you can directly call the method no need to use deifned controller

1:    //Calling static method  
2:    CountContactOfAccount.something(listOfAccount);   

TESTING ApexPages.currentPage().getParameters().get('ID')
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

In this scenario you just need to put the value in the test of the variable that you fetching from the the URL

1:  //this method will put Id in the URL in the test class  
2:  currentPageReference().getParameters().put('id', '001c384348247811');  

ASSERTS (Most Important thing for test classes)

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

There as three most commonly used asserts are there shown below, use asserts to check that your method are returning the correct values or not.

1:  System.assert(pBoolean)  
2:  System.assertEquals(pANY, pANY)  
3:  System.assertNotEquals(pANY, pANY)  

TESTING ERROR MESSAGES
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

If you have error message in your test class and you want to cover it in test class then use this snippet

1:  //List of Message  
2:  List msgs = ApexPages.getMessages();  
3:  //Define a boolean variable  
4:  boolean msg = false;  
5:  //Loop through the messages of the page  
6:  for(Apexpages.Message msg:msgs){  
7:  //if there is error message then set the boolean to true  
8:  if (msg.getDetail().contains(‘Search requires more characters’)) msg = true;  
9:  }  
10:  //Assert  
11:  system.assert(msg);  
12:  //Single Assert on Error Messages  
13:  System.assert(ApexPages.getMessages()[0].getDetail().contains( 'User not found with given username' ));  

I think these are the most common scenarios for the Test classes that makes some mess.

Some key points:

1. Whole orgs average code coverage should be 75%, NOT each class.
2. Each trigger must more then 0% code coverage.

Hope I've helped you
CHEERS....!!!!