Marketplace

sf-testing

Comprehensive Salesforce testing skill with test execution, code coverage analysis, and agentic test-fix loops. Run Apex tests, analyze coverage, generate test patterns, and automatically fix failing tests with 120-point scoring.

$ 安裝

git clone https://github.com/Jaganpro/sf-skills /tmp/sf-skills && cp -r /tmp/sf-skills/sf-testing ~/.claude/skills/sf-skills

// tip: Run this command in your terminal to install the skill


name: sf-testing description: > Comprehensive Salesforce testing skill with test execution, code coverage analysis, and agentic test-fix loops. Run Apex tests, analyze coverage, generate test patterns, and automatically fix failing tests with 120-point scoring. license: MIT metadata: version: "1.0.0" author: "Jag Valaiyapathy" scoring: "120 points across 6 categories"

sf-testing: Salesforce Test Execution & Coverage Analysis

Expert testing engineer specializing in Apex test execution, code coverage analysis, mock frameworks, and agentic test-fix loops. Execute tests, analyze failures, and automatically fix issues.

Core Responsibilities

  1. Test Execution: Run Apex tests via sf apex run test with coverage analysis
  2. Coverage Analysis: Parse coverage reports, identify untested code paths
  3. Failure Analysis: Parse test failures, identify root causes, suggest fixes
  4. Agentic Test-Fix Loop: Automatically fix failing tests and re-run until passing
  5. Test Generation: Create test classes using sf-apex patterns
  6. Bulk Testing: Validate with 251+ records for governor limit safety

Workflow (5-Phase Pattern)

Phase 1: Test Discovery

Use AskUserQuestion to gather:

  • Test scope (single class, all tests, specific test suite)
  • Target org alias
  • Coverage threshold requirement (default: 75%, recommended: 90%)
  • Whether to enable agentic fix loop

Then:

  1. Check existing tests: Glob: **/*Test*.cls, Glob: **/*_Test.cls
  2. Check for Test Data Factories: Glob: **/*TestDataFactory*.cls
  3. Create TodoWrite tasks

Phase 2: Test Execution

Run Single Test Class:

sf apex run test --class-names MyClassTest --code-coverage --result-format json --output-dir test-results --target-org [alias]

Run All Tests:

sf apex run test --test-level RunLocalTests --code-coverage --result-format json --output-dir test-results --target-org [alias]

Run Specific Methods:

sf apex run test --tests MyClassTest.testMethod1 --tests MyClassTest.testMethod2 --code-coverage --result-format json --target-org [alias]

Run Test Suite:

sf apex run test --suite-names MySuite --code-coverage --result-format json --target-org [alias]

Phase 3: Results Analysis

Parse test-results JSON:

Read: test-results/test-run-id.json

Coverage Summary Output:

📊 TEST EXECUTION RESULTS
════════════════════════════════════════════════════════════════

Test Run ID: 707xx0000000000
Org: my-sandbox
Duration: 45.2s

SUMMARY
───────────────────────────────────────────────────────────────
✅ Passed:    42
❌ Failed:    3
⏭️ Skipped:   0
📈 Coverage: 78.5%

FAILED TESTS
───────────────────────────────────────────────────────────────
❌ AccountServiceTest.testBulkInsert
   Line 45: System.AssertException: Assertion Failed
   Expected: 200, Actual: 199

❌ LeadScoringTest.testNullHandling
   Line 23: System.NullPointerException: Attempt to de-reference null

❌ OpportunityTriggerTest.testValidation
   Line 67: System.DmlException: FIELD_CUSTOM_VALIDATION_EXCEPTION

COVERAGE BY CLASS
───────────────────────────────────────────────────────────────
Class                          Lines    Covered  Uncovered  %
AccountService                 150      142      8          94.7% ✅
LeadScoringService            85       68       17         80.0% ✅
OpportunityTrigger            45       28       17         62.2% ⚠️
ContactHelper                 30       15       15         50.0% ❌

UNCOVERED LINES (OpportunityTrigger)
───────────────────────────────────────────────────────────────
Lines 23-28: Exception handling block
Lines 45-52: Bulk processing edge case
Lines 78-82: Null check branch

Phase 4: Agentic Test-Fix Loop

When tests fail, automatically:

┌─────────────────────────────────────────────────────────────────┐
│                    AGENTIC TEST-FIX LOOP                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. Parse failure message and stack trace                        │
│  2. Identify root cause:                                         │
│     - Assertion failure → Check expected vs actual               │
│     - NullPointerException → Add null checks                     │
│     - DmlException → Check validation rules, required fields     │
│     - LimitException → Reduce SOQL/DML in test                  │
│  3. Read the failing test class                                  │
│  4. Read the class under test                                    │
│  5. Generate fix using sf-apex skill                             │
│  6. Re-run the specific failing test                             │
│  7. Repeat until passing (max 3 attempts)                        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Failure Analysis Decision Tree:

Error TypeRoot CauseAuto-Fix Strategy
System.AssertExceptionWrong expected value or logic bugAnalyze assertion, check if test or code is wrong
System.NullPointerExceptionMissing null check or test dataAdd null safety or fix test data setup
System.DmlExceptionValidation rule, required field, triggerCheck org config, add required fields to test data
System.LimitExceptionGovernor limit hitRefactor to use bulkified patterns
System.QueryExceptionNo rows returnedAdd test data or adjust query
System.TypeExceptionType mismatchFix type casting or data format

Auto-Fix Command:

Skill(skill="sf-apex", args="Fix failing test [TestClassName].[methodName] - Error: [error message]")

Phase 5: Coverage Improvement

If coverage < threshold:

  1. Identify Uncovered Lines:
sf apex run test --class-names MyClassTest --code-coverage --detailed-coverage --result-format json --target-org [alias]
  1. Generate Tests for Uncovered Code:
Read: force-app/main/default/classes/MyClass.cls (lines 45-52)

Then use sf-apex to generate test methods targeting those lines.

  1. Bulk Test Validation:
Skill(skill="sf-data", args="Create 251 [ObjectName] records for bulk testing")
  1. Re-run with New Tests:
sf apex run test --class-names MyClassTest --code-coverage --result-format json --target-org [alias]

Best Practices (120-Point Scoring)

CategoryPointsKey Rules
Test Coverage2590%+ class coverage; all public methods tested; edge cases covered
Assertion Quality25Assert class used; meaningful messages; positive AND negative tests
Bulk Testing20Test with 251+ records; verify no SOQL/DML in loops under load
Test Data20Test Data Factory used; no hardcoded IDs; @TestSetup for efficiency
Isolation15SeeAllData=false; no org dependencies; mock external callouts
Documentation15Test method names describe scenario; comments for complex setup

Scoring Thresholds:

⭐⭐⭐⭐⭐ 108-120 pts (90%+)  → Production Ready
⭐⭐⭐⭐   96-107 pts (80-89%) → Good, minor improvements
⭐⭐⭐    84-95 pts  (70-79%) → Acceptable, needs work
⭐⭐      72-83 pts  (60-69%) → Below standard
⭐        <72 pts   (<60%)   → BLOCKED - Major issues

⛔ TESTING GUARDRAILS (MANDATORY)

BEFORE running tests, verify:

CheckCommandWhy
Org authenticatedsf org display --target-org [alias]Tests need valid org connection
Classes deployedsf project deploy report --target-org [alias]Can't test undeployed code
Test data existsCheck @TestSetup or TestDataFactoryTests need data to operate on

NEVER do these:

Anti-PatternProblemCorrect Pattern
@IsTest(SeeAllData=true)Tests depend on org data, break in clean orgsAlways SeeAllData=false (default)
Hardcoded Record IDsIDs differ between orgsQuery or create in test
No assertionsTests pass without validating anythingAssert every expected outcome
Single record tests onlyMisses bulk trigger issuesAlways test with 200+ records
Test.startTest() without Test.stopTest()Async code won't executeAlways pair start/stop

CLI Command Reference

Test Execution Commands

CommandPurposeExample
sf apex run testRun testsSee examples above
sf apex get testGet async test statussf apex get test --test-run-id 707xx...
sf apex list logList debug logssf apex list log --target-org alias
sf apex tail logStream logs real-timesf apex tail log --target-org alias

Useful Flags

FlagPurpose
--code-coverageInclude coverage in results
--detailed-coverageLine-by-line coverage (slower)
--result-format jsonMachine-parseable output
--output-dirSave results to directory
--synchronousWait for completion (default)
--test-level RunLocalTestsAll tests except managed packages
--test-level RunAllTestsInOrgEvery test including packages

Test Patterns & Templates

Pattern 1: Basic Test Class

Use template: templates/basic-test.cls

@IsTest
private class AccountServiceTest {

    @TestSetup
    static void setupTestData() {
        // Use Test Data Factory for consistent data creation
        List<Account> accounts = TestDataFactory.createAccounts(5);
        insert accounts;
    }

    @IsTest
    static void testCreateAccount_Success() {
        // Given
        Account testAccount = new Account(Name = 'Test Account');

        // When
        Test.startTest();
        Id accountId = AccountService.createAccount(testAccount);
        Test.stopTest();

        // Then
        Assert.isNotNull(accountId, 'Account ID should not be null');
        Account inserted = [SELECT Name FROM Account WHERE Id = :accountId];
        Assert.areEqual('Test Account', inserted.Name, 'Account name should match');
    }

    @IsTest
    static void testCreateAccount_NullInput_ThrowsException() {
        // Given
        Account nullAccount = null;

        // When/Then
        try {
            Test.startTest();
            AccountService.createAccount(nullAccount);
            Test.stopTest();
            Assert.fail('Expected IllegalArgumentException was not thrown');
        } catch (IllegalArgumentException e) {
            Assert.isTrue(e.getMessage().contains('cannot be null'),
                'Error message should mention null: ' + e.getMessage());
        }
    }
}

Pattern 2: Bulk Test (251+ Records)

Use template: templates/bulk-test.cls

@IsTest
static void testBulkInsert_251Records() {
    // Given - 251 records crosses the 200-record batch boundary
    List<Account> accounts = TestDataFactory.createAccounts(251);

    // When
    Test.startTest();
    insert accounts;  // Triggers fire in batches of 200, then 51
    Test.stopTest();

    // Then
    Integer count = [SELECT COUNT() FROM Account];
    Assert.areEqual(251, count, 'All 251 accounts should be inserted');

    // Verify no governor limits hit
    Assert.isTrue(Limits.getQueries() < 100,
        'Should not approach SOQL limit: ' + Limits.getQueries());
}

Pattern 3: Mock Callout Test

Use template: templates/mock-callout-test.cls

@IsTest
private class ExternalAPIServiceTest {

    // Mock class for HTTP callouts
    private class MockHttpResponse implements HttpCalloutMock {
        public HttpResponse respond(HttpRequest req) {
            HttpResponse res = new HttpResponse();
            res.setStatusCode(200);
            res.setBody('{"success": true, "data": {"id": "12345"}}');
            return res;
        }
    }

    @IsTest
    static void testCallExternalAPI_Success() {
        // Given
        Test.setMock(HttpCalloutMock.class, new MockHttpResponse());

        // When
        Test.startTest();
        String result = ExternalAPIService.callAPI('test-endpoint');
        Test.stopTest();

        // Then
        Assert.isTrue(result.contains('success'), 'Response should indicate success');
    }
}

Pattern 4: Test Data Factory

Use template: templates/test-data-factory.cls

@IsTest
public class TestDataFactory {

    public static List<Account> createAccounts(Integer count) {
        List<Account> accounts = new List<Account>();
        for (Integer i = 0; i < count; i++) {
            accounts.add(new Account(
                Name = 'Test Account ' + i,
                Industry = 'Technology',
                BillingCity = 'San Francisco'
            ));
        }
        return accounts;
    }

    public static List<Contact> createContacts(Integer count, Id accountId) {
        List<Contact> contacts = new List<Contact>();
        for (Integer i = 0; i < count; i++) {
            contacts.add(new Contact(
                FirstName = 'Test',
                LastName = 'Contact ' + i,
                AccountId = accountId,
                Email = 'test' + i + '@example.com'
            ));
        }
        return contacts;
    }

    // Convenience method with insert
    public static List<Account> createAndInsertAccounts(Integer count) {
        List<Account> accounts = createAccounts(count);
        insert accounts;
        return accounts;
    }
}

Agentic Test-Fix Loop Implementation

How It Works

When the agentic loop is enabled, sf-testing will:

  1. Run tests and capture results
  2. Parse failures to identify error type and location
  3. Read source files (test class + class under test)
  4. Analyze root cause using the decision tree above
  5. Generate fix by invoking sf-apex skill
  6. Re-run failing test to verify fix
  7. Iterate until passing or max attempts (3)

Example Agentic Flow

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
3. Read AccountServiceTest.cls (line 45 context)
4. Read AccountService.cls (trace the null reference)
5. Identify: Missing null check in AccountService.processAccounts()
6. Skill(sf-apex): Add null safety to AccountService.processAccounts()
7. Deploy fix
8. Re-run: sf apex run test --tests AccountServiceTest.testBulkInsert
9. ✅ Passing! Report success.

Cross-Skill Integration

SkillWhen to UseExample
sf-apexGenerate test classes, fix failing codeSkill(skill="sf-apex", args="Create test class for LeadService")
sf-dataCreate bulk test data (251+ records)Skill(skill="sf-data", args="Create 251 Leads for bulk testing")
sf-deployDeploy test classes to orgSkill(skill="sf-deploy", args="Deploy tests to sandbox")
sf-debugAnalyze failures with debug logsSkill(skill="sf-debug", args="Analyze test failure logs")

Common Test Failures & Fixes

FailureLikely CauseFix
MIXED_DML_OPERATIONUser + non-setup object in same transactionUse System.runAs() or separate transactions
CANNOT_INSERT_UPDATE_ACTIVATE_ENTITYTrigger or flow errorCheck trigger logic with debug logs
REQUIRED_FIELD_MISSINGTest data incompleteAdd required fields to TestDataFactory
DUPLICATE_VALUEUnique field conflictUse dynamic values or delete existing
FIELD_CUSTOM_VALIDATION_EXCEPTIONValidation rule firedMeet validation criteria in test data
UNABLE_TO_LOCK_ROWRecord lock conflictUse FOR UPDATE or retry logic

Dependencies

Required: Target org with sf CLI authenticated Recommended: sf-apex (for auto-fix), sf-data (for bulk test data), sf-debug (for log analysis)

Install: /plugin install github:Jaganpro/sf-skills/sf-testing


Documentation

DocumentDescription
testing-best-practices.mdGeneral testing guidelines
cli-commands.mdSF CLI test commands
mocking-patterns.mdMocking vs Stubbing, DML mocking, HttpCalloutMock
performance-optimization.mdFast tests, reduce execution time

Templates

TemplateDescription
basic-test.clsStandard test class with Given-When-Then
bulk-test.cls251+ record bulk testing
mock-callout-test.clsHTTP callout mocking
test-data-factory.clsReusable test data creation
dml-mock.clsDML abstraction for 35x faster tests
stub-provider-example.clsStubProvider for dynamic behavior

Credits

See CREDITS.md for acknowledgments of community resources that shaped this skill.


License

MIT License. See LICENSE file. Copyright (c) 2024-2025 Jag Valaiyapathy