

Test Class Best Practices in Salesforce
In this post, we will discuss Apex Test class best practices in Salesforce with examples. As a Salesforce developer, you should know the best practices for writing effective test classes. Here are some best practices to help you enhance your Apex Testing skills and make the most of Salesforce’s testing framework.
What to Test Classes?
Unit tests are performed by developers to ensure that functionality is working as expected, considering both positive and negative tests. We should not focus on the percentage of code coverage. Salesforce recommends testing the following components.
- Single Records: This includes testing to verify that a single record produces the correct, expected result
- 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.
- Positive scenarios: This type of component testing expects a system to save a record without error.
- Negative scenarios: This type of component testing expects a system to give an error.
- 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.
12 Apex Unit Test Tips Every Salesforce Developer Should Know.
Test Class Best Practices in Salesforce
To write effective test cases, a developer needs to ensure to include the below points into the test class.
- Test class must start with @isTest annotation.
1. Focus 90+ Code Coverage
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.
2. One Assert Statement per method
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)
3. Use @TestSetup
@testSetup to create test records once in a method and use in every test method in the test class. Learn more about Test Data Factory in Salesforce.
4. Use Test Data Factory
Create TestFactory class with @isTest annotation to exclude from organization code size limit.
5. Dont use SeeAllDdata=true
No SeeAllDdata=true: Always use ( seeAllData = false ) at the class or method level. User, profile, organization, AsyncApexjob, Corntrigger, RecordType, ApexClass, ApexComponent, ApexPage we can access without (seeAllData=true).
6. Check permission with System.runAs
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.
7. don’t use hard Coded IDs
Avoid Using Hard Coding IDs anywhere in the test Class or any apex class.
8. Handle Governor limits in Test classes
Governor limits: Using Test.startTest()
gives developers a new set of governor limits for the act stage of their test. Test.stopTest()
Then, it allows them to return to their previous governor’s limits. Test classes must test for Governor Limits using Limits Class.
9. Testing Exception in Test Classes
Testing Exception: Any exceptions that are caught in the production methods should be tested by feeding the test data that throws an exception. Exception Type and error message should be asserted.
10. Bulk Testing
Exercise bulk trigger functionality – use at least 200 records in your tests.
11. Handle External Callouts with Mock Callouts
Apex Test methods cannot make real callouts to external services. Instead, use mock callouts to simulate API responses and test your callout logic. Implement the HttpCalloutMock interface to create mock responses, ensuring your tests are not dependent on external systems or network conditions.
How to Create Test Data in Test Classes.
Let’s 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 an example of a 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 = 'FirstName@test.com' + 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
The data created in the test method was not committed to the test classes, so you don’t need to delete the test data created in the test classes. Let’s examine some more details.

Further Learning
Summary
Do you have more tips for writing effective Test classes in Salesforce? Please share them in the comments below! And don’t forget to subscribe to our blog for more Salesforce development insights.
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
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.
You say: “One Assert Statement per method”
But this is not a good idea. While a test should only test one bit of functionality, it can require multiple Assertions to confirm the code has functioned as expected.
Perhaps several records are being created – you want to check how many records are being created (that’s an assertion) and you may want to check the data in each created record – that’s one Assertion per field being checked, per record returned. It could be a LOT of assertions, but you are still only checking one action.
Another scenario is where there are several steps to the test. Create a record, that creates another record and then do some action on the auto-created records.
It’s reasonable to do some assertions after the first section, to make sure the initial records have been created as expected, and then a second set of Assertions to confirm the second stage is correct.
Running the same test many times, jut to check different aspects of the single result is unnecessary and would make the test difficult to maintain, should the logic of those tests ever change.