mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-05 23:41:31 +08:00
3.8 KiB
3.8 KiB
Test Patterns by Component Type
Quick reference for choosing the right test pattern and asset template for each component type.
Pattern Selection
| Component Under Test | Template | Key Scenarios |
|---|---|---|
| Service / Controller | assets/basic-test.cls | Valid input, null/invalid input, empty collection edge case |
| Trigger / Bulk DML | assets/bulk-test.cls | 251-record insert/update/delete, governor limit verification |
| HTTP Callout | assets/mock-callout-test.cls | Success response, error codes (400/401/500), timeout |
| DML-heavy service | assets/dml-mock.cls | Fast unit tests via DML injection, no database |
| Interface mocking | assets/stub-provider-example.cls | Dynamic behavior, method call tracking |
| Test data setup | assets/test-data-factory.cls | Consistent record creation across all tests |
Naming Convention
Use the should[ExpectedBehavior]_When[Condition] pattern for all test methods:
@IsTest static void shouldCreateContact_WhenAccountIsActive() { }
@IsTest static void shouldThrowException_WhenEmailIsInvalid() { }
@IsTest static void shouldProcessAllRecords_WhenBulkInsert() { }
@IsTest static void shouldReturnError_WhenCalloutTimesOut() { }
Test Structure (Given / When / Then)
Every test method follows this structure:
@IsTest
static void shouldUpdateStatus_WhenValidInput() {
// Given
List<Account> accounts = [SELECT Id FROM Account];
// When
Test.startTest();
MyService.processAccounts(accounts);
Test.stopTest();
// Then
List<Account> updated = [SELECT Status__c FROM Account];
Assert.areEqual('Processed', updated[0].Status__c, 'Status should be updated');
}
Component-Specific Guidance
Triggers
- Always test at 251 records (crosses 200-record batch boundary)
- Test insert, update, and delete events separately
- Verify recursion guards and field-change detection
- Use
assertGovernorLimitsNotExceeded()helper (see bulk-test.cls) - Duplicate Rules — bulk inserts of 251+ records frequently trigger
DUPLICATES_DETECTEDwhen field values collide across records. EnsureTestDataFactoryappends the loop index to all matchable fields (Name, Email, Phone, Company). If the org uses fuzzy matching rules, useDatabase.insert()withDuplicateRuleHeader.allowSave = true. See test-data-factory.md
Batch Apex
- Single
execute()in tests — only oneexecute()invocation runs; setbatchSize >= testRecordCountso all test data is processed - Verify
startquery returns expected scope - Test
finishfor cleanup and notifications - Test batch chaining in separate test methods —
finish()callingDatabase.executeBatch()throwsUnexpectedExceptionin tests - For
Database.Statefulbatches, remember accumulators reflect only the singleexecute()invocation - See async-testing.md for constraints, examples, and pitfalls
Queueable / Future
Test.stopTest()forces synchronous execution- Only the first chained queueable runs in tests
- Mock callouts before
Test.startTest()if the job makes HTTP requests - See async-testing.md for chaining and callout patterns
Callouts
- Salesforce requires
HttpCalloutMock-- real HTTP is blocked in tests - Test success, error codes, and timeout scenarios
- For complex response bodies, use
StaticResourceCalloutMock - See mocking-patterns.md for multi-endpoint and validation mocks
Scheduled Apex
- Verify CRON registration with
CronTriggerquery - Test execution by calling
execute(null)directly - See async-testing.md for schedule testing patterns