npx skills add ...
npx skills add forcedotcom/sf-skills --skill generating-apex-test
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.
npx skills add forcedotcom/sf-skills --skill generating-apex-test
Generate production-ready Apex test classes and run disciplined test-fix loops with coverage analysis.
_NullInput_ and _EmptyInput_ as separate test methodsexecute() invocation runs, so set batchSize >= testRecordCount. See references/async-testing.md [blocked]@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 [blocked]Assert class only — Assert.areEqual, Assert.isTrue, Assert.fail, etc. Never use legacy System.assert, System.assertEquals, or System.assertNotEqualsHttpCalloutMock for callouts, Test.setFixedSearchResults for SOSL, DML mock classes for database isolation. Design for testability via constructor injection. See references/mocking-patterns.md [blocked]Test.startTest() with Test.stopTest() to reset governor limits and force async executionAlways wrap the code under test in Test.startTest() / Test.stopTest():
| Anti-Pattern | Fix |
|---|---|
| SOQL/DML inside loops | Query once before the loop; use Map<Id, SObject> for lookups |
| Magic numbers in assertions | Derive expected values from setup constants |
| God test class (>500 lines) | Split into multiple test classes by behavior area |
| Long test methods (>30 lines) | Extract Given/When/Then into helper methods |
Generic Exception catch | Catch the specific expected type (e.g., DmlException) |
Before generating or fixing tests, identify:
Apply the structure, naming conventions, and patterns from the asset templates and reference docs.
MANDATORY — File Deliverables: For every test class, create BOTH files:
{ClassName}Test.cls — the test class (use assets/test-class-template.cls [blocked] as starting point){ClassName}Test.cls-meta.xml — the metadata file:If no TestDataFactory exists in the project, create TestDataFactory.cls + TestDataFactory.cls-meta.xml using assets/test-data-factory-template.cls [blocked].
Use Given/When/Then:
Use try/catch with Assert.fail to verify expected exceptions:
should[ExpectedResult]_When[Scenario]: shouldSendNotification_WhenOpportunityClosedWon[SubjectOrAction]_[Scenario]_[ExpectedResult]: AccountUpdate_ChangeName_SuccessStart narrow when debugging; widen after the fix is stable.
Focus on:
When tests fail, run a disciplined fix loop (max 3 iterations — stop and surface root cause if still failing):
generating-apex skill| Level | Coverage | Purpose |
|---|---|---|
| Production deploy | 75% minimum | Required by Salesforce |
| Recommended | 90%+ | Best practice target |
| Critical paths | 100% | Business-critical code |
Cover all paths: positive, negative/exception, bulk (251+ records), callout/async.
| Component | Key Test Scenarios |
|---|---|
| Trigger | Bulk insert/update/delete, recursion guard, field change detection |
| Service | Valid/invalid inputs, bulk operations, exception handling |
| Controller | Page load, action methods, view state |
| Batch | start/execute/finish, scope matching (batch size >= record count), Database.Stateful tracking, error handling, chaining (separate methods — finish() calling Database.executeBatch() throws UnexpectedException) |
| Queueable | Chaining (only first job runs in tests), bulkification, error handling, callout mocks before Test.startTest() |
| Callout | Success response, error response, timeout |
| Selector | Valid/null/empty inputs, bulk (251+), field population, sort order, WITH USER_MODE via System.runAs |
| Scheduled | Direct execution via execute(null), CRON registration via CronTrigger query |
| Platform Event | Test.enableChangeDataCapture(), Test.getEventBus().deliver(), verify subscriber side effects |
Deliverables per test class:
{ClassName}Test.cls + {ClassName}Test.cls-meta.xml (match API version of class under test; default 66.0)TestDataFactory.cls + TestDataFactory.cls-meta.xml (if not already present)Load on demand for detailed patterns:
| Reference | When to use |
|---|---|
| references/test-data-factory.md [blocked] | TestDataFactory patterns, field overrides, duplicate rule handling |
| references/assertion-patterns.md [blocked] | Assertion best practices, anti-patterns, common pitfalls |
| references/mocking-patterns.md [blocked] | HttpCalloutMock, DML mocking, StubProvider, SOSL, Email, Platform Events |
| references/async-testing.md [blocked] | Batch, Queueable, Future, Scheduled job testing |
@TestSetup
static void setupTestData() {
List<Account> accounts = TestDataFactory.createAccounts(251, true);
}@isTest
static void shouldUpdateStatus_WhenValidInput() {
// Given
List<Account> accounts = [SELECT Id FROM Account];
// When
Test.startTest();
MyService.processAccounts(accounts);
Test.stopTest();
// Then
List<Account> updated = [SELECT Id, Status__c FROM Account];
Assert.areEqual(251, updated.size(), 'All accounts should be processed');
}@isTest
static void shouldThrowException_WhenInvalidInput() {
// Given
List<Account> emptyList = new List<Account>();
// When/Then
Test.startTest();
try {
MyService.processAccounts(emptyList);
Assert.fail('Expected MyCustomException to be thrown');
} catch (MyCustomException e) {
Assert.isTrue(e.getMessage().contains('cannot be empty'),
'Exception message should indicate empty input');
}
Test.stopTest();
}# Single test class
sf apex run test --class-names MyServiceTest --result-format human --code-coverage --target-org <alias>
# Specific test methods
sf apex run test --tests MyServiceTest.shouldUpdateStatus_WhenValidInput --result-format human --target-org <alias>
# All local tests
sf apex run test --test-level RunLocalTests --result-format human --code-coverage --target-org <alias>