@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.
- package/README.md +16 -416
- package/package.json +5 -3
- package/skills/building-ui-bundle-app/SKILL.md +325 -0
- package/skills/building-ui-bundle-frontend/SKILL.md +122 -0
- package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/component.md +1 -1
- package/skills/creating-b2b-commerce-store/SKILL.md +169 -0
- package/skills/creating-b2b-commerce-store/references/store-vs-storefront.md +169 -0
- package/skills/deploying-ui-bundle/SKILL.md +77 -0
- package/skills/generating-apex/CREDITS.md +30 -0
- package/skills/generating-apex/SKILL.md +342 -189
- package/skills/generating-apex/assets/abstract.cls +12 -9
- package/skills/generating-apex/assets/batch.cls +7 -8
- package/skills/generating-apex/assets/domain.cls +5 -6
- package/skills/generating-apex/assets/dto.cls +11 -12
- package/skills/generating-apex/assets/exception.cls +1 -2
- package/skills/generating-apex/assets/interface.cls +2 -3
- package/skills/generating-apex/assets/invocable.cls +114 -0
- package/skills/generating-apex/assets/queueable.cls +6 -7
- package/skills/generating-apex/assets/rest-resource.cls +300 -0
- package/skills/generating-apex/assets/schedulable.cls +7 -8
- package/skills/generating-apex/assets/selector.cls +7 -8
- package/skills/generating-apex/assets/service.cls +4 -5
- package/skills/generating-apex/assets/trigger.cls +45 -0
- package/skills/generating-apex/assets/utility.cls +5 -6
- package/skills/generating-apex/references/AccountDeduplicationBatch.cls +7 -8
- package/skills/generating-apex/references/AccountSelector.cls +10 -11
- package/skills/generating-apex/references/AccountService.cls +9 -10
- package/skills/generating-apex-test/CREDITS.md +30 -0
- package/skills/generating-apex-test/SKILL.md +165 -74
- package/skills/generating-apex-test/assets/test-class-template.cls +25 -56
- package/skills/generating-apex-test/assets/test-data-factory-template.cls +0 -1
- package/skills/generating-apex-test/references/assertion-patterns.md +38 -95
- package/skills/generating-apex-test/references/async-testing.md +59 -142
- package/skills/generating-apex-test/references/mocking-patterns.md +77 -76
- package/skills/generating-apex-test/references/test-data-factory.md +29 -130
- package/skills/generating-experience-react-site/SKILL.md +9 -9
- package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience.md +1 -1
- package/skills/generating-flexipage/SKILL.md +28 -12
- package/skills/generating-ui-bundle-features/SKILL.md +45 -0
- package/skills/generating-ui-bundle-metadata/SKILL.md +106 -0
- package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/SKILL.md +5 -5
- package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/constraints.md +2 -2
- package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/examples.md +1 -1
- package/skills/{implementing-webapp-file-upload → implementing-ui-bundle-file-upload}/SKILL.md +11 -11
- package/skills/searching-media/SKILL.md +1 -1
- package/skills/uplifting-components-to-slds2/SKILL.md +236 -0
- package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +438 -0
- package/skills/uplifting-components-to-slds2/references/common-patterns.md +87 -0
- package/skills/uplifting-components-to-slds2/references/examples.md +443 -0
- package/skills/uplifting-components-to-slds2/references/migration-checklist.md +67 -0
- package/skills/uplifting-components-to-slds2/references/non-color-hooks-decision-guide.md +333 -0
- package/skills/uplifting-components-to-slds2/references/rule-lwc-token-to-slds-hook.md +135 -0
- package/skills/uplifting-components-to-slds2/references/rule-no-deprecated-tokens-slds1.md +211 -0
- package/skills/uplifting-components-to-slds2/references/rule-no-hardcoded-values.md +160 -0
- package/skills/uplifting-components-to-slds2/references/rule-no-slds-class-overrides.md +126 -0
- package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data}/SKILL.md +52 -25
- package/skills/using-ui-bundle-salesforce-data/references/mutation-query-generation.md +140 -0
- package/skills/using-ui-bundle-salesforce-data/references/query-testing.md +78 -0
- package/skills/using-ui-bundle-salesforce-data/references/read-query-generation.md +307 -0
- package/skills/using-ui-bundle-salesforce-data/references/schema-introspection.md +53 -0
- package/skills/using-ui-bundle-salesforce-data/references/ui-bundle-integration.md +221 -0
- package/skills/{using-webapp-salesforce-data → using-ui-bundle-salesforce-data/scripts}/graphql-search.sh +75 -23
- package/skills/building-webapp-data-visualization/SKILL.md +0 -72
- package/skills/building-webapp-data-visualization/implementation/bar-line-chart.md +0 -316
- package/skills/building-webapp-data-visualization/implementation/dashboard-layout.md +0 -189
- package/skills/building-webapp-data-visualization/implementation/donut-chart.md +0 -181
- package/skills/building-webapp-data-visualization/implementation/stat-card.md +0 -150
- package/skills/building-webapp-react-components/SKILL.md +0 -96
- package/skills/configuring-webapp-csp-trusted-sites/SKILL.md +0 -90
- package/skills/configuring-webapp-metadata/SKILL.md +0 -158
- package/skills/creating-webapp/SKILL.md +0 -138
- package/skills/deploying-webapp-to-salesforce/SKILL.md +0 -226
- package/skills/installing-webapp-features/SKILL.md +0 -210
- /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/header-footer.md +0 -0
- /package/skills/{building-webapp-react-components → building-ui-bundle-frontend}/implementation/page.md +0 -0
- /package/skills/{configuring-webapp-csp-trusted-sites/implementation/metadata-format.md → generating-ui-bundle-metadata/implementation/csp-metadata-format.md} +0 -0
- /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/style-tokens.md +0 -0
- /package/skills/{managing-webapp-agentforce-conversation-client → implementing-ui-bundle-agentforce-conversation-client}/references/troubleshooting.md +0 -0
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Selector class for Account queries.
|
|
3
3
|
* Encapsulates all SOQL for Account records.
|
|
4
4
|
* All methods return bulkified results (Lists or Maps).
|
|
5
|
-
* @author Generated by Apex Class Writer Skill
|
|
6
5
|
*/
|
|
7
6
|
public with sharing class AccountSelector {
|
|
8
7
|
|
|
9
8
|
// ─── Field Lists ─────────────────────────────────────────────────────
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
11
|
+
* Returns the default set of fields to query for Account.
|
|
13
12
|
* Centralizes field references to keep queries DRY.
|
|
14
13
|
* @return Comma-separated field list as a String
|
|
15
14
|
*/
|
|
@@ -34,7 +33,7 @@ public with sharing class AccountSelector {
|
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
/**
|
|
37
|
-
*
|
|
36
|
+
* Returns fields needed for billing/territory operations
|
|
38
37
|
* @return Comma-separated field list as a String
|
|
39
38
|
*/
|
|
40
39
|
private static String getBillingFields() {
|
|
@@ -56,7 +55,7 @@ public with sharing class AccountSelector {
|
|
|
56
55
|
// ─── Query Methods ───────────────────────────────────────────────────
|
|
57
56
|
|
|
58
57
|
/**
|
|
59
|
-
*
|
|
58
|
+
* Selects Account records by their Ids
|
|
60
59
|
* @param recordIds Set of Account Ids to query
|
|
61
60
|
* @return List of Account records matching the provided Ids
|
|
62
61
|
* @example
|
|
@@ -76,7 +75,7 @@ public with sharing class AccountSelector {
|
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
/**
|
|
79
|
-
*
|
|
78
|
+
* Selects Account records as a Map keyed by Id
|
|
80
79
|
* @param recordIds Set of Account Ids to query
|
|
81
80
|
* @return Map of Id to Account
|
|
82
81
|
*/
|
|
@@ -85,7 +84,7 @@ public with sharing class AccountSelector {
|
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
/**
|
|
88
|
-
*
|
|
87
|
+
* Selects Accounts with billing address fields for territory assignment
|
|
89
88
|
* @param recordIds Set of Account Ids to query
|
|
90
89
|
* @return List of Account records with billing address fields populated
|
|
91
90
|
*/
|
|
@@ -102,7 +101,7 @@ public with sharing class AccountSelector {
|
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
/**
|
|
105
|
-
*
|
|
104
|
+
* Selects Accounts by Account Type
|
|
106
105
|
* @param accountTypes Set of Account Type values to filter by
|
|
107
106
|
* @return List of matching Account records
|
|
108
107
|
* @example
|
|
@@ -124,7 +123,7 @@ public with sharing class AccountSelector {
|
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
/**
|
|
127
|
-
*
|
|
126
|
+
* Selects Accounts by Industry with a minimum annual revenue
|
|
128
127
|
* @param industries Set of Industry values to filter by
|
|
129
128
|
* @param minRevenue Minimum AnnualRevenue threshold
|
|
130
129
|
* @return List of matching Account records ordered by revenue descending
|
|
@@ -151,7 +150,7 @@ public with sharing class AccountSelector {
|
|
|
151
150
|
}
|
|
152
151
|
|
|
153
152
|
/**
|
|
154
|
-
*
|
|
153
|
+
* Selects Accounts with their related Contacts (subquery)
|
|
155
154
|
* @param recordIds Set of Account Ids to query
|
|
156
155
|
* @return List of Account records with nested Contacts
|
|
157
156
|
*/
|
|
@@ -173,7 +172,7 @@ public with sharing class AccountSelector {
|
|
|
173
172
|
// ─── Aggregate Queries ───────────────────────────────────────────────
|
|
174
173
|
|
|
175
174
|
/**
|
|
176
|
-
*
|
|
175
|
+
* Returns a count of Accounts grouped by Industry
|
|
177
176
|
* @return List of AggregateResult with Industry and record count
|
|
178
177
|
* @example
|
|
179
178
|
* List<AggregateResult> results = AccountSelector.countByIndustry();
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Service class for Account business logic.
|
|
3
3
|
* Provides account deduplication, enrichment, and territory assignment.
|
|
4
4
|
* Delegates queries to AccountSelector and SObject manipulation to AccountDomain.
|
|
5
|
-
* @author Generated by Apex Class Writer Skill
|
|
6
5
|
*/
|
|
7
6
|
public with sharing class AccountService {
|
|
8
7
|
|
|
@@ -14,7 +13,7 @@ public with sharing class AccountService {
|
|
|
14
13
|
// ─── Public API ──────────────────────────────────────────────────────
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
|
-
*
|
|
16
|
+
* Merges duplicate Account records into a master record.
|
|
18
17
|
* The master record retains its field values; child records are reparented.
|
|
19
18
|
* @param masterIds Map of master Account Id to Set of duplicate Account Ids to merge
|
|
20
19
|
* @return List of master Account Ids that were successfully merged
|
|
@@ -77,7 +76,7 @@ public with sharing class AccountService {
|
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
/**
|
|
80
|
-
*
|
|
79
|
+
* Assigns accounts to territories based on Billing State/Country.
|
|
81
80
|
* Uses Custom Metadata Type (Territory_Mapping__mdt) for mappings.
|
|
82
81
|
* @param accountIds Set of Account Ids to assign territories for
|
|
83
82
|
* @return Number of accounts successfully updated
|
|
@@ -115,7 +114,7 @@ public with sharing class AccountService {
|
|
|
115
114
|
// ─── Convenience Overloads ───────────────────────────────────────────
|
|
116
115
|
|
|
117
116
|
/**
|
|
118
|
-
*
|
|
117
|
+
* Single-account territory assignment convenience method
|
|
119
118
|
* @param accountId The Account Id to assign a territory for
|
|
120
119
|
* @return 1 if updated, 0 if no change needed
|
|
121
120
|
*/
|
|
@@ -126,7 +125,7 @@ public with sharing class AccountService {
|
|
|
126
125
|
// ─── Private Helpers ─────────────────────────────────────────────────
|
|
127
126
|
|
|
128
127
|
/**
|
|
129
|
-
*
|
|
128
|
+
* Loads territory mappings from Custom Metadata
|
|
130
129
|
* @return Map of territory key (State:Country) to territory name
|
|
131
130
|
*/
|
|
132
131
|
private static Map<String, String> loadTerritoryMappings() {
|
|
@@ -139,7 +138,7 @@ public with sharing class AccountService {
|
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
/**
|
|
142
|
-
*
|
|
141
|
+
* Builds a consistent territory lookup key
|
|
143
142
|
* @param state The billing state
|
|
144
143
|
* @param country The billing country
|
|
145
144
|
* @return A normalized key string
|
|
@@ -149,7 +148,7 @@ public with sharing class AccountService {
|
|
|
149
148
|
}
|
|
150
149
|
|
|
151
150
|
/**
|
|
152
|
-
*
|
|
151
|
+
* Chunks a list of Accounts into sublists of the given size
|
|
153
152
|
* @param accounts The accounts to chunk
|
|
154
153
|
* @param chunkSize Maximum chunk size
|
|
155
154
|
* @return List of account sublists
|
|
@@ -172,7 +171,7 @@ public with sharing class AccountService {
|
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
/**
|
|
175
|
-
*
|
|
174
|
+
* Counts successful results from a DML operation
|
|
176
175
|
* @param results List of Database.SaveResult
|
|
177
176
|
* @return Count of successful operations
|
|
178
177
|
*/
|
|
@@ -195,7 +194,7 @@ public with sharing class AccountService {
|
|
|
195
194
|
// ─── Exception ───────────────────────────────────────────────────────
|
|
196
195
|
|
|
197
196
|
/**
|
|
198
|
-
*
|
|
197
|
+
* Custom exception for AccountService errors
|
|
199
198
|
*/
|
|
200
199
|
public class AccountServiceException extends Exception {}
|
|
201
200
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Credits & Acknowledgments
|
|
2
|
+
|
|
3
|
+
This skill was influenced by the [sf-skills](https://github.com/Jaganpro/sf-skills) repository and built upon the collective wisdom of the Salesforce developer community. We gratefully acknowledge the following authors and resources whose ideas, patterns, and best practices have shaped this skill.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Authors & Contributors
|
|
8
|
+
|
|
9
|
+
### Jag Valaiyapathy (**[Jaganpro)](https://github.com/Jaganpro)**
|
|
10
|
+
|
|
11
|
+
**[sf-skills](https://github.com/Jaganpro/sf-skills)**
|
|
12
|
+
|
|
13
|
+
Key contributions influencing this skill:
|
|
14
|
+
|
|
15
|
+
- Pioneering open-source Salesforce skills for agentic coding tools
|
|
16
|
+
- Apex code generation and review patterns
|
|
17
|
+
- Best practices, anti-patterns, and design patterns reference material
|
|
18
|
+
- Template library for common Apex class types
|
|
19
|
+
|
|
20
|
+
This skill was influenced by the [sf-skills](https://github.com/Jaganpro/sf-skills) repository.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Special Thanks
|
|
25
|
+
|
|
26
|
+
To the entire Salesforce developer community for sharing knowledge, writing blogs, creating open-source tools, and helping each other build better solutions.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
*If we've missed anyone whose work influenced these skills, please let us know so we can add proper attribution.*
|
|
@@ -1,108 +1,199 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: generating-apex-test
|
|
3
|
-
description: Apex test
|
|
3
|
+
description: Generate and validate Apex test classes with TestDataFactory patterns, bulk testing (251+ records), mocking strategies, assertion best practices, and disciplined test-fix loops. Use this skill when creating new Apex test classes, improving test coverage, debugging and fixing failing Apex tests, running test execution and coverage analysis, or implementing testing patterns for triggers, services, controllers, batch jobs, queueables, and integrations. Triggers on *Test.cls, *_Test.cls files, sf apex run test workflows, coverage reports, test-fix loops. Do NOT trigger for production Apex code (use generating-apex) or Jest/LWC tests.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Apex
|
|
6
|
+
# Generating Apex Tests
|
|
7
|
+
|
|
8
|
+
Generate production-ready Apex test classes and run disciplined test-fix loops with coverage analysis.
|
|
7
9
|
|
|
8
10
|
## Core Principles
|
|
9
11
|
|
|
10
|
-
1. **
|
|
11
|
-
2. **
|
|
12
|
-
3. **
|
|
13
|
-
4. **
|
|
14
|
-
5. **
|
|
12
|
+
1. **One behavior per method** — each test method validates a single scenario. Separate positive, negative, and bulk tests. NEVER combine related-but-distinct inputs (e.g., null and empty) in one method — create `_NullInput_` and `_EmptyInput_` as separate test methods
|
|
13
|
+
2. **Bulkify tests** — test with 251+ records to cross the 200-record trigger batch boundary. **Batch Apex exception:** in test context only one `execute()` invocation runs, so set `batchSize >= testRecordCount`. See [references/async-testing.md](references/async-testing.md)
|
|
14
|
+
3. **Isolate test data** — every `@TestSetup` must delegate record creation to a `TestDataFactory` class. If none exists, create one first. Never build record lists inline in `@TestSetup`. Never rely on org data (`SeeAllData=false`) or hardcoded IDs. For duplicate rule handling, see [references/test-data-factory.md](references/test-data-factory.md)
|
|
15
|
+
4. **Assert meaningfully** — use exact expected values computed from test data setup. NEVER use range assertions or approximate counts when the value is deterministic. Always include failure messages. See [references/assertion-patterns.md](references/assertion-patterns.md)
|
|
16
|
+
5. **Use `Assert` class only** — `Assert.areEqual`, `Assert.isTrue`, `Assert.fail`, etc. Never use legacy `System.assert`, `System.assertEquals`, or `System.assertNotEquals`
|
|
17
|
+
6. **Mock external boundaries** — use `HttpCalloutMock` for callouts, `Test.setFixedSearchResults` for SOSL, DML mock classes for database isolation. Design for testability via constructor injection. See [references/mocking-patterns.md](references/mocking-patterns.md)
|
|
18
|
+
7. **Test negative paths** — validate error handling and exception scenarios, not just happy paths
|
|
19
|
+
8. **Wrap with start/stop** — pair `Test.startTest()` with `Test.stopTest()` to reset governor limits and force async execution
|
|
20
|
+
|
|
21
|
+
## Test.startTest() / Test.stopTest()
|
|
22
|
+
|
|
23
|
+
Always wrap the code under test in `Test.startTest()` / `Test.stopTest()`:
|
|
24
|
+
|
|
25
|
+
- Resets governor limits so the test measures only the code under test
|
|
26
|
+
- Executes async operations synchronously (queueables, batch, future methods)
|
|
27
|
+
- Fires scheduled jobs immediately
|
|
28
|
+
|
|
29
|
+
## Test Code Anti-Patterns
|
|
30
|
+
|
|
31
|
+
| Anti-Pattern | Fix |
|
|
32
|
+
|---|---|
|
|
33
|
+
| SOQL/DML inside loops | Query once before the loop; use `Map<Id, SObject>` for lookups |
|
|
34
|
+
| Magic numbers in assertions | Derive expected values from setup constants |
|
|
35
|
+
| God test class (>500 lines) | Split into multiple test classes by behavior area |
|
|
36
|
+
| Long test methods (>30 lines) | Extract Given/When/Then into helper methods |
|
|
37
|
+
| Generic `Exception` catch | Catch the specific expected type (e.g., `DmlException`) |
|
|
38
|
+
|
|
39
|
+
## Workflow
|
|
40
|
+
|
|
41
|
+
### Step 1 — Gather Context
|
|
42
|
+
|
|
43
|
+
Before generating or fixing tests, identify:
|
|
44
|
+
|
|
45
|
+
- the target production class(es) under test
|
|
46
|
+
- existing test classes, test data factories, and setup helpers
|
|
47
|
+
- desired test scope (single class, specific methods, suite, or local tests)
|
|
48
|
+
- coverage threshold (75% minimum for deploy, 90%+ recommended)
|
|
49
|
+
- org alias when running tests against an org
|
|
15
50
|
|
|
16
|
-
|
|
51
|
+
### Step 2 — Generate the Test Class
|
|
52
|
+
|
|
53
|
+
Apply the structure, naming conventions, and patterns from the asset templates and reference docs.
|
|
54
|
+
|
|
55
|
+
**MANDATORY — File Deliverables:** For every test class, create BOTH files:
|
|
56
|
+
1. `{ClassName}Test.cls` — the test class (use [assets/test-class-template.cls](assets/test-class-template.cls) as starting point)
|
|
57
|
+
2. `{ClassName}Test.cls-meta.xml` — the metadata file:
|
|
58
|
+
|
|
59
|
+
```xml
|
|
60
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
61
|
+
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
62
|
+
<apiVersion>66.0</apiVersion>
|
|
63
|
+
<status>Active</status>
|
|
64
|
+
</ApexClass>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
If no `TestDataFactory` exists in the project, create `TestDataFactory.cls` + `TestDataFactory.cls-meta.xml` using [assets/test-data-factory-template.cls](assets/test-data-factory-template.cls).
|
|
68
|
+
|
|
69
|
+
#### @TestSetup Example
|
|
70
|
+
|
|
71
|
+
```apex
|
|
72
|
+
@TestSetup
|
|
73
|
+
static void setupTestData() {
|
|
74
|
+
List<Account> accounts = TestDataFactory.createAccounts(251, true);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### Test Method Structure
|
|
79
|
+
|
|
80
|
+
Use Given/When/Then:
|
|
17
81
|
|
|
18
82
|
```apex
|
|
19
83
|
@isTest
|
|
20
|
-
|
|
84
|
+
static void shouldUpdateStatus_WhenValidInput() {
|
|
85
|
+
// Given
|
|
86
|
+
List<Account> accounts = [SELECT Id FROM Account];
|
|
87
|
+
|
|
88
|
+
// When
|
|
89
|
+
Test.startTest();
|
|
90
|
+
MyService.processAccounts(accounts);
|
|
91
|
+
Test.stopTest();
|
|
92
|
+
|
|
93
|
+
// Then
|
|
94
|
+
List<Account> updated = [SELECT Id, Status__c FROM Account];
|
|
95
|
+
Assert.areEqual(251, updated.size(), 'All accounts should be processed');
|
|
96
|
+
}
|
|
97
|
+
```
|
|
21
98
|
|
|
22
|
-
|
|
23
|
-
static void setupTestData() {
|
|
24
|
-
// Create shared test data using TestDataFactory
|
|
25
|
-
List<Account> accounts = TestDataFactory.createAccounts(200, true);
|
|
26
|
-
}
|
|
99
|
+
#### Negative Test — Exception Pattern
|
|
27
100
|
|
|
28
|
-
|
|
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.assertEquals(200, updated.size(), 'All accounts should be processed');
|
|
41
|
-
for (Account acc : updated) {
|
|
42
|
-
System.assertEquals('Processed', acc.Status__c, 'Status should be updated');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
101
|
+
Use try/catch with `Assert.fail` to verify expected exceptions:
|
|
45
102
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Test.stopTest();
|
|
103
|
+
```apex
|
|
104
|
+
@isTest
|
|
105
|
+
static void shouldThrowException_WhenInvalidInput() {
|
|
106
|
+
// Given
|
|
107
|
+
List<Account> emptyList = new List<Account>();
|
|
108
|
+
|
|
109
|
+
// When/Then
|
|
110
|
+
Test.startTest();
|
|
111
|
+
try {
|
|
112
|
+
MyService.processAccounts(emptyList);
|
|
113
|
+
Assert.fail('Expected MyCustomException to be thrown');
|
|
114
|
+
} catch (MyCustomException e) {
|
|
115
|
+
Assert.isTrue(e.getMessage().contains('cannot be empty'),
|
|
116
|
+
'Exception message should indicate empty input');
|
|
61
117
|
}
|
|
118
|
+
Test.stopTest();
|
|
62
119
|
}
|
|
63
120
|
```
|
|
64
121
|
|
|
65
|
-
|
|
122
|
+
#### Naming Convention
|
|
66
123
|
|
|
67
|
-
|
|
124
|
+
- `should[ExpectedResult]_When[Scenario]`: `shouldSendNotification_WhenOpportunityClosedWon`
|
|
125
|
+
- `[SubjectOrAction]_[Scenario]_[ExpectedResult]`: `AccountUpdate_ChangeName_Success`
|
|
68
126
|
|
|
69
|
-
|
|
70
|
-
- `shouldCreateContact_WhenAccountIsActive`
|
|
71
|
-
- `shouldThrowException_WhenEmailIsInvalid`
|
|
72
|
-
- `shouldSendNotification_WhenOpportunityClosedWon`
|
|
73
|
-
- `shouldBypassTrigger_WhenRunningAsBatch`
|
|
127
|
+
### Step 3 — Run Tests
|
|
74
128
|
|
|
75
|
-
|
|
129
|
+
Start narrow when debugging; widen after the fix is stable.
|
|
76
130
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
- Fires scheduled jobs immediately
|
|
131
|
+
```bash
|
|
132
|
+
# Single test class
|
|
133
|
+
sf apex run test --class-names MyServiceTest --result-format human --code-coverage --target-org <alias>
|
|
81
134
|
|
|
82
|
-
|
|
135
|
+
# Specific test methods
|
|
136
|
+
sf apex run test --tests MyServiceTest.shouldUpdateStatus_WhenValidInput --result-format human --target-org <alias>
|
|
83
137
|
|
|
84
|
-
|
|
138
|
+
# All local tests
|
|
139
|
+
sf apex run test --test-level RunLocalTests --result-format human --code-coverage --target-org <alias>
|
|
140
|
+
```
|
|
85
141
|
|
|
86
|
-
|
|
87
|
-
- **[assets/test-data-factory-template.cls](assets/test-data-factory-template.cls)** - TestDataFactory with Account, Contact, Opportunity, User factories and field override support
|
|
142
|
+
### Step 4 — Analyze Results
|
|
88
143
|
|
|
89
|
-
|
|
144
|
+
Focus on:
|
|
90
145
|
|
|
91
|
-
|
|
146
|
+
- failing methods — exception types and stack traces
|
|
147
|
+
- uncovered lines and weak coverage areas
|
|
148
|
+
- whether failures indicate bad test data, brittle assertions, or broken production logic
|
|
92
149
|
|
|
93
|
-
|
|
94
|
-
- **[references/assertion-patterns.md](references/assertion-patterns.md)** - Assertion best practices and common pitfalls
|
|
95
|
-
- **[references/mocking-patterns.md](references/mocking-patterns.md)** - HttpCalloutMock, Test.setMock(), stubbing
|
|
96
|
-
- **[references/async-testing.md](references/async-testing.md)** - Batch, Queueable, Future, Scheduled job testing
|
|
150
|
+
### Step 5 — Fix Loop
|
|
97
151
|
|
|
98
|
-
|
|
152
|
+
When tests fail, run a disciplined fix loop (max 3 iterations — stop and surface root cause if still failing):
|
|
153
|
+
|
|
154
|
+
1. Read the failing test class and the class under test
|
|
155
|
+
2. Identify root cause from error messages and stack traces
|
|
156
|
+
3. Apply fix — adjust test data or assertions for test-side issues; delegate production code issues to the `generating-apex` skill
|
|
157
|
+
4. Rerun the focused test before broader regression
|
|
158
|
+
5. Repeat until all tests pass, iteration limit reached, or root cause requires design change
|
|
159
|
+
|
|
160
|
+
### Step 6 — Validate Coverage
|
|
161
|
+
|
|
162
|
+
| Level | Coverage | Purpose |
|
|
163
|
+
|-------|----------|---------|
|
|
164
|
+
| Production deploy | 75% minimum | Required by Salesforce |
|
|
165
|
+
| Recommended | 90%+ | Best practice target |
|
|
166
|
+
| Critical paths | 100% | Business-critical code |
|
|
167
|
+
|
|
168
|
+
Cover all paths: positive, negative/exception, bulk (251+ records), callout/async.
|
|
169
|
+
|
|
170
|
+
## What to Test by Component
|
|
99
171
|
|
|
100
172
|
| Component | Key Test Scenarios |
|
|
101
173
|
|-----------|-------------------|
|
|
102
|
-
| Trigger | Bulk insert/update/delete, recursion, field
|
|
103
|
-
| Service | Valid/invalid inputs, bulk operations,
|
|
174
|
+
| Trigger | Bulk insert/update/delete, recursion guard, field change detection |
|
|
175
|
+
| Service | Valid/invalid inputs, bulk operations, exception handling |
|
|
104
176
|
| Controller | Page load, action methods, view state |
|
|
105
|
-
| Batch |
|
|
106
|
-
| Queueable | Chaining, bulkification, error handling |
|
|
177
|
+
| Batch | start/execute/finish, scope matching (batch size >= record count), `Database.Stateful` tracking, error handling, chaining (separate methods — `finish()` calling `Database.executeBatch()` throws `UnexpectedException`) |
|
|
178
|
+
| Queueable | Chaining (only first job runs in tests), bulkification, error handling, callout mocks before `Test.startTest()` |
|
|
107
179
|
| Callout | Success response, error response, timeout |
|
|
108
|
-
|
|
|
180
|
+
| Selector | Valid/null/empty inputs, bulk (251+), field population, sort order, `WITH USER_MODE` via `System.runAs` |
|
|
181
|
+
| Scheduled | Direct execution via `execute(null)`, CRON registration via `CronTrigger` query |
|
|
182
|
+
| Platform Event | `Test.enableChangeDataCapture()`, `Test.getEventBus().deliver()`, verify subscriber side effects |
|
|
183
|
+
|
|
184
|
+
## Output Expectations
|
|
185
|
+
|
|
186
|
+
Deliverables per test class:
|
|
187
|
+
- `{ClassName}Test.cls` + `{ClassName}Test.cls-meta.xml` (match API version of class under test; default `66.0`)
|
|
188
|
+
- `TestDataFactory.cls` + `TestDataFactory.cls-meta.xml` (if not already present)
|
|
189
|
+
|
|
190
|
+
## Reference Files
|
|
191
|
+
|
|
192
|
+
Load on demand for detailed patterns:
|
|
193
|
+
|
|
194
|
+
| Reference | When to use |
|
|
195
|
+
|-----------|-------------|
|
|
196
|
+
| [references/test-data-factory.md](references/test-data-factory.md) | TestDataFactory patterns, field overrides, duplicate rule handling |
|
|
197
|
+
| [references/assertion-patterns.md](references/assertion-patterns.md) | Assertion best practices, anti-patterns, common pitfalls |
|
|
198
|
+
| [references/mocking-patterns.md](references/mocking-patterns.md) | HttpCalloutMock, DML mocking, StubProvider, SOSL, Email, Platform Events |
|
|
199
|
+
| [references/async-testing.md](references/async-testing.md) | Batch, Queueable, Future, Scheduled job testing |
|
|
@@ -1,51 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* @author Generated by Apex Test Writer Skill
|
|
2
|
+
* Test class for {ClassUnderTest}.
|
|
3
|
+
* Tests bulk operations (251+ records), positive/negative paths,
|
|
4
|
+
* and exception handling.
|
|
6
5
|
*/
|
|
7
6
|
@isTest
|
|
8
7
|
private class {ClassUnderTest}Test {
|
|
9
8
|
|
|
10
|
-
// ─── Test Setup ───────────────────────────────────────────────────────
|
|
11
|
-
|
|
12
9
|
@TestSetup
|
|
13
10
|
static void setupTestData() {
|
|
14
|
-
|
|
15
|
-
// List<Account> accounts = TestDataFactory.createAccounts(200, true);
|
|
11
|
+
List<Account> accounts = TestDataFactory.createAccounts(251, true);
|
|
16
12
|
}
|
|
17
13
|
|
|
18
14
|
// ─── Positive Tests ───────────────────────────────────────────────────
|
|
19
15
|
|
|
20
16
|
@isTest
|
|
21
17
|
static void shouldPerformExpectedBehavior_WhenValidInput() {
|
|
22
|
-
// Given
|
|
23
|
-
|
|
18
|
+
// Given
|
|
19
|
+
List<Account> accounts = [SELECT Id, Name FROM Account];
|
|
24
20
|
|
|
25
|
-
// When
|
|
21
|
+
// When
|
|
26
22
|
Test.startTest();
|
|
27
23
|
// {ClassUnderTest}.methodUnderTest(params);
|
|
28
24
|
Test.stopTest();
|
|
29
25
|
|
|
30
|
-
// Then
|
|
31
|
-
//
|
|
26
|
+
// Then
|
|
27
|
+
// Assert.areEqual(expected, actual, 'Descriptive failure message');
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
@isTest
|
|
35
|
-
static void
|
|
36
|
-
// Given
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
static void shouldHandleBulkRecords_WhenProcessing251() {
|
|
32
|
+
// Given
|
|
33
|
+
List<Account> accounts = [SELECT Id FROM Account];
|
|
34
|
+
Assert.areEqual(251, accounts.size(), 'Should have 251 test records');
|
|
39
35
|
|
|
40
36
|
// When
|
|
41
37
|
Test.startTest();
|
|
42
38
|
// {ClassUnderTest}.bulkMethod(accounts);
|
|
43
39
|
Test.stopTest();
|
|
44
40
|
|
|
45
|
-
// Then
|
|
41
|
+
// Then
|
|
46
42
|
// List<Account> results = [SELECT Id, Status__c FROM Account];
|
|
47
43
|
// for (Account acc : results) {
|
|
48
|
-
//
|
|
44
|
+
// Assert.areEqual('Processed', acc.Status__c, 'All records should be processed');
|
|
49
45
|
// }
|
|
50
46
|
}
|
|
51
47
|
|
|
@@ -53,21 +49,15 @@ private class {ClassUnderTest}Test {
|
|
|
53
49
|
|
|
54
50
|
@isTest
|
|
55
51
|
static void shouldThrowException_WhenNullInput() {
|
|
56
|
-
Boolean exceptionThrown = false;
|
|
57
|
-
String exceptionMessage = '';
|
|
58
|
-
|
|
59
52
|
Test.startTest();
|
|
60
53
|
try {
|
|
61
54
|
// {ClassUnderTest}.methodUnderTest(null);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
Assert.fail('Expected exception for null input');
|
|
56
|
+
} catch (MyCustomException e) {
|
|
57
|
+
Assert.isTrue(e.getMessage().contains('cannot be null'),
|
|
58
|
+
'Exception message should mention null input');
|
|
65
59
|
}
|
|
66
60
|
Test.stopTest();
|
|
67
|
-
|
|
68
|
-
System.assert(exceptionThrown, 'Exception should be thrown for null input');
|
|
69
|
-
System.assert(exceptionMessage.contains('cannot be null'),
|
|
70
|
-
'Exception message should mention null input');
|
|
71
61
|
}
|
|
72
62
|
|
|
73
63
|
@isTest
|
|
@@ -76,16 +66,16 @@ private class {ClassUnderTest}Test {
|
|
|
76
66
|
// List<SObject> results = {ClassUnderTest}.methodUnderTest(new List<Id>());
|
|
77
67
|
Test.stopTest();
|
|
78
68
|
|
|
79
|
-
//
|
|
69
|
+
// Assert.isTrue(results.isEmpty(), 'Should return empty list for empty input');
|
|
80
70
|
}
|
|
81
71
|
|
|
82
72
|
// ─── Edge Case Tests ──────────────────────────────────────────────────
|
|
83
73
|
|
|
84
74
|
@isTest
|
|
85
75
|
static void shouldHandleMixedRecords_WhenSomeQualify() {
|
|
86
|
-
// Given
|
|
87
|
-
|
|
88
|
-
|
|
76
|
+
// Given
|
|
77
|
+
List<Account> accounts = [SELECT Id, Status__c FROM Account];
|
|
78
|
+
Integer half = accounts.size() / 2;
|
|
89
79
|
// for (Integer i = 0; i < half; i++) {
|
|
90
80
|
// accounts[i].Status__c = 'Qualifying';
|
|
91
81
|
// }
|
|
@@ -96,29 +86,8 @@ private class {ClassUnderTest}Test {
|
|
|
96
86
|
// {ClassUnderTest}.conditionalMethod(accounts);
|
|
97
87
|
Test.stopTest();
|
|
98
88
|
|
|
99
|
-
// Then
|
|
89
|
+
// Then
|
|
100
90
|
// List<Account> qualifying = [SELECT Id FROM Account WHERE Processed__c = true];
|
|
101
|
-
//
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ─── Governor Limit Tests ─────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
@isTest
|
|
107
|
-
static void shouldNotExceedGovernorLimits_WhenBulkProcessing() {
|
|
108
|
-
// Given
|
|
109
|
-
// List<Account> accounts = [SELECT Id FROM Account];
|
|
110
|
-
|
|
111
|
-
Test.startTest();
|
|
112
|
-
// {ClassUnderTest}.heavyMethod(accounts);
|
|
113
|
-
Test.stopTest();
|
|
114
|
-
|
|
115
|
-
System.assert(Limits.getDmlStatements() < Limits.getLimitDmlStatements(),
|
|
116
|
-
'Should not exceed DML statement limit');
|
|
117
|
-
System.assert(Limits.getQueries() < Limits.getLimitQueries(),
|
|
118
|
-
'Should not exceed SOQL query limit');
|
|
91
|
+
// Assert.areEqual(half, qualifying.size(), 'Only qualifying records should be processed');
|
|
119
92
|
}
|
|
120
|
-
|
|
121
|
-
// ─── Helper Methods ───────────────────────────────────────────────────
|
|
122
|
-
|
|
123
|
-
// Add test-specific helper methods here
|
|
124
93
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* @description Centralized factory for creating test data with sensible defaults.
|
|
3
3
|
* All methods accept a doInsert flag for flexibility.
|
|
4
4
|
* Bulk methods create multiple records; single-record methods delegate to bulk.
|
|
5
|
-
* @author Generated by Apex Test Writer Skill
|
|
6
5
|
*/
|
|
7
6
|
@isTest
|
|
8
7
|
public class TestDataFactory {
|