@W-21623314 Adding Apex skill and Apex testing skill

This commit is contained in:
Ayush Gupta 2026-03-26 22:57:29 +05:30
parent 3f5b5f0baf
commit 17621bb6d1
18 changed files with 135 additions and 112 deletions

View File

@ -199,3 +199,7 @@ A maintainer will merge your PR after approval.
- Open a PR.
- Respond to feedback.
- Keep your fork updated.
> **Note:** Our rules forbid specific mentions of community members or
> endorsements of sellable materials like training courses, books, etc.
> Please ensure your contributions do not include these.

28
skills/CREDITS.md Normal file
View File

@ -0,0 +1,28 @@
# Credits & Acknowledgments
The skills in this library were 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 these skills.
---
## Authors & Contributors
### Jag Valaiyapathy (**[Jaganpro)](https://github.com/Jaganpro)**
**[sf-skills](https://github.com/Jaganpro/sf-skills)**
Key contributions:
- Pioneering open-source Salesforce skills for agentic coding tools
- Apex code generation and review patterns
- Best practices, anti-patterns, and design patterns reference material
- Template library for common Apex class types
---
## Special Thanks
To the entire Salesforce developer community for sharing knowledge, writing blogs, creating open-source tools, and helping each other build better solutions.
---
*If we've missed anyone whose work influenced these skills, please let us know so we can add proper attribution.*

View File

@ -37,6 +37,22 @@ Before generating or fixing tests, identify:
Apply the structure, naming conventions, and patterns below. Reference the appropriate asset templates and reference docs for the component type.
**MANDATORY — File Deliverables:** For every test class you generate, you MUST create BOTH files:
1. `{ClassName}Test.cls` — the test class source
2. `{ClassName}Test.cls-meta.xml` — the accompanying metadata file (use the meta XML template below)
Never create the `.cls` without its `.cls-meta.xml`. Both files are required for deployment.
Meta XML template for test classes:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>66.0</apiVersion>
<status>Active</status>
</ApexClass>
```
#### Test Class Structure
```apex
@ -88,12 +104,17 @@ private class MyServiceTest {
#### Naming Convention
Use descriptive method names: `test[SubjectOrAction]_[Scenario]_[ExpectedResult]`
Use descriptive method names following one of these patterns:
- `testAccountUpdate_ChangeName_Success`
- `testEmailValidation_InvalidFormat_ThrowsException`
- `testOpportunity_ClosedWon_SendsNotification`
- `testBatchExecution_RunningAsBatch_TriggerBypassed`
- `[SubjectOrAction]_[Scenario]_[ExpectedResult]`: `AccountUpdate_ChangeName_Success`
- `should[ExpectedResult]_When[Scenario]`: `shouldSendNotification_WhenOpportunityClosedWon`
Examples:
- `AccountUpdate_ChangeName_Success`
- `EmailValidation_InvalidFormat_ThrowsException`
- `shouldSendNotification_WhenOpportunityClosedWon`
- `BatchExecution_RunningAsBatch_TriggerBypassed`
### Step 3 — Run the Smallest Useful Test Set
@ -144,6 +165,13 @@ Cover all paths: positive, negative/exception, bulk (251+ records), callout/asyn
| Selector | Query results for valid/null/empty inputs, bulk (251+), field population, sort order verification, `WITH USER_MODE` enforcement via restricted-permission user (`System.runAs`) |
| Scheduled | Execution, CRON validation |
## Output Expectations
Deliverables per test class:
- `{ClassName}Test.cls`
- `{ClassName}Test.cls-meta.xml` (MANDATORY — match the API version of the class under test; default `66.0`)
- `TestDataFactory.cls` + `TestDataFactory.cls-meta.xml` (if not already present in the project)
## Output Format
When reporting test results, use this structure:

View File

@ -26,7 +26,7 @@ private class {{ClassName}}Test {
* Tests successful {{methodUnderTest}} with valid input
*/
@IsTest
static void test{{MethodUnderTest}}_ValidInput_Success() {
static void {{MethodUnderTest}}_ValidInput_Success() {
// ─────────────────────────────────────────────────────────────────────
// GIVEN - Set up test conditions
// ─────────────────────────────────────────────────────────────────────
@ -56,7 +56,7 @@ private class {{ClassName}}Test {
* Tests {{methodUnderTest}} throws exception with null input
*/
@IsTest
static void test{{MethodUnderTest}}_NullInput_ThrowsException() {
static void {{MethodUnderTest}}_NullInput_ThrowsException() {
// ─────────────────────────────────────────────────────────────────────
// GIVEN - Null input
// ─────────────────────────────────────────────────────────────────────
@ -83,7 +83,7 @@ private class {{ClassName}}Test {
* Tests {{methodUnderTest}} handles invalid data gracefully
*/
@IsTest
static void test{{MethodUnderTest}}_InvalidData_ReturnsError() {
static void {{MethodUnderTest}}_InvalidData_ReturnsError() {
// ─────────────────────────────────────────────────────────────────────
// GIVEN - Invalid input data
// ─────────────────────────────────────────────────────────────────────
@ -113,7 +113,7 @@ private class {{ClassName}}Test {
* 251 records crosses the 200-record trigger batch boundary
*/
@IsTest
static void test{{MethodUnderTest}}_BulkOperation_Success() {
static void {{MethodUnderTest}}_BulkOperation_Success() {
// ─────────────────────────────────────────────────────────────────────
// GIVEN - 251 records (crosses 200-record batch boundary)
// ─────────────────────────────────────────────────────────────────────
@ -147,7 +147,7 @@ private class {{ClassName}}Test {
* Tests {{methodUnderTest}} with empty list input
*/
@IsTest
static void test{{MethodUnderTest}}_EmptyList_NoError() {
static void {{MethodUnderTest}}_EmptyList_NoError() {
// ─────────────────────────────────────────────────────────────────────
// GIVEN - Empty list
// ─────────────────────────────────────────────────────────────────────

View File

@ -20,7 +20,7 @@ private class {{ClassName}}BulkTest {
* Verifies trigger handles multiple batch chunks correctly
*/
@IsTest
static void testBulkInsert_251Records_AllProcessed() {
static void BulkInsert_251Records_AllProcessed() {
// GIVEN
List<{{ObjectName}}> records = TestDataFactory.create{{ObjectName}}s(BULK_SIZE);
@ -53,7 +53,7 @@ private class {{ClassName}}BulkTest {
* Tests bulk insert at exact batch boundary (200 records)
*/
@IsTest
static void testBulkInsert_ExactBatchSize_AllProcessed() {
static void BulkInsert_ExactBatchSize_AllProcessed() {
// GIVEN
List<{{ObjectName}}> records = TestDataFactory.create{{ObjectName}}s(SINGLE_BATCH);
@ -78,7 +78,7 @@ private class {{ClassName}}BulkTest {
* Verifies update triggers handle multiple batches
*/
@IsTest
static void testBulkUpdate_251Records_AllUpdated() {
static void BulkUpdate_251Records_AllUpdated() {
// GIVEN - Insert records first
List<{{ObjectName}}> records = TestDataFactory.create{{ObjectName}}s(BULK_SIZE);
insert records;
@ -116,7 +116,7 @@ private class {{ClassName}}BulkTest {
* Verifies delete triggers handle cleanup correctly
*/
@IsTest
static void testBulkDelete_251Records_AllDeleted() {
static void BulkDelete_251Records_AllDeleted() {
// GIVEN
List<{{ObjectName}}> records = TestDataFactory.create{{ObjectName}}s(BULK_SIZE);
insert records;
@ -150,7 +150,7 @@ private class {{ClassName}}BulkTest {
* Verifies partial success scenarios are handled
*/
@IsTest
static void testBulkMixedOperations_PartialSuccess() {
static void BulkMixedOperations_PartialSuccess() {
// GIVEN - Some valid, some invalid records
List<{{ObjectName}}> validRecords = TestDataFactory.create{{ObjectName}}s(200);
List<{{ObjectName}}> invalidRecords = new List<{{ObjectName}}>();
@ -199,23 +199,23 @@ private class {{ClassName}}BulkTest {
private static void assertGovernorLimitsNotExceeded() {
// SOQL Queries (limit: 100)
Assert.isTrue(Limits.getQueries() < 90,
'SOQL queries approaching limit: ' + Limits.getQueries() + '/100');
'SOQL queries approaching limit: ' + Limits.getQueries() + '/' + Limits.getLimitQueries());
// DML Statements (limit: 150)
Assert.isTrue(Limits.getDmlStatements() < 140,
'DML statements approaching limit: ' + Limits.getDmlStatements() + '/150');
'DML statements approaching limit: ' + Limits.getDmlStatements() + '/' + Limits.getLimitDMLStatements());
// DML Rows (limit: 10,000)
Assert.isTrue(Limits.getDmlRows() < 9500,
'DML rows approaching limit: ' + Limits.getDmlRows() + '/10000');
'DML rows approaching limit: ' + Limits.getDmlRows() + '/' + Limits.getLimitDMLRows());
// Heap Size (limit: 6MB sync, 12MB async)
Assert.isTrue(Limits.getHeapSize() < 5000000,
'Heap size approaching limit: ' + Limits.getHeapSize() + '/6000000');
'Heap size approaching limit: ' + Limits.getHeapSize() + '/' + Limits.getLimitHeapSize());
// CPU Time (limit: 10,000ms sync, 60,000ms async)
Assert.isTrue(Limits.getCpuTime() < 9000,
'CPU time approaching limit: ' + Limits.getCpuTime() + '/10000');
'CPU time approaching limit: ' + Limits.getCpuTime() + '/' + Limits.getLimitCpuTime());
System.debug('═══════════════════════════════════════════════════════');
System.debug('GOVERNOR LIMIT USAGE:');
@ -236,7 +236,7 @@ private class {{ClassName}}BulkTest {
* Use sparingly - consumes significant test execution time
*/
@IsTest
static void testStressTest_501Records_AllProcessed() {
static void StressTest_501Records_AllProcessed() {
// GIVEN
List<{{ObjectName}}> records = TestDataFactory.create{{ObjectName}}s(LARGE_BULK_SIZE);

View File

@ -3,8 +3,6 @@
*
* Enables true unit testing by replacing database operations with in-memory tracking.
* Tests using this pattern run ~35x faster than tests with actual DML.
*
* @see https://www.jamessimone.net/blog/joys-of-apex/mocking-dml/
*/
// ═══════════════════════════════════════════════════════════════════════════
@ -298,7 +296,7 @@ public class AccountService {
private class AccountServiceTest {
@IsTest
static void testCreateAccount_Success() {
static void CreateAccount_ValidInput_Success() {
// Arrange
DMLMock.reset();
AccountService service = new AccountService(new DMLMock());
@ -320,7 +318,7 @@ private class AccountServiceTest {
}
@IsTest
static void testCreateAccount_NullInput_ThrowsException() {
static void CreateAccount_NullInput_ThrowsException() {
// Arrange
DMLMock.reset();
AccountService service = new AccountService(new DMLMock());

View File

@ -98,7 +98,7 @@ private class {{ClassName}}CalloutTest {
* Tests successful API call returns expected data
*/
@IsTest
static void testCallout_Success_ReturnsData() {
static void Callout_Success_ReturnsData() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new SuccessMock());
@ -124,7 +124,7 @@ private class {{ClassName}}CalloutTest {
* Tests POST request with body
*/
@IsTest
static void testCallout_PostWithBody_Success() {
static void Callout_PostWithBody_Success() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new SuccessMock());
@ -155,7 +155,7 @@ private class {{ClassName}}CalloutTest {
* Tests handling of 400 Bad Request
*/
@IsTest
static void testCallout_BadRequest_HandlesGracefully() {
static void Callout_BadRequest_HandlesGracefully() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new ErrorMock(400, 'Invalid request parameters'));
@ -179,7 +179,7 @@ private class {{ClassName}}CalloutTest {
* Tests handling of 500 Internal Server Error
*/
@IsTest
static void testCallout_ServerError_HandlesGracefully() {
static void Callout_ServerError_HandlesGracefully() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new ErrorMock(500, 'Internal server error'));
@ -199,7 +199,7 @@ private class {{ClassName}}CalloutTest {
* Tests handling of timeout exception
*/
@IsTest
static void testCallout_Timeout_ThrowsException() {
static void Callout_Timeout_ThrowsException() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new TimeoutMock());
@ -222,7 +222,7 @@ private class {{ClassName}}CalloutTest {
* Tests handling of 401 Unauthorized
*/
@IsTest
static void testCallout_Unauthorized_HandlesGracefully() {
static void Callout_Unauthorized_HandlesGracefully() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new ErrorMock(401, 'Invalid or expired token'));
@ -246,7 +246,7 @@ private class {{ClassName}}CalloutTest {
* Verifies correct endpoint and method are used
*/
@IsTest
static void testCallout_ValidatesRequestParameters() {
static void Callout_ValidatesRequestParameters() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new ValidatingMock('/api/v1/resource', 'POST'));
@ -272,7 +272,7 @@ private class {{ClassName}}CalloutTest {
* Note: Must use Test.startTest/stopTest to execute @future
*/
@IsTest
static void testFutureCallout_ExecutesSuccessfully() {
static void FutureCallout_ExecutesSuccessfully() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new SuccessMock());
@ -290,7 +290,7 @@ private class {{ClassName}}CalloutTest {
* Tests Queueable with callout
*/
@IsTest
static void testQueueableCallout_ExecutesSuccessfully() {
static void QueueableCallout_ExecutesSuccessfully() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new SuccessMock());
@ -327,7 +327,7 @@ private class {{ClassName}}CalloutTest {
* Tests multiple callouts in sequence (limit: 100 per transaction)
*/
@IsTest
static void testMultipleCallouts_AllSucceed() {
static void MultipleCallouts_AllSucceed() {
// GIVEN
Test.setMock(HttpCalloutMock.class, new MultiCalloutMock());

View File

@ -3,9 +3,6 @@
*
* The Stub API enables dynamic mocking of interfaces and virtual classes.
* Use when you need conditional logic or method tracking in your mocks.
*
* @see https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_interface_System_StubProvider.htm
* @see https://blog.beyondthecloud.dev/blog/salesforce-mock-in-apex-tests
*/
// ═══════════════════════════════════════════════════════════════════════════
@ -115,7 +112,7 @@ public class AccountServiceStub implements System.StubProvider {
private class AccountServiceStubTest {
@IsTest
static void testWithConfiguredResponse() {
static void ConfiguredResponse_ReturnsExpectedValues() {
// Arrange - Configure stub responses
AccountServiceStub stub = new AccountServiceStub()
.whenCalled('getAccountCount', 42)
@ -144,7 +141,7 @@ private class AccountServiceStubTest {
}
@IsTest
static void testDefaultResponses() {
static void DefaultResponse_ReturnsDefaults() {
// Arrange - No configured responses
AccountServiceStub stub = new AccountServiceStub();
IAccountService service = (IAccountService) Test.createStub(
@ -241,7 +238,7 @@ public class ErrorStub implements System.StubProvider {
private class ErrorStubTest {
@IsTest
static void testErrorHandling() {
static void ErrorHandling_ThrowsConfiguredException() {
// Arrange
ErrorStub stub = new ErrorStub().failOn('getAccount', 'Account not found');
IAccountService service = (IAccountService) Test.createStub(
@ -265,8 +262,6 @@ private class ErrorStubTest {
/**
* Highly flexible stub that can mock any interface
*
* Inspired by: Suraj Pillai's UniversalMock pattern
*/
@IsTest
public class UniversalMock implements System.StubProvider {

View File

@ -37,8 +37,8 @@ sf apex run test \
```bash
sf apex run test \
--tests AccountServiceTest.testCreate \
--tests AccountServiceTest.testUpdate \
--tests AccountServiceTest.CreateAccount_Success \
--tests AccountServiceTest.UpdateAccount_Success \
--target-org my-sandbox
```
@ -213,7 +213,7 @@ sf apex run test \
```bash
sf apex run test \
--tests AccountServiceTest.testCreate \
--tests AccountServiceTest.CreateAccount_Success \
--target-org my-sandbox
```

View File

@ -2,8 +2,6 @@
This guide covers mocking and stubbing patterns that enable true unit testing in Apex. By replacing database operations and external services with mock implementations, you can write fast, isolated, reliable tests.
> **Sources**: [Beyond the Cloud](https://blog.beyondthecloud.dev/blog/salesforce-mock-in-apex-tests), [James Simone](https://www.jamessimone.net/blog/joys-of-apex/mocking-dml/), [Trailhead](https://trailhead.salesforce.com/content/learn/modules/unit-testing-on-the-lightning-platform/mock-stub-objects)
---
## Mocking vs Stubbing
@ -28,8 +26,6 @@ Salesforce **requires** mock callouts - you cannot make real HTTP requests in te
```apex
/**
* Simple HTTP mock returning a fixed response
*
* @see https://www.apexhours.com/testing-web-services-callouts-in-salesforce/
*/
@IsTest
public class MockHttpResponse implements HttpCalloutMock {
@ -53,7 +49,7 @@ public class MockHttpResponse implements HttpCalloutMock {
// Usage in test
@IsTest
static void testApiCall_Success() {
static void shouldReturnData_WhenApiCallSucceeds() {
String mockBody = '{"success": true, "data": {"id": "12345"}}';
Test.setMock(HttpCalloutMock.class, new MockHttpResponse(200, mockBody));
@ -102,7 +98,7 @@ public class MultiEndpointMock implements HttpCalloutMock {
* Mock for testing error handling
*/
@IsTest
static void testApiCall_ServerError_HandlesGracefully() {
static void shouldHandleGracefully_WhenServerReturnsError() {
Test.setMock(HttpCalloutMock.class, new MockHttpResponse(500, '{"error": "Server Error"}'));
Test.startTest();
@ -127,8 +123,6 @@ This pattern eliminates database operations from tests, achieving 35x faster exe
```apex
/**
* Interface for DML operations - enables mocking
*
* @see https://www.jamessimone.net/blog/joys-of-apex/mocking-dml/
*/
public interface IDML {
void doInsert(SObject record);
@ -307,7 +301,7 @@ public class AccountService {
// Test using mock DML
@IsTest
static void testCreateAccount_NoDatabase() {
static void shouldCreateAccount_WithMockDML() {
// Arrange
DMLMock.reset();
AccountService service = new AccountService(new DMLMock());
@ -393,7 +387,7 @@ public class AccountServiceStub implements System.StubProvider {
```apex
// Create stub with Test.createStub()
@IsTest
static void testWithStub() {
static void shouldReturnConfiguredValues_WhenUsingStub() {
// Create the stub
AccountServiceStub stub = new AccountServiceStub()
.withMethodReturn('getAccountCount', 42);
@ -450,7 +444,7 @@ public class AccountSelector {
// Usage in test
@IsTest
static void testWithMockedQuery() {
static void shouldReturnMockData_WhenQueryMocked() {
// Arrange - no database insert needed!
List<Account> mockAccounts = new List<Account>{
new Account(Name = 'Mock 1', Industry = 'Tech'),

View File

@ -2,8 +2,6 @@
Fast tests enable faster development. When test suites run quickly, developers refactor confidently. This guide covers techniques to dramatically reduce test execution time.
> **Source**: [James Simone - Writing Performant Apex Tests](https://www.jamessimone.net/blog/joys-of-apex/writing-performant-apex-tests/)
---
## Why Test Speed Matters
@ -172,7 +170,7 @@ List<List<SObject>> results = [FIND 'test' IN ALL FIELDS RETURNING Account];
// ✅ SOLUTION: Use Test.setFixedSearchResults()
@IsTest
static void testSearch() {
static void Search_WithFixedResults_FindsRecord() {
Account acc = new Account(Name = 'Searchable');
insert acc;
@ -201,7 +199,7 @@ sf apex run test --test-level RunLocalTests --target-org sandbox
sf apex run test --class-names MyClassTest --target-org sandbox
# ✅ FASTER: Run single test method
sf apex run test --tests MyClassTest.testSpecificMethod --target-org sandbox
sf apex run test --tests MyClassTest.ProcessRecords_Success --target-org sandbox
```
---
@ -251,7 +249,7 @@ Before committing tests, verify:
```apex
@IsTest
static void testPerformance() {
static void shouldCompleteInUnderOneSecond() {
Long startTime = System.currentTimeMillis();
// Your test code here

View File

@ -19,13 +19,13 @@ User: "Run tests for AccountService with auto-fix enabled"
Claude:
1. sf apex run test --class-names AccountServiceTest --code-coverage --result-format json
2. Parse results: 1 failure - testBulkInsert line 45 NullPointerException
2. Parse results: 1 failure - BulkInsert_Success line 45 NullPointerException
3. Read AccountServiceTest.cls (line 45 context)
4. Read AccountService.cls (trace the null reference)
5. Identify: Missing null check in AccountService.processAccounts()
6. generating-apex: Add null safety to AccountService.processAccounts()
7. Deploy fix
8. Re-run: sf apex run test --tests AccountServiceTest.testBulkInsert
8. Re-run: sf apex run test --tests AccountServiceTest.BulkInsert_Success
9. ✅ Passing! Report success.
```

View File

@ -44,7 +44,6 @@ All steps in this workflow are MANDATORY and must be executed in order. Execute
**CRITICAL -- WORKFLOW INTEGRITY RULES:**
- NEVER remove, rename, or consolidate checklist items from your task progress. Every step listed below must appear in task_progress from start to finish.
- NEVER replace specific step names (e.g., "Run static analysis", "Execute tests") with vague alternatives (e.g., "Complete workflow", "Finalize").
- The task is NOT complete after writing files. Writing `.cls` and `.cls-meta.xml` files is the MIDPOINT of this workflow, not the end. Steps 6 and 7 are mandatory tool invocations that MUST execute before completion.
- NEVER call `attempt_completion` or present a final summary until Steps 6, 7, and 8 are all executed and documented with their actual tool outputs.
@ -63,7 +62,7 @@ All steps in this workflow are MANDATORY and must be executed in order. Execute
- Generate `{ClassName}.cls` with ApexDoc
- Generate `{ClassName}.cls-meta.xml`
5. [MANDATORY] **Generate test classes** -- Use the skill `generating-apex-test` and follow its complete workflow to generate `{ClassName}Test.cls` and `{ClassName}Test.cls-meta.xml`. You MUST use that skill now and execute its instructions before proceeding to Step 6. After the test skill workflow completes, return here and continue with Step 6 -- do NOT end the task.
5. [MANDATORY] **Generate test classes** -- STOP: Do not write any apex test code in this skill. Immediately activate `generating-apex-test` skill and follow its complete workflow to generate `{ClassName}Test.cls` and `{ClassName}Test.cls-meta.xml`. Only return here after that skill reports completion; if activation is unavailable, abort this step and record `test_skill=unavailable: <reason>` in Step 8.
---
@ -73,18 +72,18 @@ You have written files. You are NOT done. The two steps below require MCP tool i
---
6. [MANDATORY] **Static analysis -- DO NOT SKIP -- REQUIRES TOOL INVOCATION**
- You MUST call the MCP tool `run_code_analyzer` RIGHT NOW on the newly generated/updated `.cls` files.
6. [MANDATORY] **run_code_analyzer MCP tool -- DO NOT SKIP -- REQUIRES TOOL INVOCATION**
- You MUST call the MCP tool `run_code_analyzer` RIGHT NOW to performe static analysis against code on the newly generated/updated `.cls` files.
- This is a tool invocation, not a file write. Invoke `run_code_analyzer` as an MCP tool call.
- Remediate all violations with severity levels `sev0`, `sev1`, and `sev2`.
- Re-run `run_code_analyzer` until no `sev0-sev2` issues remain.
- Record the final `run_code_analyzer` output (clean or with remaining sev3+ only) -- you will need it for the report in Step 8.
- If the MCP tool is unavailable after a real invocation attempt, record `run_code_analyzer=unavailable` with the error and state this in the report. Do NOT silently skip.
- If the MCP tool is unavailable after a real invocation attempt cli command `sf code-analyzer run --target <target>`, if that also doesnt work record `run_code_analyzer=unavailable` with the error and state this in the report. Do NOT silently skip.
7. [MANDATORY] **Execute Apex tests -- DO NOT SKIP -- REQUIRES TOOL INVOCATION**
- You MUST run the org's Apex test suite RIGHT NOW, including `{ClassName}Test`.
- Execute tests using the `sf apex run test` CLI command or the appropriate MCP tool.
- All test authoring, failure remediation, and coverage improvements MUST be performed by reading and following `skills/generating-apex-test/SKILL.md`; iterate until green.
- All test authoring, failure remediation, and coverage improvements MUST be performed by reading and following `generating-apex-test` skill; iterate until green.
- Record the test execution results (pass/fail counts, coverage percentage) -- you will need them for the report in Step 8.
- If test execution is unavailable (no org connected, no CLI access), record `test_execution=unavailable` with the error and state this in the report. Do NOT silently skip.
@ -94,7 +93,7 @@ You have written files. You are NOT done. The two steps below require MCP tool i
- NEVER omit the Analyzer or Testing lines. NEVER write "N/A" without having attempted the tool invocation first.
9. [MANDATORY] **Enforce cross-skill boundaries**
- Test class creation, updates, refactors, data setup, and advanced fixtures: ALWAYS delegate by reading and following `skills/generating-apex-test/SKILL.md`
- Test class creation, updates, refactors, data setup, and advanced fixtures: ALWAYS delegate by reading and following `generating-apex-test` skill
- NEVER write test code directly in this skill; all test work flows through the test skill
---
@ -339,8 +338,8 @@ Prefer current language features:
Deliverables per class:
- `{ClassName}.cls`
- `{ClassName}.cls-meta.xml` (default API version `66.0` or higher unless specified)
- `{ClassName}Test.cls` (generated via `skills/generating-apex-test/SKILL.md`)
- `{ClassName}Test.cls-meta.xml` (generated via `skills/generating-apex-test/SKILL.md`)
- `{ClassName}Test.cls` (generated via `generating-apex-test` skill)
- `{ClassName}Test.cls-meta.xml` (generated via `generating-apex-test` skill)
Meta XML template:
@ -373,7 +372,7 @@ Deploy: <dry-run or next step>
|---|---|---|
| Describe objects / fields first | metadata skill | Ensure alignment with the correct schema |
| Seed bulk or edge-case data | data skill | Create realistic datasets |
| Run Apex tests / fix failing tests | Read and follow `skills/generating-apex-test/SKILL.md` | Execute and iterate on failures |
| Run Apex tests / fix failing tests | Read and follow `generating-apex-test` skill | Execute and iterate on failures |
| Deploy to org | deploy skill | Validation and deployment orchestration |
| Build Flow that calls Apex | Flow skill | Declarative orchestration via `@InvocableMethod` |
| Build LWC that calls Apex | LWC skill | UI/controller integration via `@AuraEnabled` |

View File

@ -32,7 +32,7 @@ These patterns indicate poor code quality and should be refactored.
| **No trigger bypass flag** | Can't disable for data loads | Add Custom Setting bypass |
| **`System.debug()` in main code paths** | Performance impact from concatenating variables, even when logs are disabled | Use logging framework with levels |
| **Unnecessary `isEmpty()` before DML** | Wastes CPU | Remove - DML handles empty lists |
| **`!= false` comparisons** | Confusing double negative | Use `== true` or just the boolean |
| **`!= false` comparisons** | Confusing double negative | Use `== true` or just the boolean if the value has been null checked |
| **God Class** | Single class does everything | Split into Service/Selector/Domain |
| **Magic Numbers** | Hardcoded values like `if (score > 75)` | Use named constants |
@ -43,7 +43,7 @@ These patterns indicate poor code quality and should be refactored.
**❌ BAD:**
```apex
List<Account> accounts = [SELECT Id FROM Account];
// Returns ALL accounts - could be millions!
// Returns ALL accounts - could exceed the Total number of records retrieved by SOQL queries limit
```
**✅ GOOD:**
@ -148,7 +148,7 @@ if (acc.IsActive__c == true) {
// Clear intent
}
// Or even better
// Or even better if acc.IsActive__c can't be null
if (acc.IsActive__c) {
// Most concise
}
@ -173,13 +173,13 @@ for (Account acc : accounts) {
**✅ GOOD:**
```apex
Map<Id, Account> accountsWithContacts = new Map<Id, Account>([
List<Account> accountsWithContacts = [
SELECT Id, (SELECT Id FROM Contacts)
FROM Account
WHERE Id IN :accountIds
]);
];
for (Account acc : accountsWithContacts.values()) {
for (Account acc : accountsWithContacts) {
for (Contact con : acc.Contacts) {
// No SOQL in loop
}
@ -204,19 +204,21 @@ public class AccountService {
**✅ GOOD:**
```apex
public class AccountService {
private List<Account> accounts;
private List<Account> accounts {
get {
// lazy load only when needed
if (accounts == null) {
accounts = [SELECT Id FROM Account LIMIT 200];
}
return accounts;
}
set { accounts = value; }
}
public AccountService(List<Account> accounts) {
this.accounts = accounts; // Inject dependencies
}
// Or lazy load only when needed
private List<Account> getAccounts() {
if (accounts == null) {
accounts = [SELECT Id FROM Account LIMIT 200];
}
return accounts;
}
}
```
@ -267,8 +269,6 @@ Set<Id> uniqueIds = new Set<Id>(allIds); // O(1) deduplication
## Code Smell Catalog
Based on "Clean Apex Code" by Pablo Gonzalez and clean code principles.
### Long Methods
#### The Smell

View File

@ -75,9 +75,6 @@ public class TriggerHelper {
## 3. Guard Clauses & Fail-Fast
> 💡 *Principles inspired by "Clean Apex Code" by Pablo Gonzalez.
> [Purchase the book](https://link.springer.com/book/10.1007/979-8-8688-1411-2) for complete coverage.*
### The Problem
Deeply nested validation leads to hard-to-read code where business logic is buried.
@ -162,9 +159,6 @@ public Database.LeadConvertResult convertLead(Id leadId, Id accountId) {
## 4. Comment Best Practices
> 💡 *Principles inspired by "Clean Apex Code" by Pablo Gonzalez.
> [Purchase the book](https://link.springer.com/book/10.1007/979-8-8688-1411-2) for complete coverage.*
### Core Principle
Comments should explain **"why"**, not **"what"**. The code itself should communicate the "what".
@ -181,10 +175,6 @@ private static final Integer BULK_TEST_SIZE = 201;
// Safe navigation (?.) doesn't work in formulas - must use IF(ISBLANK())
// See Known Issue W-12345678
// GOOD: References external documentation
// Algorithm based on RFC 7519 (JSON Web Token specification)
// See: https://tools.ietf.org/html/rfc7519#section-4.1
// GOOD: Explains non-obvious optimization
// SOQL query in a loop replaced with map-based lookup — O(1) per record.
Account acc = accountsByExternalId.get(record.ExternalId__c);

View File

@ -896,9 +896,6 @@ if (result.success) {
## Domain Class Pattern
> 💡 *Principles inspired by "Clean Apex Code" by Pablo Gonzalez.
> [Purchase the book](https://link.springer.com/book/10.1007/979-8-8688-1411-2) for complete coverage.*
### Purpose
Encapsulate business rules in domain-specific classes, making code read like plain English and enabling reuse across the application.
@ -991,9 +988,6 @@ public void processOpportunity(Opportunity opp, Account account) {
## Abstraction Level Management
> 💡 *Principles inspired by "Clean Apex Code" by Pablo Gonzalez.
> [Purchase the book](https://link.springer.com/book/10.1007/979-8-8688-1411-2) for complete coverage.*
### Purpose
Ensure each method operates at a consistent level of abstraction. Don't mix high-level orchestration with low-level implementation details.

View File

@ -2,8 +2,6 @@
This guide documents systematic errors that LLMs (including Claude) commonly make when generating Salesforce Apex code. These patterns are critical to validate in generated code.
> **Source**: [LLM Mistakes in Apex & LWC - Salesforce Diaries](https://salesforcediaries.com/2026/01/16/llm-mistakes-in-apex-lwc-salesforce-code-generation-rules/)
---
## Table of Contents
@ -703,4 +701,3 @@ def validate_apex(content):
- **Existing Anti-Patterns**: See `references/anti-patterns.md` for traditional Apex anti-patterns
- **Best Practices**: See `references/best-practices.md` for correct patterns
- **Source**: [Salesforce Diaries - LLM Mistakes](https://salesforcediaries.com/2026/01/16/llm-mistakes-in-apex-lwc-salesforce-code-generation-rules/)

View File

@ -10,8 +10,6 @@ The Trigger Actions Framework provides a metadata-driven approach to trigger man
## Installation
Install from: https://github.com/mitchspano/trigger-actions-framework
## Basic Setup
### 1. Create the Trigger