Apex Test Class Best Practices

In this post we will talk about Apex Test class best practices in Salesforce with examples. Please check our Deep Drive in Test classes session recording here. If you are new to Test classes. Please check this post to learn about test classes in Salesforce with examples.

What to Test in Apex?

Unit tests are the test performed by the developers to ensure that functionality is working as expected considering Both positive & Negative tests. We should not focus on the percentage of code coverage. Salesforce recommends the following components need to tested.

  1. Single Records: This includes testing to verify that a single record produces the correct, expected result
  2. Bulk Records: Any apex code, whether a triggers, a class or on extension may be used for 1 to 200 records we must test not only the single record case, but the bulk cases as well.
  3. Positive scenarios: This type of component testing expect a system to save a record without error.
  4. Negative scenarios: This type of component testing expect a system to give error.
  5. Restricted User: Test whether a user with restricted access to the objects used in code sees the expected behavior, i.e whether they can run the code or receive error messages.

Best Practices of Test Classes in Apex

To write an effective test cases, a developer needs to ensure to include the below points into the test class

  • Test class must start with @isTest annotation.
  • Focus 90+ : To deploy to production at least 75% code coverage is required. But always try to focus on 90%+. We should not focus on the percentage of code coverage, We should make sure that every use case should covered including positive, negative,bulk and single record.
  • One Assert Statement per method : Always put assert statements for negative and positive tests.
    • System.assert(condition, msg)
    • System.assertEquals(expected, actual, msg)
    • System.assertNotEquals(expected, actual, msg)
  • @testSetup to create test records once in a method and use in every test method in the test class.
  • Create TestFactory class with @isTest annotation to exclude from organization code size limit.
  • No SeeAllData=true : Always use ( seeAllData = false ) at class or method level. User, profile, organization, AsyncApexjob, Corntrigger, RecordType, ApexClass, ApexComponent, ApexPage we can access without (seeAllData=true) .
  • As apex runs in system mode so the permission and record sharing are not taken into account . So we need to use System.runAs to enforce record sharing.
  • Avoid Using Hard Coding Ids anywhere in test Class or any apex class
  • Governor limits : Using Test.startTest()gives developers a new set of governor limits for the act stage of their test. Test.stopTest() then allows them to return to their previous governor limits. Test classes must test for Governor Limits using Limits Class
  • Testing Exception : Any exceptions that are caught in the production methods should be tested by feeding the test data that throws exception. Exception Type and error message should be asserted
  • Exercise bulk trigger functionality – use at least 200 records in your tests

How to Create Test Data

Let see how we can create the test data for test classes in Salesforce.

1) Manually create it for each test

To create a test method we can use testMethod keyword or @isTest annotation

  • Pro: Variables in context
  • Con: Lots of duplicate code

2) Load data via CSV Static Resource

We can use Test.loadData method to create test data in our test class.

  • Pro: Don’t need to make code updates for record creation
  • Con: Might be harder to track changes to static resources
@isTest
private class loadDataFromStaticResources {
    @isTest
    static void testLoadData() {
		// Load the test accounts from the static resource
		List<sObject> accList = Test.loadData(Account.sObjectType, 'MyTestData');
		System.assertEquals(accList.size() , 5);
		// Add your Test logic here'
    } 
}

3) Test Setup (@TestSetup)

We can create one createTestData method with @TestSetup annotation

  • Pro: Reduce repetitive code
  • Con: Variables out of context
@TestSetup
static void createTestData() {
    Account acc = TestDataFactory.createAccount(true);
    Contact cont = TestDataFactory.createContact(acc.id, true);
    List<Opportunity>oppList = TestDataFactory.createOpportunity(acc.id, 10); 
}

4) Test Factory

Here is example of test data factory in Salesforce.

  • Pro: Single location to update schema in code
  • Con: Variables out of context
@isTest
public with sharing class TestDataFactory{

    /** 
    * ********************************************************
    * This method is test data for create Lead
    * ********************************************************
    */

    public static Lead createLead(Boolean doInsert){
        Lead newLead = new Lead() ;
        newLead.FirstName = 'Cole';
        newLead.LastName = 'Swain';
        newLead.Company = 'BlueWave';
        newLead.Status = 'contacted';
        if(doInsert){
            insert newLead;
        }
        return newLead;
    }

    public static Void convertLead(Lead newLead ){
        database.leadConvert lc = new database.leadConvert();
        lc.setLeadId(newLead.id);
        leadStatus convertStatus = [SELECT Id, MasterLabel FROM LeadStatus WHERE IsConverted=true LIMIT 1];
        lc.setConvertedStatus(convertStatus.MasterLabel);
        
        Database.LeadConvertResult lcr = Database.convertLead(lc);
        System.assert(lcr.isSuccess());
        lc.setOpportunityName('Cole Swain');
    }
        
    /** 
    * ******************************************************
    * This method is test data for create Account
    * ******************************************************
    */
    
    public static Account createAccount(Boolean doInsert){
        Account acc = new Account();
        acc.Name = 'Test Account';
        if(doInsert){
            insert acc;
        }
        return acc;
    }
       
     /**
     * *******************************************************
     * This method is test data for create contact object
     * *******************************************************
     */
    public static Contact createContact(Boolean doInsert){
        return createContact(doInsert, createAccount(true).Id);
    }
    
    public static Contact createContact(Boolean doInsert, Id accId){
        Contact con = new Contact();
        con.AccountId = accId;
        con.FirstName = 'FirstName';
        con.LastName = 'LastName';
        con.Email = '[email protected]' + Math.floor(Math.random() * 1000);
        if(doInsert)
        {
            insert con;
        }
        return con;
    }

    /**
    * ***********************************************************
    * This method is test data for create Opportunity object
    * ***********************************************************
    */
    
     public static List<Opportunity>createOpportunity(Id accountId, Integer numOpps) {
		List<Opportunity> opps = new List<Opportunity>();
		for(Integer i = 1; i <= numOpps; i++) {
			Opportunity opp = new Opportunity();
			opp.name = 'Account ' + i;
			opp.accountId = accountid;
			opp.amount = 1000;
			opp.closeDate = Date.today().addDays(5);
			opp.stageName = 'Prospecting';
			opps.add(opp);
		}
		return opps;
	}
}

Consideration

Test classes did not commit the data which is created in test method so you don’t need to delete the test data which is created in test classes. Let see some more consideration in details.

Important thing to learn
  • Test method and test classes are not counted as a part of code limit
  • System.debug statement are not counted as a part of apex code limit.
  • @isTest annotation with test method  is equivalent to testMethod keyword.
  • Test class and method default access is private, no matter to add access specifier.
  • Classes with @isTest annotation can’t be a interface or enum.
  • Test method code can’t be invoked by non test request .
  • Stating with salesforce API 28.0 test method can not reside inside non test classes .
  • @Testvisible annotation to make visible private methods inside test classes.
  • Test method can’t be used to test web-service call out. Please use call out mock .
  • You can’t  send email from test method.
  • SeeAllData=true will not work for API 23 version eailer.
  • We can run unit test by using Salesforce Standard UI,Force.com IDE ,Console ,API.
  • System.runAs will not enforce user permission or field level permission .
  • Every test to runAs count against the total number of DML issued in the process .
  • Test environment support @testVisible, @testSetup as well
  • Test method should static and no void return type.
  • Accessing static resource test records in test class e,g List accList=Test.loadData(Account,SobjectType,’ResourceName’).

Further Learning

Amit Chaudhary
Amit Chaudhary

Amit Chaudhary is Salesforce Application & System Architect and working on Salesforce Platform since 2010. He is Salesforce MVP since 2017 and have 17 Salesforce Certificates.

He is a active blogger and founder of Apex Hours.

Articles: 461

3 Comments

  1. I was asked in Interview.
    If we create new account in SFDC from Integration. What all all mandatory things will create in Salesforce?
    What are the prerequisite of integration or what to do before we start integration

  2. Is bypassing Triggers in Test classes is best practices? We always bypass the triggeres in Test class to avoid CPU time limit? Is it a best practice?

    • No that would generally not be a good practice. During data setup maybe as you can set fields exactly as you need them and not burn CPU time limits. During the test method itself you would want the code to run simulating a real transaction, otherwise you will not not if method A causes method B to error or vice versa. You could have apex test methods that only test specific methods but you need to test a full transaction also.

Leave a Reply

Your email address will not be published. Required fields are marked *