Test classes are mandatory in Salesforce. You need at least 75% code coverage to deploy to production, but aiming for 100% with meaningful tests is the real goal. This guide covers writing effective test classes and test data setup.
@isTest
private class AccountServiceTest {
@TestSetup
static void setup() {
// Test data creation runs once for all tests
insertTestAccounts();
}
@isTest
static void testPositiveScenario() {
// Arrange
List<Account> accounts = [SELECT Id FROM Account LIMIT 1];
// Act
AccountService.processAccounts(accounts);
// Assert
List<Account> updatedAccounts = [SELECT Id, Rating FROM Account];
System.assertEquals('Hot', updatedAccounts[0].Rating);
}
@isTest
static void testNegativeScenario() {
Account acc = new Account(Name = 'Test Account', Revenue = 0);
insert acc;
try {
AccountService.validateRevenue(acc);
System.assert(false, 'Should have thrown exception');
} catch (CustomException e) {
System.assert(e.getMessage().contains('Revenue required'));
}
}
@isTest
static void testEdgeCase() {
// Test boundary conditions, null values, empty lists
AccountService.processAccounts(new List<Account>());
// Should not throw error
}
private static void insertTestAccounts() {
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < 10; i++) {
accounts.add(new Account(
Name = 'Test Account ' + i,
Revenue = 100000 + (i * 10000)
));
}
insert accounts;
}
}
Use a dedicated factory class to create test data. This keeps your tests clean and DRY.
@isTest
public class TestDataFactory {
public static Account createAccount(String name, String rating) {
Account acc = new Account(
Name = name,
Rating = rating,
Phone = '555-1234'
);
insert acc;
return acc;
}
public static List<Contact> createContacts(Integer count, Id accountId) {
List<Contact> contacts = new List<Contact>();
for (Integer i = 0; i < count; i++) {
contacts.add(new Contact(
FirstName = 'Test',
LastName = 'Contact ' + i,
AccountId = accountId,
Email = 'test' + i + '@example.com'
));
}
insert contacts;
return contacts;
}
public static Opportunity createOpportunity(Id accountId, String stageName) {
Opportunity opp = new Opportunity(
Name = 'Test Opportunity',
AccountId = accountId,
StageName = stageName,
CloseDate = Date.today().addDays(30),
Amount = 50000
);
insert opp;
return opp;
}
}
// Usage in tests
@isTest
private class OpportunityServiceTest {
@isTest
static void testOpportunityStage() {
Account acc = TestDataFactory.createAccount('Acme Corp', 'Hot');
Opportunity opp = TestDataFactory.createOpportunity(acc.Id, 'Prospecting');
OpportunityService.updateStage(opp.Id, 'Negotiation/Review');
Opportunity updatedOpp = [SELECT StageName FROM Opportunity WHERE Id = :opp.Id];
System.assertEquals('Negotiation/Review', updatedOpp.StageName);
}
}
Tests should verify your code works with bulk data. This catches bulkification issues and governor limit problems.
@isTest
private class BulkAccountTriggerTest {
@isTest
static void testBulkInsert() {
List<Account> accounts = new List<Account>();
// Create 200 accounts (max bulk size)
for (Integer i = 0; i < 200; i++) {
accounts.add(new Account(
Name = 'Bulk Account ' + i,
BillingCity = 'San Francisco'
));
}
insert accounts;
// Verify trigger processed all records
List<Account> verifyAccounts = [SELECT Id, Description FROM Account WHERE Name LIKE 'Bulk Account%'];
System.assertEquals(200, verifyAccounts.size());
for (Account acc : verifyAccounts) {
System.assert(acc.Description != null);
}
}
}
@isTest
private class AccountTriggerTest {
@TestSetup
static void setup() {
TestDataFactory.createAccount('Test Account', 'Hot');
}
@isTest
static void testBeforeInsert() {
Account acc = new Account(
Name = 'New Account',
BillingCity = null
);
insert acc;
// Trigger should set default city
Account inserted = [SELECT BillingCity FROM Account WHERE Id = :acc.Id];
System.assertEquals('San Francisco', inserted.BillingCity);
}
@isTest
static void testAfterInsert() {
Account parentAcc = [SELECT Id FROM Account LIMIT 1];
Account newAcc = new Account(
Name = 'Child Account',
ParentId = parentAcc.Id,
Type = 'Subsidiary'
);
insert newAcc;
// Trigger should create activity
List<Task> tasks = [SELECT Id FROM Task WHERE WhoId = :newAcc.Id];
System.assertEquals(1, tasks.size());
}
@isTest
static void testTriggerNotRecursive() {
Account acc = [SELECT Id FROM Account LIMIT 1];
acc.Name = 'Updated Name';
update acc; // Should not cause infinite loop
// Add assertion to verify expected behavior
System.assert(true); // Passed without recursion error
}
}
✓ Happy path (normal execution)
✓ Negative scenarios (exceptions, invalid data)
✓ Edge cases (null values, empty lists, boundary conditions)
✓ All if/else branches
✓ All loops with different iteration counts
✓ Exception handling (try/catch blocks)
✓ Bulk operations (200 records)
In Developer Console:
// Mark test classes and methods
@isTest
private class MyTest {
@isTest
static void testSomething() { }
}
// Mark test utilities
@isTest
public class TestHelper { }
// Use System.Test.startTest() and stopTest()
@isTest
static void testWithIsolation() {
Account acc = TestDataFactory.createAccount('Test', 'Hot');
Test.startTest();
// Test code here - governor limits reset
AccountService.processAccount(acc);
Test.stopTest();
// Assertions after stopTest()
}