@salesforce/afv-skills 1.5.1 → 1.5.3

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 (78) hide show
  1. package/README.md +16 -416
  2. package/package.json +5 -3
  3. package/skills/building-ui-bundle-app/SKILL.md +325 -0
  4. package/skills/building-ui-bundle-frontend/SKILL.md +122 -0
  5. package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/component.md +1 -1
  6. package/skills/creating-b2b-commerce-store/SKILL.md +169 -0
  7. package/skills/creating-b2b-commerce-store/references/store-vs-storefront.md +169 -0
  8. package/skills/deploying-ui-bundle/SKILL.md +77 -0
  9. package/skills/generating-apex/CREDITS.md +30 -0
  10. package/skills/generating-apex/SKILL.md +342 -189
  11. package/skills/generating-apex/assets/abstract.cls +12 -9
  12. package/skills/generating-apex/assets/batch.cls +7 -8
  13. package/skills/generating-apex/assets/domain.cls +5 -6
  14. package/skills/generating-apex/assets/dto.cls +11 -12
  15. package/skills/generating-apex/assets/exception.cls +1 -2
  16. package/skills/generating-apex/assets/interface.cls +2 -3
  17. package/skills/generating-apex/assets/invocable.cls +114 -0
  18. package/skills/generating-apex/assets/queueable.cls +6 -7
  19. package/skills/generating-apex/assets/rest-resource.cls +300 -0
  20. package/skills/generating-apex/assets/schedulable.cls +7 -8
  21. package/skills/generating-apex/assets/selector.cls +7 -8
  22. package/skills/generating-apex/assets/service.cls +4 -5
  23. package/skills/generating-apex/assets/trigger.cls +45 -0
  24. package/skills/generating-apex/assets/utility.cls +5 -6
  25. package/skills/generating-apex/references/AccountDeduplicationBatch.cls +7 -8
  26. package/skills/generating-apex/references/AccountSelector.cls +10 -11
  27. package/skills/generating-apex/references/AccountService.cls +9 -10
  28. package/skills/generating-apex-test/CREDITS.md +30 -0
  29. package/skills/generating-apex-test/SKILL.md +165 -74
  30. package/skills/generating-apex-test/assets/test-class-template.cls +25 -56
  31. package/skills/generating-apex-test/assets/test-data-factory-template.cls +0 -1
  32. package/skills/generating-apex-test/references/assertion-patterns.md +38 -95
  33. package/skills/generating-apex-test/references/async-testing.md +59 -142
  34. package/skills/generating-apex-test/references/mocking-patterns.md +77 -76
  35. package/skills/generating-apex-test/references/test-data-factory.md +29 -130
  36. package/skills/generating-experience-react-site/SKILL.md +9 -9
  37. package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience.md +1 -1
  38. package/skills/generating-flexipage/SKILL.md +28 -12
  39. package/skills/generating-ui-bundle-features/SKILL.md +45 -0
  40. package/skills/generating-ui-bundle-metadata/SKILL.md +106 -0
  41. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/SKILL.md +5 -5
  42. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/constraints.md +2 -2
  43. package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/examples.md +1 -1
  44. package/skills/{implementing-webapp-file-upload → implementing-ui-bundle-file-upload}/SKILL.md +11 -11
  45. package/skills/searching-media/SKILL.md +1 -1
  46. package/skills/uplifting-components-to-slds2/SKILL.md +236 -0
  47. package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +438 -0
  48. package/skills/uplifting-components-to-slds2/references/common-patterns.md +87 -0
  49. package/skills/uplifting-components-to-slds2/references/examples.md +443 -0
  50. package/skills/uplifting-components-to-slds2/references/migration-checklist.md +67 -0
  51. package/skills/uplifting-components-to-slds2/references/non-color-hooks-decision-guide.md +333 -0
  52. package/skills/uplifting-components-to-slds2/references/rule-lwc-token-to-slds-hook.md +135 -0
  53. package/skills/uplifting-components-to-slds2/references/rule-no-deprecated-tokens-slds1.md +211 -0
  54. package/skills/uplifting-components-to-slds2/references/rule-no-hardcoded-values.md +160 -0
  55. package/skills/uplifting-components-to-slds2/references/rule-no-slds-class-overrides.md +126 -0
  56. package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data}/SKILL.md +52 -25
  57. package/skills/using-ui-bundle-salesforce-data/references/mutation-query-generation.md +140 -0
  58. package/skills/using-ui-bundle-salesforce-data/references/query-testing.md +78 -0
  59. package/skills/using-ui-bundle-salesforce-data/references/read-query-generation.md +307 -0
  60. package/skills/using-ui-bundle-salesforce-data/references/schema-introspection.md +53 -0
  61. package/skills/using-ui-bundle-salesforce-data/references/ui-bundle-integration.md +221 -0
  62. package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data/scripts}/graphql-search.sh +75 -23
  63. package/skills/building-webapp-data-visualization/SKILL.md +0 -72
  64. package/skills/building-webapp-data-visualization/implementation/bar-line-chart.md +0 -316
  65. package/skills/building-webapp-data-visualization/implementation/dashboard-layout.md +0 -189
  66. package/skills/building-webapp-data-visualization/implementation/donut-chart.md +0 -181
  67. package/skills/building-webapp-data-visualization/implementation/stat-card.md +0 -150
  68. package/skills/building-webapp-react-components/SKILL.md +0 -96
  69. package/skills/configuring-webapp-csp-trusted-sites/SKILL.md +0 -90
  70. package/skills/configuring-webapp-metadata/SKILL.md +0 -158
  71. package/skills/creating-webapp/SKILL.md +0 -138
  72. package/skills/deploying-webapp-to-salesforce/SKILL.md +0 -226
  73. package/skills/installing-webapp-features/SKILL.md +0 -210
  74. /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/header-footer.md +0 -0
  75. /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/page.md +0 -0
  76. /package/skills/{configuring-webapp-csp-trusted-sites/implementation/metadata-format.md → generating-ui-bundle-metadata/implementation/csp-metadata-format.md} +0 -0
  77. /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/style-tokens.md +0 -0
  78. /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/troubleshooting.md +0 -0
@@ -4,26 +4,30 @@
4
4
 
5
5
  | Method | Use Case |
6
6
  |--------|----------|
7
- | `System.assertEquals(expected, actual, msg)` | Exact equality |
8
- | `System.assertNotEquals(expected, actual, msg)` | Value should differ |
9
- | `System.assert(condition, msg)` | Boolean condition |
7
+ | `Assert.areEqual(expected, actual, msg)` | Exact equality |
8
+ | `Assert.areNotEqual(expected, actual, msg)` | Value should differ |
9
+ | `Assert.isTrue(condition, msg)` | Boolean condition |
10
+ | `Assert.isFalse(condition, msg)` | Negated boolean condition |
11
+ | `Assert.fail(msg)` | Force failure (e.g., expected exception not thrown) |
12
+ | `Assert.isNotNull(value, msg)` | Non-null check |
13
+ | `Assert.isNull(value, msg)` | Null check |
10
14
 
11
- **Always include the third parameter (message)** - Makes test failures meaningful.
15
+ **Always include the message parameter** makes test failures actionable.
12
16
 
13
17
  ## Good vs Bad Assertions
14
18
 
15
- ### Bad: No message, tests coverage not behavior
19
+ ### Bad: No message, tests coverage not behavior
16
20
 
17
21
  ```apex
18
- System.assertEquals(true, result);
19
- System.assert(accounts.size() > 0);
22
+ Assert.isTrue(result); // no message
23
+ Assert.isTrue(accounts.size() > 0); // vague — use areEqual with exact count
20
24
  ```
21
25
 
22
- ### Good: Descriptive message, tests specific behavior
26
+ ### Good: Descriptive message, tests specific behavior
23
27
 
24
28
  ```apex
25
- System.assertEquals(true, result, 'Service should return true for valid input');
26
- System.assertEquals(200, accounts.size(), 'All 200 accounts should be processed');
29
+ Assert.isTrue(result, 'Service should return true for valid input');
30
+ Assert.areEqual(200, accounts.size(), 'All 200 accounts should be processed');
27
31
  ```
28
32
 
29
33
  ## Common Assertion Patterns
@@ -31,25 +35,18 @@ System.assertEquals(200, accounts.size(), 'All 200 accounts should be processed'
31
35
  ### Collection Size
32
36
 
33
37
  ```apex
34
- // Exact count
35
- System.assertEquals(200, results.size(), 'Should process all 200 records');
36
-
37
- // Not empty
38
- System.assert(!results.isEmpty(), 'Results should not be empty');
39
-
40
- // Empty
41
- System.assert(results.isEmpty(), 'No results expected for invalid input');
38
+ Assert.areEqual(200, results.size(), 'Should process all 200 records');
39
+ Assert.isTrue(results.isEmpty(), 'No results expected for invalid input');
40
+ Assert.isFalse(results.isEmpty(), 'Results should not be empty');
42
41
  ```
43
42
 
44
43
  ### Field Values
45
44
 
46
45
  ```apex
47
- // Single record
48
- System.assertEquals('Processed', acc.Status__c, 'Account status should be updated to Processed');
46
+ Assert.areEqual('Processed', acc.Status__c, 'Account status should be updated to Processed');
49
47
 
50
- // All records in collection
51
48
  for (Account acc : updatedAccounts) {
52
- System.assertEquals('Active', acc.Status__c,
49
+ Assert.areEqual('Active', acc.Status__c,
53
50
  'Account ' + acc.Name + ' should have Active status');
54
51
  }
55
52
  ```
@@ -59,21 +56,15 @@ for (Account acc : updatedAccounts) {
59
56
  ```apex
60
57
  @isTest
61
58
  static void shouldThrowException_WhenInputInvalid() {
62
- Boolean exceptionThrown = false;
63
- String exceptionMessage = '';
64
-
65
59
  Test.startTest();
66
60
  try {
67
61
  MyService.process(null);
62
+ Assert.fail('Expected MyCustomException for null input');
68
63
  } catch (MyCustomException e) {
69
- exceptionThrown = true;
70
- exceptionMessage = e.getMessage();
64
+ Assert.isTrue(e.getMessage().contains('cannot be null'),
65
+ 'Exception message should mention null input');
71
66
  }
72
67
  Test.stopTest();
73
-
74
- System.assert(exceptionThrown, 'MyCustomException should be thrown for null input');
75
- System.assert(exceptionMessage.contains('cannot be null'),
76
- 'Exception message should mention null input');
77
68
  }
78
69
  ```
79
70
 
@@ -83,83 +74,35 @@ static void shouldThrowException_WhenInputInvalid() {
83
74
  // Insert success
84
75
  Database.SaveResult[] results = Database.insert(accounts, false);
85
76
  for (Database.SaveResult sr : results) {
86
- System.assert(sr.isSuccess(), 'Insert should succeed: ' + sr.getErrors());
77
+ Assert.isTrue(sr.isSuccess(), 'Insert should succeed: ' + sr.getErrors());
87
78
  }
88
79
 
89
- // Expected failures
90
80
  Database.SaveResult sr = Database.insert(invalidAccount, false);
91
- System.assert(!sr.isSuccess(), 'Insert should fail for invalid data');
92
- System.assert(sr.getErrors()[0].getMessage().contains('REQUIRED_FIELD_MISSING'),
81
+ Assert.isFalse(sr.isSuccess(), 'Insert should fail for invalid data');
82
+ Assert.isTrue(sr.getErrors()[0].getMessage().contains('REQUIRED_FIELD_MISSING'),
93
83
  'Error should indicate missing required field');
94
84
  ```
95
85
 
96
- ### Comparing Objects
97
-
98
- ```apex
99
- // Compare specific fields, not entire objects
100
- System.assertEquals(expected.Name, actual.Name, 'Names should match');
101
- System.assertEquals(expected.Status__c, actual.Status__c, 'Status should match');
102
-
103
- // Or use JSON for deep comparison (use sparingly)
104
- System.assertEquals(
105
- JSON.serialize(expected),
106
- JSON.serialize(actual),
107
- 'Objects should be identical'
108
- );
109
- ```
110
-
111
- ### Date/DateTime Assertions
112
-
113
- ```apex
114
- // Exact date
115
- System.assertEquals(Date.today(), record.CreatedDate__c, 'Should be created today');
116
-
117
- // Date within range
118
- System.assert(record.DueDate__c >= Date.today(), 'Due date should be in the future');
119
- System.assert(record.DueDate__c <= Date.today().addDays(30),
120
- 'Due date should be within 30 days');
121
- ```
122
-
123
86
  ### Null Checks
124
87
 
125
88
  ```apex
126
- // Should be null
127
- System.assertEquals(null, result.ErrorMessage__c, 'No error expected for valid input');
128
-
129
- // Should not be null
130
- System.assertNotEquals(null, result.Id, 'Record should have been inserted');
89
+ Assert.isNull(result.ErrorMessage__c, 'No error expected for valid input');
90
+ Assert.isNotNull(result.Id, 'Record should have been inserted');
131
91
  ```
132
92
 
133
- ## Anti-Patterns to Avoid
134
-
135
- ### ❌ Testing implementation, not behavior
93
+ ### Date/DateTime
136
94
 
137
95
  ```apex
138
- // Bad: Testing that a specific method was called
139
- System.assert(MyClass.methodWasCalled, 'Method should be called');
140
-
141
- // Good: Testing the observable outcome
142
- System.assertEquals('Expected Value', record.Field__c, 'Field should be updated');
96
+ Assert.areEqual(Date.today(), record.CreatedDate__c, 'Should be created today');
97
+ Assert.isTrue(record.DueDate__c >= Date.today(), 'Due date should be in the future');
143
98
  ```
144
99
 
145
- ### ❌ Overly generic assertions
100
+ ## Anti-Patterns
146
101
 
147
- ```apex
148
- // Bad: Passes for any non-empty result
149
- System.assert(results.size() > 0);
150
-
151
- // Good: Verifies exact expected count
152
- System.assertEquals(200, results.size(), 'All 200 records should be returned');
153
- ```
154
-
155
- ### ❌ Missing negative test assertions
156
-
157
- ```apex
158
- // Bad: Only tests that no exception occurred
159
- MyService.process(data); // Test passes if no exception
160
-
161
- // Good: Verifies the actual outcome
162
- Result r = MyService.process(data);
163
- System.assertEquals('Success', r.status, 'Processing should succeed');
164
- System.assertEquals(0, r.errorCount, 'No errors should occur');
165
- ```
102
+ | Anti-Pattern | Fix |
103
+ |---|---|
104
+ | `Assert.isTrue(results.size() > 0)` | Use `Assert.areEqual(expectedCount, results.size(), ...)` |
105
+ | `Assert.isTrue(results.size() >= expected)` | Compute exact expected count, use `Assert.areEqual` |
106
+ | Testing implementation not behavior | Assert on observable outcomes (field values, record counts) |
107
+ | Missing negative test assertions | Verify the actual outcome, not just that no exception occurred |
108
+ | `Assert.isTrue(count != 0)` | Use `Assert.areEqual` with deterministic value from test data |
@@ -8,36 +8,33 @@
8
8
 
9
9
  ### Basic Batch Test
10
10
 
11
+ **Critical:** In test context only one `execute()` invocation runs, so always set `batchSize >= testRecordCount` (e.g., `Database.executeBatch(batch, 200)` with 200 records). Never create more records than the batch size.
12
+
11
13
  ```apex
12
14
  @isTest
13
15
  static void shouldProcessAllRecords_WhenBatchExecutes() {
14
- // Given: Create test data
15
16
  List<Account> accounts = TestDataFactory.createAccounts(200, true);
16
-
17
- // When: Execute batch
17
+
18
18
  Test.startTest();
19
19
  MyBatchClass batch = new MyBatchClass();
20
20
  Id batchId = Database.executeBatch(batch, 200);
21
- Test.stopTest(); // Forces batch to complete
22
-
23
- // Then: Verify results
21
+ Test.stopTest();
22
+
24
23
  List<Account> updated = [SELECT Id, Status__c FROM Account];
25
24
  for (Account acc : updated) {
26
- System.assertEquals('Processed', acc.Status__c,
25
+ Assert.areEqual('Processed', acc.Status__c,
27
26
  'Batch should update all account statuses');
28
27
  }
29
28
  }
30
29
  ```
31
30
 
32
- ### Testing Batch with Failures
31
+ ### Batch with Failures
33
32
 
34
33
  ```apex
35
34
  @isTest
36
35
  static void shouldLogErrors_WhenRecordsFail() {
37
- // Given: Create mix of valid and invalid records
38
36
  List<Account> accounts = TestDataFactory.createAccounts(198, true);
39
-
40
- // Create 2 accounts that will fail processing
37
+
41
38
  List<Account> invalidAccounts = new List<Account>();
42
39
  for (Integer i = 0; i < 2; i++) {
43
40
  invalidAccounts.add(new Account(
@@ -46,37 +43,20 @@ static void shouldLogErrors_WhenRecordsFail() {
46
43
  ));
47
44
  }
48
45
  insert invalidAccounts;
49
-
50
- // When
46
+
51
47
  Test.startTest();
52
48
  MyBatchClass batch = new MyBatchClass();
53
- Database.executeBatch(batch, 50);
49
+ Database.executeBatch(batch, 200);
54
50
  Test.stopTest();
55
-
56
- // Then
51
+
57
52
  List<Error_Log__c> errors = [SELECT Id, Message__c FROM Error_Log__c];
58
- System.assertEquals(2, errors.size(), 'Should log 2 failed records');
53
+ Assert.areEqual(2, errors.size(), 'Should log 2 failed records');
59
54
  }
60
55
  ```
61
56
 
62
- ### Testing Batch Scope
57
+ ### Batch Chaining
63
58
 
64
- ```apex
65
- @isTest
66
- 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.assertEquals(250, processed.size(), 'All records should be processed');
78
- }
79
- ```
59
+ Test `finish()` chaining in a **separate test method** — calling `Database.executeBatch()` inside `finish()` during a test can throw `UnexpectedException`. Verify the first batch independently, then test that `finish()` enqueues the next batch.
80
60
 
81
61
  ## Queueable Testing
82
62
 
@@ -85,70 +65,65 @@ static void shouldRespectBatchSize() {
85
65
  ```apex
86
66
  @isTest
87
67
  static void shouldCompleteProcessing_WhenQueueableEnqueued() {
88
- // Given
89
68
  Account acc = TestDataFactory.createAccount(true);
90
-
91
- // When
69
+
92
70
  Test.startTest();
93
71
  MyQueueableClass queueable = new MyQueueableClass(acc.Id);
94
72
  System.enqueueJob(queueable);
95
- Test.stopTest(); // Forces queueable to complete
96
-
97
- // Then
73
+ Test.stopTest();
74
+
98
75
  Account updated = [SELECT Id, Status__c FROM Account WHERE Id = :acc.Id];
99
- System.assertEquals('Processed', updated.Status__c,
76
+ Assert.areEqual('Processed', updated.Status__c,
100
77
  'Queueable should update account status');
101
78
  }
102
79
  ```
103
80
 
104
- ### Testing Queueable Chaining
81
+ ### Queueable Chaining
105
82
 
106
- Chained queueables only execute the first job in tests:
83
+ Only the **first** chained queueable executes in tests. Design tests to verify:
84
+ 1. First job completes correctly
85
+ 2. Chain is properly enqueued (query `AsyncApexJob`)
86
+ 3. Each job works independently in its own test method
107
87
 
108
88
  ```apex
109
89
  @isTest
110
90
  static void shouldChainNextJob_WhenMoreRecordsExist() {
111
- // Given: More records than one queueable can process
112
91
  List<Account> accounts = TestDataFactory.createAccounts(500, true);
113
-
92
+
114
93
  Test.startTest();
115
- // First queueable processes batch 1 and chains next
116
94
  MyChainedQueueable queueable = new MyChainedQueueable(0, 100);
117
95
  System.enqueueJob(queueable);
118
96
  Test.stopTest();
119
-
120
- // Verify first batch processed
97
+
121
98
  List<Account> processed = [SELECT Id FROM Account WHERE Processed__c = true];
122
- System.assertEquals(100, processed.size(), 'First batch should process 100 records');
123
-
124
- // Verify chain was enqueued (check AsyncApexJob)
99
+ Assert.areEqual(100, processed.size(), 'First batch should process 100 records');
100
+
125
101
  List<AsyncApexJob> jobs = [
126
- SELECT Id, Status, JobType
127
- FROM AsyncApexJob
102
+ SELECT Id, Status, JobType
103
+ FROM AsyncApexJob
128
104
  WHERE ApexClass.Name = 'MyChainedQueueable'
129
105
  ];
130
- System.assert(jobs.size() >= 1, 'Chained job should be enqueued');
106
+ Assert.isTrue(jobs.size() >= 1, 'Chained job should be enqueued');
131
107
  }
132
108
  ```
133
109
 
134
- ### Testing Queueable with Callouts
110
+ ### Queueable with Callouts
111
+
112
+ Set the callout mock **before** `Test.startTest()`:
135
113
 
136
114
  ```apex
137
115
  @isTest
138
116
  static void shouldMakeCallout_WhenQueueableWithCallout() {
139
- // Given
140
117
  Test.setMock(HttpCalloutMock.class, new MockHttpResponse(200, '{"status":"ok"}'));
141
118
  Account acc = TestDataFactory.createAccount(true);
142
-
143
- // When
119
+
144
120
  Test.startTest();
145
121
  MyQueueableWithCallout queueable = new MyQueueableWithCallout(acc.Id);
146
122
  System.enqueueJob(queueable);
147
123
  Test.stopTest();
148
-
149
- // Then
124
+
150
125
  Account updated = [SELECT Id, External_Status__c FROM Account WHERE Id = :acc.Id];
151
- System.assertEquals('Synced', updated.External_Status__c,
126
+ Assert.areEqual('Synced', updated.External_Status__c,
152
127
  'Should update status after successful callout');
153
128
  }
154
129
  ```
@@ -158,119 +133,61 @@ static void shouldMakeCallout_WhenQueueableWithCallout() {
158
133
  ```apex
159
134
  @isTest
160
135
  static void shouldExecuteFutureMethod() {
161
- // Given
162
136
  Account acc = TestDataFactory.createAccount(true);
163
-
164
- // When
137
+
165
138
  Test.startTest();
166
- MyClass.processFuture(acc.Id); // @future method
167
- Test.stopTest(); // Forces future to complete
168
-
169
- // Then
139
+ MyClass.processFuture(acc.Id);
140
+ Test.stopTest();
141
+
170
142
  Account updated = [SELECT Id, Processed__c FROM Account WHERE Id = :acc.Id];
171
- System.assertEquals(true, updated.Processed__c, 'Future should process record');
143
+ Assert.isTrue(updated.Processed__c, 'Future should process record');
172
144
  }
173
145
  ```
174
146
 
175
147
  ## Scheduled Apex Testing
176
148
 
177
- ### Testing Scheduled Execution
149
+ ### Direct Execution
178
150
 
179
151
  ```apex
180
152
  @isTest
181
153
  static void shouldExecuteScheduledJob() {
182
- // Given
183
154
  List<Account> accounts = TestDataFactory.createAccounts(50, true);
184
-
185
- // When
155
+
186
156
  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
157
  MyScheduledClass scheduled = new MyScheduledClass();
192
- scheduled.execute(null); // Pass null SchedulableContext in tests
158
+ scheduled.execute(null);
193
159
  Test.stopTest();
194
-
195
- // Then
160
+
196
161
  List<Account> processed = [SELECT Id FROM Account WHERE Processed__c = true];
197
- System.assertEquals(50, processed.size(), 'Scheduled job should process records');
162
+ Assert.areEqual(50, processed.size(), 'Scheduled job should process records');
198
163
  }
199
164
  ```
200
165
 
201
- ### Testing Schedule Registration
166
+ ### CRON Registration
202
167
 
203
168
  ```apex
204
169
  @isTest
205
170
  static void shouldScheduleJob() {
206
171
  Test.startTest();
207
- String cronExp = '0 0 6 * * ?'; // Daily at 6 AM
172
+ String cronExp = '0 0 6 * * ?';
208
173
  String jobId = System.schedule('Daily Processing', cronExp, new MyScheduledClass());
209
174
  Test.stopTest();
210
-
211
- // Verify job is scheduled
175
+
212
176
  CronTrigger ct = [
213
- SELECT Id, CronExpression, State
214
- FROM CronTrigger
177
+ SELECT Id, CronExpression, State
178
+ FROM CronTrigger
215
179
  WHERE Id = :jobId
216
180
  ];
217
- System.assertEquals('0 0 6 * * ?', ct.CronExpression, 'CRON should match');
218
- System.assertEquals('WAITING', ct.State, 'Job should be waiting');
219
- }
220
- ```
221
-
222
- ## Testing Async Limits
223
-
224
- ```apex
225
- @isTest
226
- 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(queueablesUsed <= 50,
240
- 'Should not exceed queueable limit. Used: ' + queueablesUsed);
181
+ Assert.areEqual('0 0 6 * * ?', ct.CronExpression, 'CRON should match');
182
+ Assert.areEqual('WAITING', ct.State, 'Job should be waiting');
241
183
  }
242
184
  ```
243
185
 
244
186
  ## Common Pitfalls
245
187
 
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.assertEquals(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
- ```
188
+ | Pitfall | Impact |
189
+ |---|---|
190
+ | Missing `Test.stopTest()` | Async never executes, assertions fail silently |
191
+ | Expecting all chained queueables to run | Only the first runs; test each independently |
192
+ | Mock set after `Test.startTest()` | Callout mock must be set **before** `Test.startTest()` |
193
+ | Batch size < record count in tests | Only `batchSize` records processed; set `batchSize >= recordCount` |