UNPKG

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
# Test Class Creator Task This task guides the creation of comprehensive Apex test classes that ensure code quality, meet coverage requirements, and validate business logic. ## Purpose 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(); } } } ``` ### Test Data Factory Pattern ```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; } } ``` ## Testing Patterns ### 1. Testing Triggers ```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'); } } ``` ### 2. Testing Async Operations ```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'); } } ``` ### 3. Testing Web Services ```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; } } } ``` ### 4. Testing with Custom Metadata ```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; } } } ``` ## Test Assertions Best Practices ### Comprehensive Assertions ```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'); } ``` ## Governor Limits Testing ### Testing Near Limits ```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(); } ``` ## Success Criteria95%+ code coverage ✅ All test methods pass ✅ Tests are independent ✅ Meaningful assertions ✅ Bulk operations tested ✅ Security validated ✅ Edge cases covered ✅ Fast execution time