@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.
Files changed (103) hide show
  1. package/package.json +4 -4
  2. package/skills/agentforce-development/SKILL.md +427 -0
  3. package/skills/agentforce-development/assets/README-legacy.md +89 -0
  4. package/skills/agentforce-development/assets/agent-spec-template.md +90 -0
  5. package/skills/agentforce-development/assets/agents/README.md +45 -0
  6. package/skills/agentforce-development/assets/agents/hello-world.agent +60 -0
  7. package/skills/agentforce-development/assets/agents/multi-topic.agent +105 -0
  8. package/skills/agentforce-development/assets/agents/production-faq.agent +101 -0
  9. package/skills/agentforce-development/assets/agents/production-faq.bundle-meta.xml +4 -0
  10. package/skills/agentforce-development/assets/agents/simple-qa.agent +72 -0
  11. package/skills/agentforce-development/assets/apex/models-api-queueable.cls +225 -0
  12. package/skills/agentforce-development/assets/bundle-meta.xml +23 -0
  13. package/skills/agentforce-development/assets/components/apex-action.agent +52 -0
  14. package/skills/agentforce-development/assets/components/error-handling.agent +58 -0
  15. package/skills/agentforce-development/assets/components/escalation-setup.agent +169 -0
  16. package/skills/agentforce-development/assets/components/flow-action.agent +66 -0
  17. package/skills/agentforce-development/assets/components/n-ary-conditions.agent +110 -0
  18. package/skills/agentforce-development/assets/components/topic-with-actions.agent +40 -0
  19. package/skills/agentforce-development/assets/deterministic-routing.agent +166 -0
  20. package/skills/agentforce-development/assets/escalation-pattern.agent +209 -0
  21. package/skills/agentforce-development/assets/flow-action-lookup.agent +115 -0
  22. package/skills/agentforce-development/assets/hub-and-spoke.agent +104 -0
  23. package/skills/agentforce-development/assets/invocable-apex-template.cls +187 -0
  24. package/skills/agentforce-development/assets/local-info-agent-annotated.agent +355 -0
  25. package/skills/agentforce-development/assets/metadata/basic-prompt-template.promptTemplate-meta.xml +109 -0
  26. package/skills/agentforce-development/assets/metadata/genai-function-apex.xml +92 -0
  27. package/skills/agentforce-development/assets/metadata/genai-function-flow.xml +57 -0
  28. package/skills/agentforce-development/assets/metadata/genai-plugin.xml +72 -0
  29. package/skills/agentforce-development/assets/metadata/http-callout-flow.flow-meta.xml +348 -0
  30. package/skills/agentforce-development/assets/metadata/record-grounded-prompt.promptTemplate-meta.xml +136 -0
  31. package/skills/agentforce-development/assets/minimal-starter.agent +42 -0
  32. package/skills/agentforce-development/assets/patterns/README.md +254 -0
  33. package/skills/agentforce-development/assets/patterns/action-callbacks.agent +178 -0
  34. package/skills/agentforce-development/assets/patterns/advanced-input-bindings.agent +141 -0
  35. package/skills/agentforce-development/assets/patterns/bidirectional-routing.agent +156 -0
  36. package/skills/agentforce-development/assets/patterns/critical-input-collection.agent +244 -0
  37. package/skills/agentforce-development/assets/patterns/delegation-routing.agent +89 -0
  38. package/skills/agentforce-development/assets/patterns/lifecycle-events.agent +127 -0
  39. package/skills/agentforce-development/assets/patterns/llm-controlled-actions.agent +184 -0
  40. package/skills/agentforce-development/assets/patterns/multi-step-workflow.agent +282 -0
  41. package/skills/agentforce-development/assets/patterns/open-gate-routing.agent +286 -0
  42. package/skills/agentforce-development/assets/patterns/procedural-instructions.agent +273 -0
  43. package/skills/agentforce-development/assets/patterns/prompt-template-action.agent +188 -0
  44. package/skills/agentforce-development/assets/patterns/system-instruction-overrides.agent +293 -0
  45. package/skills/agentforce-development/assets/prompt-rag-search.agent +131 -0
  46. package/skills/agentforce-development/assets/template-multi-topic.agent +160 -0
  47. package/skills/agentforce-development/assets/template-single-topic.agent +81 -0
  48. package/skills/agentforce-development/assets/verification-gate.agent +208 -0
  49. package/skills/agentforce-development/references/action-prompt-templates.md +164 -0
  50. package/skills/agentforce-development/references/actions-reference.md +592 -0
  51. package/skills/agentforce-development/references/agent-access-guide.md +72 -0
  52. package/skills/agentforce-development/references/agent-design-and-spec-creation.md +1010 -0
  53. package/skills/agentforce-development/references/agent-metadata-and-lifecycle.md +575 -0
  54. package/skills/agentforce-development/references/agent-script-core-language.md +1218 -0
  55. package/skills/agentforce-development/references/agent-topic-map-diagrams.md +323 -0
  56. package/skills/agentforce-development/references/agent-user-setup.md +526 -0
  57. package/skills/agentforce-development/references/agent-validation-and-debugging.md +803 -0
  58. package/skills/agentforce-development/references/known-issues.md +353 -0
  59. package/skills/agentforce-development/references/minimal-examples.md +67 -0
  60. package/skills/agentforce-development/references/production-gotchas.md +279 -0
  61. package/skills/agentforce-development/references/salesforce-cli-for-agents.md +393 -0
  62. package/skills/agentforce-development/references/version-history.md +23 -0
  63. package/skills/generate-permission-set/SKILL.md +174 -0
  64. package/skills/salesforce-custom-application/SKILL.md +1 -2
  65. package/skills/salesforce-custom-field/SKILL.md +0 -4
  66. package/skills/salesforce-custom-tab/SKILL.md +84 -8
  67. package/skills/salesforce-experience-lwr-site/SKILL.md +196 -0
  68. package/skills/salesforce-experience-lwr-site/docs/bootstrap-template-byo-lwr.md +224 -0
  69. package/skills/salesforce-experience-lwr-site/docs/configure-content-brandingSet.md +131 -0
  70. package/skills/salesforce-experience-lwr-site/docs/configure-content-route.md +232 -0
  71. package/skills/salesforce-experience-lwr-site/docs/configure-content-themeLayout.md +141 -0
  72. package/skills/salesforce-experience-lwr-site/docs/configure-content-view.md +233 -0
  73. package/skills/salesforce-experience-lwr-site/docs/configure-guest-sharing-rules.md +42 -0
  74. package/skills/salesforce-experience-lwr-site/docs/handle-component-and-region-ids.md +27 -0
  75. package/skills/salesforce-experience-lwr-site/docs/handle-ui-components.md +215 -0
  76. package/skills/salesforce-flow/SKILL.md +2 -2
  77. package/skills/salesforce-fragment/SKILL.md +85 -10
  78. package/skills/salesforce-lightning-app-build/SKILL.md +102 -10
  79. package/skills/apex-class/SKILL.md +0 -253
  80. package/skills/apex-class/examples/AccountDeduplicationBatch.cls +0 -148
  81. package/skills/apex-class/examples/AccountSelector.cls +0 -193
  82. package/skills/apex-class/examples/AccountService.cls +0 -201
  83. package/skills/apex-class/templates/abstract.cls +0 -128
  84. package/skills/apex-class/templates/batch.cls +0 -125
  85. package/skills/apex-class/templates/domain.cls +0 -102
  86. package/skills/apex-class/templates/dto.cls +0 -108
  87. package/skills/apex-class/templates/exception.cls +0 -51
  88. package/skills/apex-class/templates/interface.cls +0 -25
  89. package/skills/apex-class/templates/queueable.cls +0 -92
  90. package/skills/apex-class/templates/schedulable.cls +0 -75
  91. package/skills/apex-class/templates/selector.cls +0 -92
  92. package/skills/apex-class/templates/service.cls +0 -69
  93. package/skills/apex-class/templates/utility.cls +0 -97
  94. package/skills/apex-test-class/SKILL.md +0 -101
  95. package/skills/apex-test-class/references/assertion-patterns.md +0 -209
  96. package/skills/apex-test-class/references/async-testing.md +0 -276
  97. package/skills/apex-test-class/references/mocking-patterns.md +0 -219
  98. package/skills/apex-test-class/references/test-data-factory.md +0 -176
  99. package/skills/deployment-readiness-check/SKILL.md +0 -257
  100. package/skills/deployment-readiness-check/assets/deployment_checklist.md +0 -286
  101. package/skills/deployment-readiness-check/references/rollback_procedures.md +0 -308
  102. package/skills/deployment-readiness-check/scripts/check_metadata.sh +0 -207
  103. 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
- ```