@salesforce/afv-skills 1.1.0 → 1.3.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 +6 -5
- package/skills/accessing-webapp-data/SKILL.md +178 -0
- 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/building-webapp-data-visualization/SKILL.md +72 -0
- package/skills/building-webapp-data-visualization/implementation/bar-line-chart.md +316 -0
- package/skills/building-webapp-data-visualization/implementation/dashboard-layout.md +189 -0
- package/skills/building-webapp-data-visualization/implementation/donut-chart.md +181 -0
- package/skills/building-webapp-data-visualization/implementation/stat-card.md +150 -0
- package/skills/building-webapp-react-components/SKILL.md +96 -0
- package/skills/building-webapp-react-components/implementation/component.md +78 -0
- package/skills/building-webapp-react-components/implementation/header-footer.md +132 -0
- package/skills/building-webapp-react-components/implementation/page.md +93 -0
- package/skills/configuring-webapp-csp-trusted-sites/SKILL.md +90 -0
- package/skills/configuring-webapp-csp-trusted-sites/implementation/metadata-format.md +281 -0
- package/skills/configuring-webapp-metadata/SKILL.md +158 -0
- package/skills/creating-webapp/SKILL.md +141 -0
- package/skills/deploying-webapp-to-salesforce/SKILL.md +229 -0
- package/skills/exploring-webapp-graphql-schema/SKILL.md +149 -0
- package/skills/fetching-webapp-rest-api/SKILL.md +167 -0
- package/skills/{salesforce-custom-application → generating-custom-application}/SKILL.md +2 -4
- package/skills/{salesforce-custom-field → generating-custom-field}/SKILL.md +1 -5
- package/skills/{salesforce-custom-lightning-type → generating-custom-lightning-type}/SKILL.md +36 -2
- package/skills/{salesforce-custom-object → generating-custom-object}/SKILL.md +1 -1
- package/skills/generating-custom-tab/SKILL.md +154 -0
- package/skills/generating-experience-lwr-site/SKILL.md +196 -0
- package/skills/generating-experience-lwr-site/docs/bootstrap-template-byo-lwr.md +224 -0
- package/skills/generating-experience-lwr-site/docs/configure-content-brandingSet.md +131 -0
- package/skills/generating-experience-lwr-site/docs/configure-content-route.md +232 -0
- package/skills/generating-experience-lwr-site/docs/configure-content-themeLayout.md +141 -0
- package/skills/generating-experience-lwr-site/docs/configure-content-view.md +233 -0
- package/skills/generating-experience-lwr-site/docs/configure-guest-sharing-rules.md +42 -0
- package/skills/generating-experience-lwr-site/docs/handle-component-and-region-ids.md +27 -0
- package/skills/generating-experience-lwr-site/docs/handle-ui-components.md +215 -0
- package/skills/generating-experience-react-site/SKILL.md +67 -0
- package/skills/generating-experience-react-site/docs/configure-metadata-custom-site.md +41 -0
- package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience-bundle.md +17 -0
- package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience-config.md +21 -0
- package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience.md +38 -0
- package/skills/generating-experience-react-site/docs/configure-metadata-network.md +72 -0
- package/skills/{salesforce-flexipage → generating-flexipage}/SKILL.md +86 -9
- package/skills/{salesforce-flow → generating-flow}/SKILL.md +3 -3
- package/skills/generating-fragment/SKILL.md +117 -0
- package/skills/generating-lightning-app/SKILL.md +423 -0
- package/skills/{salesforce-list-view → generating-list-view}/SKILL.md +1 -1
- package/skills/generating-permission-set/SKILL.md +174 -0
- package/skills/{salesforce-validation-rule → generating-validation-rule}/SKILL.md +1 -1
- package/skills/generating-webapp-graphql-mutation-query/SKILL.md +258 -0
- package/skills/generating-webapp-graphql-read-query/SKILL.md +253 -0
- package/skills/implementing-webapp-file-upload/SKILL.md +396 -0
- package/skills/installing-webapp-features/SKILL.md +210 -0
- package/skills/managing-webapp-agentforce-conversation-client/SKILL.md +186 -0
- package/skills/managing-webapp-agentforce-conversation-client/references/constraints.md +134 -0
- package/skills/managing-webapp-agentforce-conversation-client/references/examples.md +132 -0
- package/skills/managing-webapp-agentforce-conversation-client/references/style-tokens.md +101 -0
- package/skills/managing-webapp-agentforce-conversation-client/references/troubleshooting.md +57 -0
- package/skills/switching-org/SKILL.md +28 -0
- package/skills/using-webapp-graphql/SKILL.md +324 -0
- package/skills/using-webapp-graphql/shared-schema.graphqls +1150 -0
- 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-custom-tab/SKILL.md +0 -78
- package/skills/salesforce-experience-site/SKILL.md +0 -178
- package/skills/salesforce-fragment/SKILL.md +0 -42
- package/skills/salesforce-lightning-app-build/SKILL.md +0 -254
- package/skills/salesforce-web-app-creating-records/SKILL.md +0 -84
- package/skills/salesforce-web-app-feature/SKILL.md +0 -70
- package/skills/salesforce-web-app-list-and-create-records/SKILL.md +0 -36
- package/skills/salesforce-web-application/SKILL.md +0 -34
|
@@ -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
|
-
```
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
# Mocking Patterns
|
|
2
|
-
|
|
3
|
-
## HTTP Callout Mocking
|
|
4
|
-
|
|
5
|
-
Apex doesn't allow real HTTP callouts in tests. Use `HttpCalloutMock` interface.
|
|
6
|
-
|
|
7
|
-
### Basic Mock Implementation
|
|
8
|
-
|
|
9
|
-
```apex
|
|
10
|
-
@IsTest
|
|
11
|
-
public class MockHttpResponse implements HttpCalloutMock {
|
|
12
|
-
|
|
13
|
-
private Integer statusCode;
|
|
14
|
-
private String body;
|
|
15
|
-
|
|
16
|
-
public MockHttpResponse(Integer statusCode, String body) {
|
|
17
|
-
this.statusCode = statusCode;
|
|
18
|
-
this.body = body;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
public HTTPResponse respond(HTTPRequest req) {
|
|
22
|
-
HttpResponse res = new HttpResponse();
|
|
23
|
-
res.setStatusCode(statusCode);
|
|
24
|
-
res.setBody(body);
|
|
25
|
-
res.setHeader('Content-Type', 'application/json');
|
|
26
|
-
return res;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Using the Mock
|
|
32
|
-
|
|
33
|
-
```apex
|
|
34
|
-
@IsTest
|
|
35
|
-
private static void shouldProcessApiResponse_WhenCalloutSucceeds() {
|
|
36
|
-
// Given
|
|
37
|
-
String mockResponse = '{"status": "success", "data": [{"id": "123"}]}';
|
|
38
|
-
Test.setMock(HttpCalloutMock.class, new MockHttpResponse(200, mockResponse));
|
|
39
|
-
|
|
40
|
-
// When
|
|
41
|
-
Test.startTest();
|
|
42
|
-
List<ExternalRecord> results = MyIntegrationService.fetchRecords();
|
|
43
|
-
Test.stopTest();
|
|
44
|
-
|
|
45
|
-
// Then
|
|
46
|
-
System.Assert.areEqual(1, results.size(), 'Should parse one record from response');
|
|
47
|
-
System.Assert.areEqual('123', results[0].externalId, 'Should extract correct ID');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
@IsTest
|
|
51
|
-
private static void shouldHandleError_WhenCalloutFails() {
|
|
52
|
-
// Given
|
|
53
|
-
String errorResponse = '{"error": "Unauthorized"}';
|
|
54
|
-
Test.setMock(HttpCalloutMock.class, new MockHttpResponse(401, errorResponse));
|
|
55
|
-
|
|
56
|
-
// When
|
|
57
|
-
Test.startTest();
|
|
58
|
-
CalloutResult result = MyIntegrationService.fetchRecords();
|
|
59
|
-
Test.stopTest();
|
|
60
|
-
|
|
61
|
-
// Then
|
|
62
|
-
System.Assert.areEqual(false, result.isSuccess, 'Should indicate failure');
|
|
63
|
-
System.Assert.isTrue(result.errorMessage.contains('Unauthorized'), 'Should capture error');
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### Multi-Request Mock
|
|
68
|
-
|
|
69
|
-
For services making multiple callouts:
|
|
70
|
-
|
|
71
|
-
```apex
|
|
72
|
-
@IsTest
|
|
73
|
-
public class MultiRequestMock implements HttpCalloutMock {
|
|
74
|
-
|
|
75
|
-
private Map<String, HttpResponse> endpointResponses;
|
|
76
|
-
|
|
77
|
-
public MultiRequestMock(Map<String, HttpResponse> responses) {
|
|
78
|
-
this.endpointResponses = responses;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
public HTTPResponse respond(HTTPRequest req) {
|
|
82
|
-
String endpoint = req.getEndpoint();
|
|
83
|
-
|
|
84
|
-
for (String key : endpointResponses.keySet()) {
|
|
85
|
-
if (endpoint.contains(key)) {
|
|
86
|
-
return endpointResponses.get(key);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Default 404 if no match
|
|
91
|
-
HttpResponse res = new HttpResponse();
|
|
92
|
-
res.setStatusCode(404);
|
|
93
|
-
res.setBody('{"error": "Not found"}');
|
|
94
|
-
return res;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Usage:
|
|
99
|
-
Map<String, HttpResponse> mocks = new Map<String, HttpResponse>();
|
|
100
|
-
|
|
101
|
-
HttpResponse authResponse = new HttpResponse();
|
|
102
|
-
authResponse.setStatusCode(200);
|
|
103
|
-
authResponse.setBody('{"token": "abc123"}');
|
|
104
|
-
mocks.put('/oauth/token', authResponse);
|
|
105
|
-
|
|
106
|
-
HttpResponse dataResponse = new HttpResponse();
|
|
107
|
-
dataResponse.setStatusCode(200);
|
|
108
|
-
dataResponse.setBody('{"records": []}');
|
|
109
|
-
mocks.put('/api/records', dataResponse);
|
|
110
|
-
|
|
111
|
-
Test.setMock(HttpCalloutMock.class, new MultiRequestMock(mocks));
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## StaticResourceCalloutMock
|
|
115
|
-
|
|
116
|
-
For complex response bodies, store JSON in Static Resources:
|
|
117
|
-
|
|
118
|
-
```apex
|
|
119
|
-
@IsTest
|
|
120
|
-
private static void shouldParseComplexResponse() {
|
|
121
|
-
StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
|
|
122
|
-
mock.setStaticResource('TestApiResponse'); // Static Resource name
|
|
123
|
-
mock.setStatusCode(200);
|
|
124
|
-
mock.setHeader('Content-Type', 'application/json');
|
|
125
|
-
|
|
126
|
-
Test.setMock(HttpCalloutMock.class, mock);
|
|
127
|
-
|
|
128
|
-
Test.startTest();
|
|
129
|
-
Result r = MyService.callExternalApi();
|
|
130
|
-
Test.stopTest();
|
|
131
|
-
|
|
132
|
-
System.Assert.isNotNull(r, 'Should parse response');
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Stub API (Enterprise Pattern)
|
|
137
|
-
|
|
138
|
-
For mocking Apex class dependencies using `System.StubProvider`:
|
|
139
|
-
|
|
140
|
-
```apex
|
|
141
|
-
@IsTest
|
|
142
|
-
public class MyServiceMock implements System.StubProvider {
|
|
143
|
-
|
|
144
|
-
public Object handleMethodCall(
|
|
145
|
-
Object stubbedObject,
|
|
146
|
-
String stubbedMethodName,
|
|
147
|
-
Type returnType,
|
|
148
|
-
List<Type> paramTypes,
|
|
149
|
-
List<String> paramNames,
|
|
150
|
-
List<Object> args
|
|
151
|
-
) {
|
|
152
|
-
if (stubbedMethodName == 'getAccountData') {
|
|
153
|
-
return new AccountData('Mock Account', 'Active');
|
|
154
|
-
}
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Usage in test:
|
|
160
|
-
@IsTest
|
|
161
|
-
private static void shouldUseAccountData() {
|
|
162
|
-
MyServiceMock mockProvider = new MyServiceMock();
|
|
163
|
-
IMyService mockService = (IMyService)Test.createStub(IMyService.class, mockProvider);
|
|
164
|
-
|
|
165
|
-
// Inject mock into class under test
|
|
166
|
-
MyController controller = new MyController(mockService);
|
|
167
|
-
|
|
168
|
-
Test.startTest();
|
|
169
|
-
String result = controller.displayAccountInfo();
|
|
170
|
-
Test.stopTest();
|
|
171
|
-
|
|
172
|
-
System.Assert.isTrue(result.contains('Mock Account'), 'Should use mocked data');
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Email Mocking
|
|
177
|
-
|
|
178
|
-
Apex sends real emails by default. Use limits to verify:
|
|
179
|
-
|
|
180
|
-
```apex
|
|
181
|
-
@IsTest
|
|
182
|
-
private static void shouldSendEmail_WhenTriggered() {
|
|
183
|
-
Integer emailsBefore = Limits.getEmailInvocations();
|
|
184
|
-
|
|
185
|
-
Test.startTest();
|
|
186
|
-
MyService.sendNotification(testContact);
|
|
187
|
-
Test.stopTest();
|
|
188
|
-
|
|
189
|
-
// Verify email was queued (not actually sent in tests)
|
|
190
|
-
System.Assert.areEqual(
|
|
191
|
-
emailsBefore + 1,
|
|
192
|
-
Limits.getEmailInvocations(),
|
|
193
|
-
'One email should be sent'
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
## Platform Event Testing
|
|
199
|
-
|
|
200
|
-
```apex
|
|
201
|
-
@IsTest
|
|
202
|
-
private static void shouldPublishEvent_WhenRecordCreated() {
|
|
203
|
-
Test.startTest();
|
|
204
|
-
|
|
205
|
-
// Enable event delivery in test context
|
|
206
|
-
Test.enableChangeDataCapture();
|
|
207
|
-
|
|
208
|
-
Account acc = TestDataFactory.createAccount(true);
|
|
209
|
-
|
|
210
|
-
// Deliver events
|
|
211
|
-
Test.getEventBus().deliver();
|
|
212
|
-
|
|
213
|
-
Test.stopTest();
|
|
214
|
-
|
|
215
|
-
// Query platform event trigger results
|
|
216
|
-
List<EventLog__c> logs = [SELECT Id FROM EventLog__c WHERE AccountId__c = :acc.Id];
|
|
217
|
-
System.Assert.areEqual(1, logs.size(), 'Event handler should create log record');
|
|
218
|
-
}
|
|
219
|
-
```
|