12 Best Practices for Writing Apex Test Classes in Salesforce

12 Best Practices for Writing Apex Test Classes in Salesforce
Chinmaya By Chinmaya
8 Min Read

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<Account> accounts = new List<Account>();
        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<Account> 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<Account> accounts = new List<Account>();
        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<Account> createAccounts(Integer numAccounts) {
        List<Account> accounts = new List<Account>();
        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<Account> 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! 🚀

Share This Article
Follow:
Chinmaya is working as a Senior Consultant with a deep expertise in Salesforce. Holding multiple Salesforce certifications, he is dedicated to designing and implementing cutting-edge CRM solutions. As the creator of Writtee.com, Chinmaya shares his knowledge on educational and technological topics, helping others excel in Salesforce and related domains.
Leave a comment