@salesforce/afv-skills 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/skills/agentforce-development/SKILL.md +427 -0
- package/skills/agentforce-development/assets/README-legacy.md +89 -0
- package/skills/agentforce-development/assets/agent-spec-template.md +90 -0
- package/skills/agentforce-development/assets/agents/README.md +45 -0
- package/skills/agentforce-development/assets/agents/hello-world.agent +60 -0
- package/skills/agentforce-development/assets/agents/multi-topic.agent +105 -0
- package/skills/agentforce-development/assets/agents/production-faq.agent +101 -0
- package/skills/agentforce-development/assets/agents/production-faq.bundle-meta.xml +4 -0
- package/skills/agentforce-development/assets/agents/simple-qa.agent +72 -0
- package/skills/agentforce-development/assets/apex/models-api-queueable.cls +225 -0
- package/skills/agentforce-development/assets/bundle-meta.xml +23 -0
- package/skills/agentforce-development/assets/components/apex-action.agent +52 -0
- package/skills/agentforce-development/assets/components/error-handling.agent +58 -0
- package/skills/agentforce-development/assets/components/escalation-setup.agent +169 -0
- package/skills/agentforce-development/assets/components/flow-action.agent +66 -0
- package/skills/agentforce-development/assets/components/n-ary-conditions.agent +110 -0
- package/skills/agentforce-development/assets/components/topic-with-actions.agent +40 -0
- package/skills/agentforce-development/assets/deterministic-routing.agent +166 -0
- package/skills/agentforce-development/assets/escalation-pattern.agent +209 -0
- package/skills/agentforce-development/assets/flow-action-lookup.agent +115 -0
- package/skills/agentforce-development/assets/hub-and-spoke.agent +104 -0
- package/skills/agentforce-development/assets/invocable-apex-template.cls +187 -0
- package/skills/agentforce-development/assets/local-info-agent-annotated.agent +355 -0
- package/skills/agentforce-development/assets/metadata/basic-prompt-template.promptTemplate-meta.xml +109 -0
- package/skills/agentforce-development/assets/metadata/genai-function-apex.xml +92 -0
- package/skills/agentforce-development/assets/metadata/genai-function-flow.xml +57 -0
- package/skills/agentforce-development/assets/metadata/genai-plugin.xml +72 -0
- package/skills/agentforce-development/assets/metadata/http-callout-flow.flow-meta.xml +348 -0
- package/skills/agentforce-development/assets/metadata/record-grounded-prompt.promptTemplate-meta.xml +136 -0
- package/skills/agentforce-development/assets/minimal-starter.agent +42 -0
- package/skills/agentforce-development/assets/patterns/README.md +254 -0
- package/skills/agentforce-development/assets/patterns/action-callbacks.agent +178 -0
- package/skills/agentforce-development/assets/patterns/advanced-input-bindings.agent +141 -0
- package/skills/agentforce-development/assets/patterns/bidirectional-routing.agent +156 -0
- package/skills/agentforce-development/assets/patterns/critical-input-collection.agent +244 -0
- package/skills/agentforce-development/assets/patterns/delegation-routing.agent +89 -0
- package/skills/agentforce-development/assets/patterns/lifecycle-events.agent +127 -0
- package/skills/agentforce-development/assets/patterns/llm-controlled-actions.agent +184 -0
- package/skills/agentforce-development/assets/patterns/multi-step-workflow.agent +282 -0
- package/skills/agentforce-development/assets/patterns/open-gate-routing.agent +286 -0
- package/skills/agentforce-development/assets/patterns/procedural-instructions.agent +273 -0
- package/skills/agentforce-development/assets/patterns/prompt-template-action.agent +188 -0
- package/skills/agentforce-development/assets/patterns/system-instruction-overrides.agent +293 -0
- package/skills/agentforce-development/assets/prompt-rag-search.agent +131 -0
- package/skills/agentforce-development/assets/template-multi-topic.agent +160 -0
- package/skills/agentforce-development/assets/template-single-topic.agent +81 -0
- package/skills/agentforce-development/assets/verification-gate.agent +208 -0
- package/skills/agentforce-development/references/action-prompt-templates.md +164 -0
- package/skills/agentforce-development/references/actions-reference.md +592 -0
- package/skills/agentforce-development/references/agent-access-guide.md +72 -0
- package/skills/agentforce-development/references/agent-design-and-spec-creation.md +1010 -0
- package/skills/agentforce-development/references/agent-metadata-and-lifecycle.md +575 -0
- package/skills/agentforce-development/references/agent-script-core-language.md +1218 -0
- package/skills/agentforce-development/references/agent-topic-map-diagrams.md +323 -0
- package/skills/agentforce-development/references/agent-user-setup.md +526 -0
- package/skills/agentforce-development/references/agent-validation-and-debugging.md +803 -0
- package/skills/agentforce-development/references/known-issues.md +353 -0
- package/skills/agentforce-development/references/minimal-examples.md +67 -0
- package/skills/agentforce-development/references/production-gotchas.md +279 -0
- package/skills/agentforce-development/references/salesforce-cli-for-agents.md +393 -0
- package/skills/agentforce-development/references/version-history.md +23 -0
- package/skills/generate-permission-set/SKILL.md +174 -0
- package/skills/salesforce-custom-application/SKILL.md +1 -2
- package/skills/salesforce-custom-field/SKILL.md +0 -4
- package/skills/salesforce-custom-tab/SKILL.md +84 -8
- package/skills/salesforce-experience-lwr-site/SKILL.md +196 -0
- package/skills/salesforce-experience-lwr-site/docs/bootstrap-template-byo-lwr.md +224 -0
- package/skills/salesforce-experience-lwr-site/docs/configure-content-brandingSet.md +131 -0
- package/skills/salesforce-experience-lwr-site/docs/configure-content-route.md +232 -0
- package/skills/salesforce-experience-lwr-site/docs/configure-content-themeLayout.md +141 -0
- package/skills/salesforce-experience-lwr-site/docs/configure-content-view.md +233 -0
- package/skills/salesforce-experience-lwr-site/docs/configure-guest-sharing-rules.md +42 -0
- package/skills/salesforce-experience-lwr-site/docs/handle-component-and-region-ids.md +27 -0
- package/skills/salesforce-experience-lwr-site/docs/handle-ui-components.md +215 -0
- package/skills/salesforce-flow/SKILL.md +2 -2
- package/skills/salesforce-fragment/SKILL.md +85 -10
- package/skills/salesforce-lightning-app-build/SKILL.md +102 -10
- package/skills/apex-class/SKILL.md +0 -253
- package/skills/apex-class/examples/AccountDeduplicationBatch.cls +0 -148
- package/skills/apex-class/examples/AccountSelector.cls +0 -193
- package/skills/apex-class/examples/AccountService.cls +0 -201
- package/skills/apex-class/templates/abstract.cls +0 -128
- package/skills/apex-class/templates/batch.cls +0 -125
- package/skills/apex-class/templates/domain.cls +0 -102
- package/skills/apex-class/templates/dto.cls +0 -108
- package/skills/apex-class/templates/exception.cls +0 -51
- package/skills/apex-class/templates/interface.cls +0 -25
- package/skills/apex-class/templates/queueable.cls +0 -92
- package/skills/apex-class/templates/schedulable.cls +0 -75
- package/skills/apex-class/templates/selector.cls +0 -92
- package/skills/apex-class/templates/service.cls +0 -69
- package/skills/apex-class/templates/utility.cls +0 -97
- package/skills/apex-test-class/SKILL.md +0 -101
- package/skills/apex-test-class/references/assertion-patterns.md +0 -209
- package/skills/apex-test-class/references/async-testing.md +0 -276
- package/skills/apex-test-class/references/mocking-patterns.md +0 -219
- package/skills/apex-test-class/references/test-data-factory.md +0 -176
- package/skills/deployment-readiness-check/SKILL.md +0 -257
- package/skills/deployment-readiness-check/assets/deployment_checklist.md +0 -286
- package/skills/deployment-readiness-check/references/rollback_procedures.md +0 -308
- package/skills/deployment-readiness-check/scripts/check_metadata.sh +0 -207
- package/skills/salesforce-experience-site/SKILL.md +0 -178
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @description Utility class for {describe the category of utilities: String, Date, Collection, etc.}.
|
|
3
|
-
* All methods are static and side-effect-free (no SOQL, no DML).
|
|
4
|
-
* Private constructor prevents instantiation.
|
|
5
|
-
* @author Generated by Apex Class Writer Skill
|
|
6
|
-
*/
|
|
7
|
-
public with sharing class {ClassName} {
|
|
8
|
-
|
|
9
|
-
// ─── Private Constructor ─────────────────────────────────────────────
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @description Prevents instantiation — use static methods only
|
|
13
|
-
*/
|
|
14
|
-
@TestVisible
|
|
15
|
-
private {ClassName}() {
|
|
16
|
-
// Utility class — do not instantiate
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// ─── Public Methods ──────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
// TODO: Add utility methods below. Examples:
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @description Safely converts a String to an Integer, returning a default if parsing fails
|
|
25
|
-
* @param value The String to parse
|
|
26
|
-
* @param defaultValue The fallback value if parsing fails
|
|
27
|
-
* @return The parsed Integer or the default value
|
|
28
|
-
* @example
|
|
29
|
-
* Integer result = {ClassName}.safeParseInteger('42', 0); // returns 42
|
|
30
|
-
* Integer result = {ClassName}.safeParseInteger('abc', 0); // returns 0
|
|
31
|
-
*/
|
|
32
|
-
public static Integer safeParseInteger(String value, Integer defaultValue) {
|
|
33
|
-
if (String.isBlank(value)) {
|
|
34
|
-
return defaultValue;
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
return Integer.valueOf(value.trim());
|
|
38
|
-
} catch (TypeException e) {
|
|
39
|
-
return defaultValue;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @description Chunks a list into smaller sublists of the specified size.
|
|
45
|
-
* Useful for processing records in governor-limit-safe batches.
|
|
46
|
-
* @param items The list to chunk
|
|
47
|
-
* @param chunkSize The maximum size of each chunk
|
|
48
|
-
* @return A list of sublists, each containing up to chunkSize elements
|
|
49
|
-
* @example
|
|
50
|
-
* List<List<String>> chunks = {ClassName}.chunkList(myList, 200);
|
|
51
|
-
*/
|
|
52
|
-
public static List<List<Object>> chunkList(List<Object> items, Integer chunkSize) {
|
|
53
|
-
List<List<Object>> chunks = new List<List<Object>>();
|
|
54
|
-
if (items == null || items.isEmpty() || chunkSize <= 0) {
|
|
55
|
-
return chunks;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
List<Object> currentChunk = new List<Object>();
|
|
59
|
-
for (Object item : items) {
|
|
60
|
-
currentChunk.add(item);
|
|
61
|
-
if (currentChunk.size() == chunkSize) {
|
|
62
|
-
chunks.add(currentChunk);
|
|
63
|
-
currentChunk = new List<Object>();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (!currentChunk.isEmpty()) {
|
|
68
|
-
chunks.add(currentChunk);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return chunks;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* @description Extracts a Set of non-null field values from a list of SObjects
|
|
76
|
-
* @param records The SObject records to extract from
|
|
77
|
-
* @param fieldName The API name of the field to extract
|
|
78
|
-
* @return A Set of non-null String values
|
|
79
|
-
* @example
|
|
80
|
-
* Set<String> emails = {ClassName}.pluckStrings(contacts, 'Email');
|
|
81
|
-
*/
|
|
82
|
-
public static Set<String> pluckStrings(List<SObject> records, String fieldName) {
|
|
83
|
-
Set<String> values = new Set<String>();
|
|
84
|
-
if (records == null || String.isBlank(fieldName)) {
|
|
85
|
-
return values;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
for (SObject record : records) {
|
|
89
|
-
Object val = record.get(fieldName);
|
|
90
|
-
if (val != null) {
|
|
91
|
-
values.add(String.valueOf(val));
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return values;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: apex-test-class
|
|
3
|
-
description: Apex test class generation with TestDataFactory patterns, bulk testing (200+ records), mocking strategies for callouts and async operations, and assertion best practices. Use when creating new Apex test classes, improving test coverage, refactoring existing tests, or implementing proper testing patterns for triggers, services, controllers, batch jobs, queueables, and integrations.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Apex Test Class Skill
|
|
7
|
-
|
|
8
|
-
## Core Principles
|
|
9
|
-
|
|
10
|
-
1. **Bulkify tests** - Always test with 200+ records to catch governor limit issues
|
|
11
|
-
2. **Isolate test data** - Use `@TestSetup` and TestDataFactory; never rely on org data
|
|
12
|
-
3. **Assert meaningfully** - Test behavior, not just coverage; include failure messages
|
|
13
|
-
4. **Mock external dependencies** - Use `HttpCalloutMock`, `Test.setMock()` for integrations
|
|
14
|
-
5. **Test negative paths** - Validate error handling, not just happy paths
|
|
15
|
-
|
|
16
|
-
## Test Class Structure
|
|
17
|
-
|
|
18
|
-
```apex
|
|
19
|
-
@IsTest
|
|
20
|
-
private class MyServiceTest {
|
|
21
|
-
|
|
22
|
-
@TestSetup
|
|
23
|
-
static void setupTestData() {
|
|
24
|
-
// Create shared test data using TestDataFactory
|
|
25
|
-
List<Account> accounts = TestDataFactory.createAccounts(200, true);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
@IsTest
|
|
29
|
-
static void shouldPerformExpectedBehavior_WhenValidInput() {
|
|
30
|
-
// Given: Setup specific test state
|
|
31
|
-
List<Account> accounts = [SELECT Id, Name FROM Account];
|
|
32
|
-
|
|
33
|
-
// When: Execute the code under test
|
|
34
|
-
Test.startTest();
|
|
35
|
-
MyService.processAccounts(accounts);
|
|
36
|
-
Test.stopTest();
|
|
37
|
-
|
|
38
|
-
// Then: Assert expected outcomes
|
|
39
|
-
List<Account> updated = [SELECT Id, Status__c FROM Account];
|
|
40
|
-
System.Assert.areEqual(200, updated.size(), 'All accounts should be processed');
|
|
41
|
-
for (Account acc : updated) {
|
|
42
|
-
System.Assert.areEqual('Processed', acc.Status__c, 'Status should be updated');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@IsTest
|
|
47
|
-
static void shouldThrowException_WhenInvalidInput() {
|
|
48
|
-
// Given
|
|
49
|
-
List<Account> emptyList = new List<Account>();
|
|
50
|
-
|
|
51
|
-
// When/Then
|
|
52
|
-
Test.startTest();
|
|
53
|
-
try {
|
|
54
|
-
MyService.processAccounts(emptyList);
|
|
55
|
-
System.Assert.fail('Expected MyCustomException to be thrown');
|
|
56
|
-
} catch (MyCustomException e) {
|
|
57
|
-
System.Assert.isTrue(e.getMessage().contains('cannot be empty'),
|
|
58
|
-
'Exception message should indicate empty input');
|
|
59
|
-
}
|
|
60
|
-
Test.stopTest();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Naming Convention
|
|
66
|
-
|
|
67
|
-
Use descriptive method names: `should[ExpectedBehavior]_When[Condition]`
|
|
68
|
-
|
|
69
|
-
Examples:
|
|
70
|
-
- `shouldCreateContact_WhenAccountIsActive`
|
|
71
|
-
- `shouldThrowException_WhenEmailIsInvalid`
|
|
72
|
-
- `shouldSendNotification_WhenOpportunityClosedWon`
|
|
73
|
-
- `shouldBypassTrigger_WhenRunningAsBatch`
|
|
74
|
-
|
|
75
|
-
## Test.startTest() / Test.stopTest()
|
|
76
|
-
|
|
77
|
-
Always wrap the code under test:
|
|
78
|
-
- Resets governor limits for accurate limit testing
|
|
79
|
-
- Executes async operations synchronously (queueables, batch, future)
|
|
80
|
-
- Fires scheduled jobs immediately
|
|
81
|
-
|
|
82
|
-
## Reference Files
|
|
83
|
-
|
|
84
|
-
Detailed patterns for specific scenarios:
|
|
85
|
-
|
|
86
|
-
- **[references/test-data-factory.md](references/test-data-factory.md)** - TestDataFactory class patterns and field defaults
|
|
87
|
-
- **[references/assertion-patterns.md](references/assertion-patterns.md)** - Assertion best practices and common pitfalls
|
|
88
|
-
- **[references/mocking-patterns.md](references/mocking-patterns.md)** - HttpCalloutMock, Test.setMock(), stubbing
|
|
89
|
-
- **[references/async-testing.md](references/async-testing.md)** - Batch, Queueable, Future, Scheduled job testing
|
|
90
|
-
|
|
91
|
-
## Quick Reference: What to Test
|
|
92
|
-
|
|
93
|
-
| Component | Key Test Scenarios |
|
|
94
|
-
|-----------|-------------------|
|
|
95
|
-
| Trigger | Bulk insert/update/delete, recursion, field changes |
|
|
96
|
-
| Service | Valid/invalid inputs, bulk operations, exceptions |
|
|
97
|
-
| Controller | Page load, action methods, view state |
|
|
98
|
-
| Batch | Start/execute/finish, chunking, error records |
|
|
99
|
-
| Queueable | Chaining, bulkification, error handling |
|
|
100
|
-
| Callout | Success response, error response, timeout |
|
|
101
|
-
| Scheduled | Execution, CRON validation |
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
# Assertion Patterns
|
|
2
|
-
|
|
3
|
-
## Assertion Methods
|
|
4
|
-
|
|
5
|
-
The `System.Assert` class provides methods to assert various conditions in test methods. All methods support an optional message parameter for better error reporting.
|
|
6
|
-
|
|
7
|
-
| Method | Use Case |
|
|
8
|
-
|--------|----------|
|
|
9
|
-
| `System.Assert.areEqual(expected, actual, msg)` | Exact equality |
|
|
10
|
-
| `System.Assert.areNotEqual(notExpected, actual, msg)` | Value should differ |
|
|
11
|
-
| `System.Assert.isTrue(condition, msg)` | Boolean condition is true |
|
|
12
|
-
| `System.Assert.isFalse(condition, msg)` | Boolean condition is false |
|
|
13
|
-
| `System.Assert.isNull(value, msg)` | Value is null |
|
|
14
|
-
| `System.Assert.isNotNull(value, msg)` | Value is not null |
|
|
15
|
-
| `System.Assert.isInstanceOfType(instance, expectedType, msg)` | Instance is of specified type |
|
|
16
|
-
| `System.Assert.isNotInstanceOfType(instance, notExpectedType, msg)` | Instance is not of specified type |
|
|
17
|
-
| `System.Assert.fail(msg)` | Explicitly fail the test |
|
|
18
|
-
|
|
19
|
-
**Always include the message parameter** - Makes test failures meaningful and easier to debug.
|
|
20
|
-
|
|
21
|
-
**Note:** Assertion failures are fatal errors that halt code execution. You cannot catch assertion failures using try/catch blocks, even though they're logged as exceptions.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
**Note:** Call `startTest()` and `stopTest()` only once per test method. Wrap only the code under test between these calls, not setup or verification code.
|
|
25
|
-
|
|
26
|
-
## Good vs Bad Assertions
|
|
27
|
-
|
|
28
|
-
### ❌ Bad: No message, tests coverage not behavior
|
|
29
|
-
|
|
30
|
-
```apex
|
|
31
|
-
System.Assert.areEqual(true, result);
|
|
32
|
-
System.Assert.isTrue(accounts.size() > 0);
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### ✅ Good: Descriptive message, tests specific behavior
|
|
36
|
-
|
|
37
|
-
```apex
|
|
38
|
-
System.Assert.areEqual(true, result, 'Service should return true for valid input');
|
|
39
|
-
System.Assert.areEqual(200, accounts.size(), 'All 200 accounts should be processed');
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Common Assertion Patterns
|
|
43
|
-
|
|
44
|
-
### Collection Size
|
|
45
|
-
|
|
46
|
-
```apex
|
|
47
|
-
// Exact count
|
|
48
|
-
System.Assert.areEqual(200, results.size(), 'Should process all 200 records');
|
|
49
|
-
|
|
50
|
-
// Not empty
|
|
51
|
-
System.Assert.isFalse(results.isEmpty(), 'Results should not be empty');
|
|
52
|
-
|
|
53
|
-
// Empty
|
|
54
|
-
System.Assert.isTrue(results.isEmpty(), 'No results expected for invalid input');
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Field Values
|
|
58
|
-
|
|
59
|
-
```apex
|
|
60
|
-
// Single record
|
|
61
|
-
System.Assert.areEqual('Processed', acc.Status__c, 'Account status should be updated to Processed');
|
|
62
|
-
|
|
63
|
-
// All records in collection
|
|
64
|
-
for (Account acc : updatedAccounts) {
|
|
65
|
-
System.Assert.areEqual('Active', acc.Status__c,
|
|
66
|
-
'Account ' + acc.Name + ' should have Active status');
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Exception Testing
|
|
71
|
-
|
|
72
|
-
```apex
|
|
73
|
-
@IsTest
|
|
74
|
-
private static void shouldThrowException_WhenInputInvalid() {
|
|
75
|
-
Boolean exceptionThrown = false;
|
|
76
|
-
String exceptionMessage = '';
|
|
77
|
-
|
|
78
|
-
Test.startTest();
|
|
79
|
-
try {
|
|
80
|
-
MyService.process(null);
|
|
81
|
-
} catch (MyCustomException e) {
|
|
82
|
-
exceptionThrown = true;
|
|
83
|
-
exceptionMessage = e.getMessage();
|
|
84
|
-
}
|
|
85
|
-
Test.stopTest();
|
|
86
|
-
|
|
87
|
-
System.Assert.isTrue(exceptionThrown, 'MyCustomException should be thrown for null input');
|
|
88
|
-
System.Assert.isTrue(exceptionMessage.contains('cannot be null'),
|
|
89
|
-
'Exception message should mention null input');
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### DML Results
|
|
94
|
-
|
|
95
|
-
```apex
|
|
96
|
-
// Insert success
|
|
97
|
-
Database.SaveResult[] results = Database.insert(accounts, false);
|
|
98
|
-
for (Database.SaveResult sr : results) {
|
|
99
|
-
System.Assert.isTrue(sr.isSuccess(), 'Insert should succeed: ' + sr.getErrors());
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Expected failures
|
|
103
|
-
Database.SaveResult sr = Database.insert(invalidAccount, false);
|
|
104
|
-
System.Assert.isFalse(sr.isSuccess(), 'Insert should fail for invalid data');
|
|
105
|
-
System.Assert.isTrue(sr.getErrors()[0].getMessage().contains('REQUIRED_FIELD_MISSING'),
|
|
106
|
-
'Error should indicate missing required field');
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### Comparing Objects
|
|
110
|
-
|
|
111
|
-
```apex
|
|
112
|
-
// Compare specific fields, not entire objects
|
|
113
|
-
System.Assert.areEqual(expected.Name, actual.Name, 'Names should match');
|
|
114
|
-
System.Assert.areEqual(expected.Status__c, actual.Status__c, 'Status should match');
|
|
115
|
-
|
|
116
|
-
// Or use JSON for deep comparison (use sparingly)
|
|
117
|
-
System.Assert.areEqual(
|
|
118
|
-
JSON.serialize(expected),
|
|
119
|
-
JSON.serialize(actual),
|
|
120
|
-
'Objects should be identical'
|
|
121
|
-
);
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Date/DateTime Assertions
|
|
125
|
-
|
|
126
|
-
```apex
|
|
127
|
-
// Exact date
|
|
128
|
-
System.Assert.areEqual(Date.today(), record.CreatedDate__c, 'Should be created today');
|
|
129
|
-
|
|
130
|
-
// Date within range
|
|
131
|
-
System.Assert.isTrue(record.DueDate__c >= Date.today(), 'Due date should be in the future');
|
|
132
|
-
System.Assert.isTrue(record.DueDate__c <= Date.today().addDays(30),
|
|
133
|
-
'Due date should be within 30 days');
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Null Checks
|
|
137
|
-
|
|
138
|
-
```apex
|
|
139
|
-
// Should be null
|
|
140
|
-
System.Assert.isNull(result.ErrorMessage__c, 'No error expected for valid input');
|
|
141
|
-
|
|
142
|
-
// Should not be null
|
|
143
|
-
System.Assert.isNotNull(result.Id, 'Record should have been inserted');
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Type Checking
|
|
147
|
-
|
|
148
|
-
```apex
|
|
149
|
-
// Verify instance is of expected type
|
|
150
|
-
Object result = MyService.processData();
|
|
151
|
-
System.Assert.isInstanceOfType(result, MyCustomClass.class,
|
|
152
|
-
'Result should be an instance of MyCustomClass');
|
|
153
|
-
|
|
154
|
-
// Verify instance is not of a specific type
|
|
155
|
-
Object handler = HandlerFactory.create('Account');
|
|
156
|
-
System.Assert.isNotInstanceOfType(handler, ContactHandler.class,
|
|
157
|
-
'Account handler should not be a ContactHandler');
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Explicit Test Failures
|
|
161
|
-
|
|
162
|
-
```apex
|
|
163
|
-
// Use Assert.fail() when an exception should have been thrown but wasn't
|
|
164
|
-
@IsTest
|
|
165
|
-
private static void shouldThrowException_WhenInputInvalid() {
|
|
166
|
-
try {
|
|
167
|
-
MyService.process(null);
|
|
168
|
-
System.Assert.fail('Expected MyCustomException to be thrown for null input');
|
|
169
|
-
} catch (MyCustomException e) {
|
|
170
|
-
// Exception was thrown as expected, test passes
|
|
171
|
-
System.Assert.isTrue(e.getMessage().contains('cannot be null'),
|
|
172
|
-
'Exception message should mention null input');
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## Anti-Patterns to Avoid
|
|
178
|
-
|
|
179
|
-
### ❌ Testing implementation, not behavior
|
|
180
|
-
|
|
181
|
-
```apex
|
|
182
|
-
// Bad: Testing that a specific method was called
|
|
183
|
-
System.Assert.isTrue(MyClass.methodWasCalled, 'Method should be called');
|
|
184
|
-
|
|
185
|
-
// Good: Testing the observable outcome
|
|
186
|
-
System.Assert.areEqual('Expected Value', record.Field__c, 'Field should be updated');
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### ❌ Overly generic assertions
|
|
190
|
-
|
|
191
|
-
```apex
|
|
192
|
-
// Bad: Passes for any non-empty result
|
|
193
|
-
System.Assert.isTrue(results.size() > 0);
|
|
194
|
-
|
|
195
|
-
// Good: Verifies exact expected count
|
|
196
|
-
System.Assert.areEqual(200, results.size(), 'All 200 records should be returned');
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### ❌ Missing negative test assertions
|
|
200
|
-
|
|
201
|
-
```apex
|
|
202
|
-
// Bad: Only tests that no exception occurred
|
|
203
|
-
MyService.process(data); // Test passes if no exception
|
|
204
|
-
|
|
205
|
-
// Good: Verifies the actual outcome
|
|
206
|
-
Result r = MyService.process(data);
|
|
207
|
-
System.Assert.areEqual('Success', r.status, 'Processing should succeed');
|
|
208
|
-
System.Assert.areEqual(0, r.errorCount, 'No errors should occur');
|
|
209
|
-
```
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
# Async Testing Patterns
|
|
2
|
-
|
|
3
|
-
## Key Principle
|
|
4
|
-
|
|
5
|
-
`Test.stopTest()` forces all async operations to execute synchronously, allowing assertions on their results.
|
|
6
|
-
|
|
7
|
-
## Batch Apex Testing
|
|
8
|
-
|
|
9
|
-
### Basic Batch Test
|
|
10
|
-
|
|
11
|
-
```apex
|
|
12
|
-
@IsTest
|
|
13
|
-
private static void shouldProcessAllRecords_WhenBatchExecutes() {
|
|
14
|
-
// Given: Create test data
|
|
15
|
-
List<Account> accounts = TestDataFactory.createAccounts(200, true);
|
|
16
|
-
|
|
17
|
-
// When: Execute batch
|
|
18
|
-
Test.startTest();
|
|
19
|
-
MyBatchClass batch = new MyBatchClass();
|
|
20
|
-
Id batchId = Database.executeBatch(batch, 200);
|
|
21
|
-
Test.stopTest(); // Forces batch to complete
|
|
22
|
-
|
|
23
|
-
// Then: Verify results
|
|
24
|
-
List<Account> updated = [SELECT Id, Status__c FROM Account];
|
|
25
|
-
for (Account acc : updated) {
|
|
26
|
-
System.Assert.areEqual('Processed', acc.Status__c,
|
|
27
|
-
'Batch should update all account statuses');
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Testing Batch with Failures
|
|
33
|
-
|
|
34
|
-
```apex
|
|
35
|
-
@IsTest
|
|
36
|
-
private static void shouldLogErrors_WhenRecordsFail() {
|
|
37
|
-
// Given: Create mix of valid and invalid records
|
|
38
|
-
List<Account> accounts = TestDataFactory.createAccounts(198, true);
|
|
39
|
-
|
|
40
|
-
// Create 2 accounts that will fail processing
|
|
41
|
-
List<Account> invalidAccounts = new List<Account>();
|
|
42
|
-
for (Integer i = 0; i < 2; i++) {
|
|
43
|
-
invalidAccounts.add(new Account(
|
|
44
|
-
Name = 'Invalid Account ' + i,
|
|
45
|
-
Invalid_Field__c = 'triggers_validation_error'
|
|
46
|
-
));
|
|
47
|
-
}
|
|
48
|
-
insert invalidAccounts;
|
|
49
|
-
|
|
50
|
-
// When
|
|
51
|
-
Test.startTest();
|
|
52
|
-
MyBatchClass batch = new MyBatchClass();
|
|
53
|
-
Database.executeBatch(batch, 50);
|
|
54
|
-
Test.stopTest();
|
|
55
|
-
|
|
56
|
-
// Then
|
|
57
|
-
List<Error_Log__c> errors = [SELECT Id, Message__c FROM Error_Log__c];
|
|
58
|
-
System.Assert.areEqual(2, errors.size(), 'Should log 2 failed records');
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Testing Batch Scope
|
|
63
|
-
|
|
64
|
-
```apex
|
|
65
|
-
@IsTest
|
|
66
|
-
private static void shouldRespectBatchSize() {
|
|
67
|
-
// Given
|
|
68
|
-
List<Account> accounts = TestDataFactory.createAccounts(250, true);
|
|
69
|
-
|
|
70
|
-
Test.startTest();
|
|
71
|
-
MyBatchClass batch = new MyBatchClass();
|
|
72
|
-
Database.executeBatch(batch, 50); // 5 batches of 50
|
|
73
|
-
Test.stopTest();
|
|
74
|
-
|
|
75
|
-
// Note: In tests, all batches execute but you can verify total processing
|
|
76
|
-
List<Account> processed = [SELECT Id FROM Account WHERE Processed__c = true];
|
|
77
|
-
System.Assert.areEqual(250, processed.size(), 'All records should be processed');
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Queueable Testing
|
|
82
|
-
|
|
83
|
-
### Basic Queueable Test
|
|
84
|
-
|
|
85
|
-
```apex
|
|
86
|
-
@IsTest
|
|
87
|
-
private static void shouldCompleteProcessing_WhenQueueableEnqueued() {
|
|
88
|
-
// Given
|
|
89
|
-
Account acc = TestDataFactory.createAccount(true);
|
|
90
|
-
|
|
91
|
-
// When
|
|
92
|
-
Test.startTest();
|
|
93
|
-
MyQueueableClass queueable = new MyQueueableClass(acc.Id);
|
|
94
|
-
System.enqueueJob(queueable);
|
|
95
|
-
Test.stopTest(); // Forces queueable to complete
|
|
96
|
-
|
|
97
|
-
// Then
|
|
98
|
-
Account updated = [SELECT Id, Status__c FROM Account WHERE Id = :acc.Id];
|
|
99
|
-
System.Assert.areEqual('Processed', updated.Status__c,
|
|
100
|
-
'Queueable should update account status');
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Testing Queueable Chaining
|
|
105
|
-
|
|
106
|
-
Chained queueables only execute the first job in tests:
|
|
107
|
-
|
|
108
|
-
```apex
|
|
109
|
-
@IsTest
|
|
110
|
-
private static void shouldChainNextJob_WhenMoreRecordsExist() {
|
|
111
|
-
// Given: More records than one queueable can process
|
|
112
|
-
List<Account> accounts = TestDataFactory.createAccounts(500, true);
|
|
113
|
-
|
|
114
|
-
Test.startTest();
|
|
115
|
-
// First queueable processes batch 1 and chains next
|
|
116
|
-
MyChainedQueueable queueable = new MyChainedQueueable(0, 100);
|
|
117
|
-
System.enqueueJob(queueable);
|
|
118
|
-
Test.stopTest();
|
|
119
|
-
|
|
120
|
-
// Verify first batch processed
|
|
121
|
-
List<Account> processed = [SELECT Id FROM Account WHERE Processed__c = true];
|
|
122
|
-
System.Assert.areEqual(100, processed.size(), 'First batch should process 100 records');
|
|
123
|
-
|
|
124
|
-
// Verify chain was enqueued (check AsyncApexJob)
|
|
125
|
-
List<AsyncApexJob> jobs = [
|
|
126
|
-
SELECT Id, Status, JobType
|
|
127
|
-
FROM AsyncApexJob
|
|
128
|
-
WHERE ApexClass.Name = 'MyChainedQueueable'
|
|
129
|
-
];
|
|
130
|
-
System.Assert.isTrue(jobs.size() >= 1, 'Chained job should be enqueued');
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### Testing Queueable with Callouts
|
|
135
|
-
|
|
136
|
-
```apex
|
|
137
|
-
@IsTest
|
|
138
|
-
private static void shouldMakeCallout_WhenQueueableWithCallout() {
|
|
139
|
-
// Given
|
|
140
|
-
Test.setMock(HttpCalloutMock.class, new MockHttpResponse(200, '{"status":"ok"}'));
|
|
141
|
-
Account acc = TestDataFactory.createAccount(true);
|
|
142
|
-
|
|
143
|
-
// When
|
|
144
|
-
Test.startTest();
|
|
145
|
-
MyQueueableWithCallout queueable = new MyQueueableWithCallout(acc.Id);
|
|
146
|
-
System.enqueueJob(queueable);
|
|
147
|
-
Test.stopTest();
|
|
148
|
-
|
|
149
|
-
// Then
|
|
150
|
-
Account updated = [SELECT Id, External_Status__c FROM Account WHERE Id = :acc.Id];
|
|
151
|
-
System.Assert.areEqual('Synced', updated.External_Status__c,
|
|
152
|
-
'Should update status after successful callout');
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
## Future Method Testing
|
|
157
|
-
|
|
158
|
-
```apex
|
|
159
|
-
@IsTest
|
|
160
|
-
private static void shouldExecuteFutureMethod() {
|
|
161
|
-
// Given
|
|
162
|
-
Account acc = TestDataFactory.createAccount(true);
|
|
163
|
-
|
|
164
|
-
// When
|
|
165
|
-
Test.startTest();
|
|
166
|
-
MyClass.processFuture(acc.Id); // @future method
|
|
167
|
-
Test.stopTest(); // Forces future to complete
|
|
168
|
-
|
|
169
|
-
// Then
|
|
170
|
-
Account updated = [SELECT Id, Processed__c FROM Account WHERE Id = :acc.Id];
|
|
171
|
-
System.Assert.areEqual(true, updated.Processed__c, 'Future should process record');
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
## Scheduled Apex Testing
|
|
176
|
-
|
|
177
|
-
### Testing Scheduled Execution
|
|
178
|
-
|
|
179
|
-
```apex
|
|
180
|
-
@IsTest
|
|
181
|
-
private static void shouldExecuteScheduledJob() {
|
|
182
|
-
// Given
|
|
183
|
-
List<Account> accounts = TestDataFactory.createAccounts(50, true);
|
|
184
|
-
|
|
185
|
-
// When
|
|
186
|
-
Test.startTest();
|
|
187
|
-
String cronExp = '0 0 0 1 1 ? 2099'; // Arbitrary future time
|
|
188
|
-
String jobId = System.schedule('Test Job', cronExp, new MyScheduledClass());
|
|
189
|
-
|
|
190
|
-
// Execute the scheduled job immediately
|
|
191
|
-
MyScheduledClass scheduled = new MyScheduledClass();
|
|
192
|
-
scheduled.execute(null); // Pass null SchedulableContext in tests
|
|
193
|
-
Test.stopTest();
|
|
194
|
-
|
|
195
|
-
// Then
|
|
196
|
-
List<Account> processed = [SELECT Id FROM Account WHERE Processed__c = true];
|
|
197
|
-
System.Assert.areEqual(50, processed.size(), 'Scheduled job should process records');
|
|
198
|
-
}
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Testing Schedule Registration
|
|
202
|
-
|
|
203
|
-
```apex
|
|
204
|
-
@IsTest
|
|
205
|
-
private static void shouldScheduleJob() {
|
|
206
|
-
Test.startTest();
|
|
207
|
-
String cronExp = '0 0 6 * * ?'; // Daily at 6 AM
|
|
208
|
-
String jobId = System.schedule('Daily Processing', cronExp, new MyScheduledClass());
|
|
209
|
-
Test.stopTest();
|
|
210
|
-
|
|
211
|
-
// Verify job is scheduled
|
|
212
|
-
CronTrigger ct = [
|
|
213
|
-
SELECT Id, CronExpression, State
|
|
214
|
-
FROM CronTrigger
|
|
215
|
-
WHERE Id = :jobId
|
|
216
|
-
];
|
|
217
|
-
System.Assert.areEqual('0 0 6 * * ?', ct.CronExpression, 'CRON should match');
|
|
218
|
-
System.Assert.areEqual('WAITING', ct.State, 'Job should be waiting');
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## Testing Async Limits
|
|
223
|
-
|
|
224
|
-
```apex
|
|
225
|
-
@IsTest
|
|
226
|
-
private static void shouldNotExceedQueueableLimits() {
|
|
227
|
-
// Given: Setup that might enqueue multiple jobs
|
|
228
|
-
List<Account> accounts = TestDataFactory.createAccounts(100, true);
|
|
229
|
-
|
|
230
|
-
Test.startTest();
|
|
231
|
-
Integer queueablesBefore = Limits.getQueueableJobs();
|
|
232
|
-
|
|
233
|
-
MyService.processWithQueueables(accounts);
|
|
234
|
-
|
|
235
|
-
Integer queueablesUsed = Limits.getQueueableJobs() - queueablesBefore;
|
|
236
|
-
Test.stopTest();
|
|
237
|
-
|
|
238
|
-
// Verify limit not exceeded (50 in synchronous context, 1 in queueable)
|
|
239
|
-
System.Assert.isTrue(queueablesUsed <= 50,
|
|
240
|
-
'Should not exceed queueable limit. Used: ' + queueablesUsed);
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
## Common Pitfalls
|
|
245
|
-
|
|
246
|
-
### ❌ Forgetting Test.stopTest()
|
|
247
|
-
|
|
248
|
-
```apex
|
|
249
|
-
// Bad: Async never executes
|
|
250
|
-
Test.startTest();
|
|
251
|
-
System.enqueueJob(new MyQueueable());
|
|
252
|
-
// Missing Test.stopTest()!
|
|
253
|
-
|
|
254
|
-
List<Account> results = [SELECT Id FROM Account WHERE Processed__c = true];
|
|
255
|
-
System.Assert.areEqual(100, results.size()); // FAILS - queueable didn't run
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### ❌ Testing chained jobs without understanding limits
|
|
259
|
-
|
|
260
|
-
```apex
|
|
261
|
-
// Only the FIRST chained queueable runs in tests
|
|
262
|
-
// Design tests to verify:
|
|
263
|
-
// 1. First job completes correctly
|
|
264
|
-
// 2. Chain is properly enqueued (check AsyncApexJob)
|
|
265
|
-
// 3. Each job works independently
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### ❌ Not mocking callouts in async
|
|
269
|
-
|
|
270
|
-
```apex
|
|
271
|
-
// Async with callouts MUST have mock set BEFORE Test.startTest()
|
|
272
|
-
Test.setMock(HttpCalloutMock.class, new MockResponse()); // Before startTest!
|
|
273
|
-
Test.startTest();
|
|
274
|
-
System.enqueueJob(new QueueableWithCallout());
|
|
275
|
-
Test.stopTest();
|
|
276
|
-
```
|