sf-agent-framework
Version:
AI Agent Orchestration Framework for Salesforce Development - Two-phase architecture with 70% context reduction
465 lines (386 loc) • 13.7 kB
Markdown
This task guides the creation of comprehensive Apex test classes that ensure
code quality, meet coverage requirements, and validate business logic.
Enable developers to:
- Write meaningful test coverage (95%+ target)
- Test positive, negative, and edge cases
- Validate bulk operations
- Ensure security compliance
- Create maintainable test suites
## Test Class Structure
### Basic Test Class Template
```apex
@isTest
private class AccountServiceTest {
// Test data setup
@TestSetup
static void setup() {
// Create common test data once
TestDataFactory.createAccounts(200);
TestDataFactory.createContacts(500);
TestDataFactory.createOpportunities(100);
}
// Positive test case
@isTest
static void testAccountCreation_Success() {
// Given - Arrange test data
Account testAccount = TestDataFactory.buildAccount();
// When - Execute the action
Test.startTest();
AccountService.createAccount(testAccount);
Test.stopTest();
// Then - Assert outcomes
Account createdAccount = [SELECT Id, Name FROM Account WHERE Name = :testAccount.Name];
System.assertNotEquals(null, createdAccount.Id, 'Account should be created');
System.assertEquals(testAccount.Name, createdAccount.Name, 'Account name should match');
}
// Negative test case
@isTest
static void testAccountCreation_MissingRequiredField() {
// Given
Account testAccount = new Account(); // Missing required Name field
// When/Then
Test.startTest();
try {
AccountService.createAccount(testAccount);
System.assert(false, 'Expected exception for missing required field');
} catch (AccountService.ValidationException e) {
System.assert(e.getMessage().contains('Name is required'),
'Exception message should indicate missing name');
}
Test.stopTest();
}
// Bulk test case
@isTest
static void testBulkAccountProcessing() {
// Given
List<Account> accounts = [SELECT Id, AnnualRevenue FROM Account LIMIT 200];
// When
Test.startTest();
AccountService.calculateRatings(accounts);
Test.stopTest();
// Then
List<Account> updatedAccounts = [SELECT Id, Rating FROM Account WHERE Id IN :accounts];
System.assertEquals(200, updatedAccounts.size(), 'All accounts should be processed');
for (Account acc : updatedAccounts) {
System.assertNotEquals(null, acc.Rating, 'Rating should be calculated for all accounts');
}
}
// Security test case
@isTest
static void testAccountAccess_RestrictedUser() {
// Given
User restrictedUser = TestDataFactory.createUser('Standard User', false);
// When/Then
System.runAs(restrictedUser) {
Test.startTest();
try {
AccountService.getConfidentialAccounts();
System.assert(false, 'Restricted user should not access confidential accounts');
} catch (SecurityException e) {
System.assert(true, 'Security exception properly thrown');
}
Test.stopTest();
}
}
}
```
```apex
@isTest
public class TestDataFactory {
// Account creation methods
public static List<Account> createAccounts(Integer count) {
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < count; i++) {
accounts.add(buildAccount('Test Account ' + i));
}
insert accounts;
return accounts;
}
public static Account buildAccount() {
return buildAccount('Test Account');
}
public static Account buildAccount(String name) {
return new Account(
Name = name,
Type = 'Prospect',
Industry = 'Technology',
AnnualRevenue = Math.round(Math.random() * 1000000),
NumberOfEmployees = Integer.valueOf(Math.random() * 1000)
);
}
// Contact creation methods
public static List<Contact> createContacts(List<Account> accounts, Integer contactsPerAccount) {
List<Contact> contacts = new List<Contact>();
for (Account acc : accounts) {
for (Integer i = 0; i < contactsPerAccount; i++) {
contacts.add(buildContact(acc.Id, 'Contact ' + i));
}
}
insert contacts;
return contacts;
}
public static Contact buildContact(Id accountId, String lastName) {
return new Contact(
AccountId = accountId,
FirstName = 'Test',
LastName = lastName,
Email = lastName.deleteWhitespace() + '@test.com',
Phone = '555-' + String.valueOf(Math.round(Math.random() * 9999)).leftPad(4, '0')
);
}
// User creation for permission testing
public static User createUser(String profileName, Boolean insertUser) {
Profile p = [SELECT Id FROM Profile WHERE Name = :profileName LIMIT 1];
User u = new User(
Alias = 'test' + String.valueOf(Math.round(Math.random() * 999)),
Email = 'testuser' + Math.round(Math.random() * 9999) + '@test.com',
EmailEncodingKey = 'UTF-8',
LastName = 'Testing',
LanguageLocaleKey = 'en_US',
LocaleSidKey = 'en_US',
ProfileId = p.Id,
TimeZoneSidKey = 'America/Los_Angeles',
UserName = 'testuser' + Math.round(Math.random() * 999999) + '@test.com'
);
if (insertUser) {
insert u;
}
return u;
}
// Custom metadata for testing
public static void createCustomSettings() {
MyCustomSetting__c setting = new MyCustomSetting__c(
Name = 'Test Setting',
Value__c = 'Test Value',
IsActive__c = true
);
insert setting;
}
}
```
```apex
@isTest
private class AccountTriggerTest {
@isTest
static void testBeforeInsert() {
// Given
Account acc = TestDataFactory.buildAccount();
acc.Type = null; // Will be set by trigger
// When
Test.startTest();
insert acc;
Test.stopTest();
// Then
Account insertedAcc = [SELECT Id, Type FROM Account WHERE Id = :acc.Id];
System.assertEquals('Prospect', insertedAcc.Type,
'Trigger should set default Type');
}
@isTest
static void testAfterUpdate_BulkOperation() {
// Given
List<Account> accounts = TestDataFactory.createAccounts(200);
// When
Test.startTest();
for (Account acc : accounts) {
acc.AnnualRevenue = 1000000;
}
update accounts;
Test.stopTest();
// Then
List<Opportunity> createdOpps = [
SELECT Id, AccountId
FROM Opportunity
WHERE AccountId IN :accounts
];
System.assertEquals(200, createdOpps.size(),
'Trigger should create opportunity for each account');
}
}
```
```apex
@isTest
private class AsyncProcessorTest {
@isTest
static void testQueueableJob() {
// Given
List<Account> accounts = TestDataFactory.createAccounts(50);
// When
Test.startTest();
AccountQueueable job = new AccountQueueable(accounts);
System.enqueueJob(job);
Test.stopTest(); // Forces async execution
// Then
List<Account> processedAccounts = [
SELECT Id, ProcessedByQueueable__c
FROM Account
WHERE Id IN :accounts
];
for (Account acc : processedAccounts) {
System.assertEquals(true, acc.ProcessedByQueueable__c,
'All accounts should be processed by queueable');
}
}
@isTest
static void testBatchJob() {
// Given
TestDataFactory.createAccounts(500);
// When
Test.startTest();
AccountBatch batch = new AccountBatch();
Database.executeBatch(batch, 200);
Test.stopTest();
// Then
Integer processedCount = [
SELECT COUNT()
FROM Account
WHERE LastProcessedDate__c = TODAY
];
System.assertEquals(500, processedCount,
'All accounts should be processed by batch');
}
@isTest
static void testFutureMethod() {
// Given
Account acc = TestDataFactory.createAccounts(1)[0];
// When
Test.startTest();
AccountService.processAccountAsync(acc.Id);
Test.stopTest();
// Then
Account processedAcc = [
SELECT Id, AsyncProcessed__c
FROM Account
WHERE Id = :acc.Id
];
System.assertEquals(true, processedAcc.AsyncProcessed__c,
'Account should be processed asynchronously');
}
}
```
```apex
@isTest
private class RestServiceTest {
@isTest
static void testGetAccount_Success() {
// Given
Account acc = TestDataFactory.createAccounts(1)[0];
RestRequest req = new RestRequest();
RestResponse res = new RestResponse();
req.requestURI = '/services/apexrest/Account/' + acc.Id;
req.httpMethod = 'GET';
RestContext.request = req;
RestContext.response = res;
// When
Test.startTest();
AccountRestService.getAccount();
Test.stopTest();
// Then
System.assertEquals(200, res.statusCode);
Map<String, Object> responseBody = (Map<String, Object>) JSON.deserializeUntyped(res.responseBody.toString());
System.assertEquals(acc.Id, responseBody.get('Id'));
}
@isTest
static void testCallout_MockResponse() {
// Given
Test.setMock(HttpCalloutMock.class, new AccountCalloutMock());
// When
Test.startTest();
HttpResponse response = AccountService.syncWithExternalSystem('12345');
Test.stopTest();
// Then
System.assertEquals(200, response.getStatusCode());
System.assertEquals('Success', response.getStatus());
}
// Mock class for callout testing
private class AccountCalloutMock implements HttpCalloutMock {
public HTTPResponse respond(HTTPRequest req) {
HttpResponse res = new HttpResponse();
res.setHeader('Content-Type', 'application/json');
res.setBody('{"status":"Success","id":"12345"}');
res.setStatusCode(200);
return res;
}
}
}
```
```apex
@isTest
private class ConfigurableServiceTest {
@isTest
static void testWithCustomMetadata() {
// Note: Custom metadata cannot be inserted in tests
// Must use existing records or dependency injection
// Given - Using dependency injection
ConfigService.MetadataProvider mockProvider = new MockMetadataProvider();
ConfigService.setMetadataProvider(mockProvider);
// When
Test.startTest();
String configValue = ConfigService.getConfigValue('TestKey');
Test.stopTest();
// Then
System.assertEquals('TestValue', configValue);
}
private class MockMetadataProvider implements ConfigService.MetadataProvider {
public Config_Setting__mdt getConfig(String key) {
Config_Setting__mdt setting = new Config_Setting__mdt();
setting.DeveloperName = key;
setting.Value__c = 'TestValue';
return setting;
}
}
}
```
```apex
// Don't just test for not null
System.assertNotEquals(null, result); // Minimal
// Do test specific values
System.assertEquals(expectedValue, result, 'Descriptive failure message');
System.assertEquals(5, accounts.size(), 'Should create exactly 5 accounts');
System.assert(result.contains('Success'), 'Result should indicate success');
// Test collections thoroughly
System.assertEquals(expectedList.size(), actualList.size(), 'List sizes should match');
for (Integer i = 0; i < expectedList.size(); i++) {
System.assertEquals(expectedList[i].Name, actualList[i].Name,
'Account at index ' + i + ' should match');
}
// Test exceptions properly
try {
AccountService.doSomethingInvalid();
System.assert(false, 'Should have thrown exception');
} catch (SpecificException e) {
System.assert(e.getMessage().contains('Expected text'),
'Exception message should be specific');
}
```
```apex
@isTest
static void testNearGovernorLimits() {
// Test with maximum data volumes
List<Account> accounts = TestDataFactory.createAccounts(199); // Just under 200 DML rows
Test.startTest();
// This gives fresh limits
AccountService.processAccounts(accounts);
// Verify limits
System.assert(Limits.getDmlRows() < Limits.getLimitDmlRows(),
'Should not exceed DML row limit');
System.assert(Limits.getQueries() < Limits.getLimitQueries(),
'Should not exceed SOQL query limit');
Test.stopTest();
}
```
✅ 95%+ code coverage ✅ All test methods pass ✅ Tests are independent ✅
Meaningful assertions ✅ Bulk operations tested ✅ Security validated ✅ Edge
cases covered ✅ Fast execution time