Introduction
Writing Apex test classes is a crucial part of developing on the Salesforce platform. Test classes ensure that your code works as expected and meets the required quality standards. Salesforce even mandates that at least 75% of your Apex code is covered by tests before you can deploy it to production.
But writing effective test classes isn’t just about meeting this requirement—it’s about ensuring your code is reliable, maintainable, and bug-free.
In this blog post, we’ll walk through some best practices for writing Apex test classes, along with examples to help you get started.
1. Follow the AAA Pattern: Arrange, Act, Assert
The AAA pattern is a simple and effective way to structure your test methods. It divides your test into three clear sections:
Arrange: Set up the test data and conditions.
Act: Execute the code you’re testing.
Assert: Verify that the results are as expected.
Example:
@isTest
public class AccountProcessorTest {
@isTest
static void testUpdateAccount() {
// Arrange
Account acc = new Account(Name = 'Test Account');
insert acc;
// Act
acc.Name = 'Updated Account';
update acc;
// Assert
Account updatedAcc = [SELECT Name FROM Account WHERE Id = :acc.Id];
System.assertEquals('Updated Account', updatedAcc.Name, 'Account name should be updated');
}
}
2. Use @isTest Annotation
Always use the @isTest
 annotation to define your test classes and methods. This tells Salesforce that the class or method is a test and should not count toward your organization’s code limit.
Example:
@isTest
public class MyTestClass {
@isTest
static void myTestMethod() {
// Test logic here
}
}
3. Test Bulk Data Handling
Salesforce is a multi-tenant platform, so your code must handle bulk operations efficiently. Test your code with multiple records to ensure it performs well in real-world scenarios.
Example:
@isTest
public class BulkAccountTest {
@isTest
static void testBulkUpdate() {
// Arrange
List accounts = new List();
for (Integer i = 0; i < 200; i++) {
accounts.add(new Account(Name = 'Test Account ' + i));
}
insert accounts;
// Act
for (Account acc : accounts) {
acc.Name = 'Updated Account';
}
update accounts;
// Assert
List updatedAccounts = [SELECT Name FROM Account WHERE Id IN :accounts];
for (Account acc : updatedAccounts) {
System.assertEquals('Updated Account', acc.Name, 'All account names should be updated');
}
}
}
4. Use Test.startTest() and Test.stopTest()
These methods help you isolate the code you’re testing and reset governor limits. Any code between Test.startTest()
 and Test.stopTest()
 gets a fresh set of governor limits, which is useful for testing performance.
Example:
@isTest
public class LimitTest {
@isTest
static void testGovernorLimits() {
// Arrange
List accounts = new List();
for (Integer i = 0; i < 100; i++) {
accounts.add(new Account(Name = 'Test Account ' + i));
}
// Act
Test.startTest();
insert accounts;
Test.stopTest();
// Assert
System.assertEquals(100, [SELECT COUNT() FROM Account], '100 accounts should be created');
}
}
5. Test for Negative Scenarios
Don’t just test for the “happy path.” Make sure your code handles errors and edge cases gracefully. Use try-catch
 blocks to test for exceptions.
Example:
@isTest
public class NegativeTest {
@isTest
static void testInvalidAccount() {
// Arrange
Account acc = new Account(); // Missing required fields
// Act & Assert
try {
insert acc;
System.assert(false, 'Expected an exception but none was thrown');
} catch (DmlException e) {
System.assert(e.getMessage().contains('Required fields are missing'), 'Expected error message not found');
}
}
}
6. Use Test Data Factories
Creating test data can be repetitive. Use a test data factory to generate reusable test data. This keeps your test classes clean and maintainable.
Example:
public class TestDataFactory {
public static List createAccounts(Integer numAccounts) {
List accounts = new List();
for (Integer i = 0; i < numAccounts; i++) {
accounts.add(new Account(Name = 'Test Account ' + i));
}
return accounts;
}
}
@isTest
public class FactoryTest {
@isTest
static void testFactory() {
// Arrange
List accounts = TestDataFactory.createAccounts(10);
// Act
insert accounts;
// Assert
System.assertEquals(10, [SELECT COUNT() FROM Account], '10 accounts should be created');
}
}
7. Achieve High Code Coverage
While 75% is the minimum, aim for 100% code coverage. This ensures that every line of your code is tested and reduces the risk of bugs.
8. Test Triggers and Workflows
If your code includes triggers or workflows, make sure to test them as part of your test class. Insert, update, or delete records to trigger these actions.
Example:
@isTest
public class TriggerTest {
@isTest
static void testAccountTrigger() {
// Arrange
Account acc = new Account(Name = 'Test Account');
// Act
Test.startTest();
insert acc;
Test.stopTest();
// Assert
Account updatedAcc = [SELECT Description FROM Account WHERE Id = :acc.Id];
System.assertEquals('Trigger updated this field', updatedAcc.Description, 'Trigger should update the description');
}
}
9. Avoid Hardcoding IDs
Avoid hardcoding record IDs in your tests. Instead, create test data dynamically.
Example:
// Bad Practice
Account acc = new Account(Id = '001000000000001');
// Good Practice
Account acc = new Account(Name = 'Test Account');
insert acc;
10. Use System.runAs() to Test 'User-Specific Logic'
If your code depends on the user’s profile or permissions, use System.runAs()
 to test it in the context of a specific user.
Example:
@isTest
public class RunAsTest {
@isTest
static void testRunAs() {
// Arrange
User testUser = [SELECT Id FROM User WHERE Username = 'testuser@example.com'];
Account acc;
// Act
System.runAs(testUser) {
acc = new Account(Name = 'Test Account');
insert acc;
}
// Assert
System.assertNotEquals(null, acc.Id, 'Account should be created by the test user');
}
}
11. Use SeeAllData=false
By default, test classes cannot access existing data in your org. Use @isTest(SeeAllData=false)
 to ensure your tests run in isolation
Example:
@isTest(SeeAllData=false)
public class MyTestClass {
// Test methods
}
12. Keep Tests Independent
Each test method should be independent of others. Avoid relying on the order of execution or shared data between tests
Conclusion
Writing effective Apex test classes is essential for building reliable and maintainable Salesforce applications. By following these best practices—such as using the AAA pattern, testing bulk data, and handling negative scenarios—you can ensure your code is robust and ready for production.
Remember, the goal isn’t just to meet the 75% code coverage requirement but to write meaningful tests that catch bugs and improve the quality of your code.Â
Happy testing! 🚀