Agent skill
sf-test
Generate comprehensive Apex test classes with @TestSetup methods, TestFactory patterns, bulk data (200 records), positive/negative/permission scenarios, and HttpCalloutMock implementations. Use when asked to write tests, improve code coverage, fix failing tests, or when you see @IsTest annotations. Activate on mentions of "test class", "code coverage", "TestDataFactory", or "mock callout".
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/sf-test
Metadata
Additional technical details for this skill
- tags
- salesforce, apex, testing, code-coverage, test-generation
- author
- clientell
- version
- 1.0.0
SKILL.md
Apex Test Class Generator
You are a Salesforce test class specialist. Generate comprehensive test classes that achieve 85%+ code coverage with meaningful assertions.
Test Class Structure
Required Pattern
@IsTest
private class MyClassTest {
@TestSetup
static void makeData() {
// Use TestFactory for all record creation
List<Account> accounts = TestDataFactory.createAccounts(200);
insert accounts;
List<Contact> contacts = TestDataFactory.createContacts(accounts);
insert contacts;
}
@IsTest
static void testMethodName_positiveScenario() {
// Arrange
List<Account> accounts = [SELECT Id, Name FROM Account WITH USER_MODE];
// Act
Test.startTest();
MyClass.myMethod(accounts);
Test.stopTest();
// Assert
List<Account> results = [SELECT Id, Status__c FROM Account WITH USER_MODE];
System.assertEquals(200, results.size(), 'All accounts should be processed');
for (Account acc : results) {
System.assertNotEquals(null, acc.Status__c, 'Status should be set');
}
}
}
Test Scenarios (generate ALL of these)
- Positive tests: Happy path with valid data
- Negative tests: Invalid data, null inputs, empty lists
- Bulk tests: 200+ records to verify bulkification
- Permission tests: Test with restricted user profile
- Boundary tests: Edge cases (0 records, 1 record, max records)
Permission Testing Pattern
@IsTest
static void testMethod_restrictedUser() {
User restrictedUser = TestDataFactory.createStandardUser();
insert restrictedUser;
System.runAs(restrictedUser) {
Test.startTest();
try {
MyClass.myMethod(testData);
System.assert(false, 'Should have thrown exception');
} catch (SecurityException e) {
System.assert(e.getMessage().contains('access'),
'Should throw security exception');
}
Test.stopTest();
}
}
Callout Mock Pattern
@IsTest
private class MyCalloutClassTest {
private class MockHttpResponse implements HttpCalloutMock {
private Integer statusCode;
private String body;
MockHttpResponse(Integer statusCode, String body) {
this.statusCode = statusCode;
this.body = body;
}
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
res.setStatusCode(this.statusCode);
res.setBody(this.body);
return res;
}
}
@IsTest
static void testCallout_success() {
Test.setMock(HttpCalloutMock.class, new MockHttpResponse(200, '{"status":"ok"}'));
Test.startTest();
String result = MyCalloutClass.makeCallout();
Test.stopTest();
System.assertEquals('ok', result, 'Should return success status');
}
@IsTest
static void testCallout_failure() {
Test.setMock(HttpCalloutMock.class, new MockHttpResponse(500, '{"error":"fail"}'));
Test.startTest();
try {
MyCalloutClass.makeCallout();
System.assert(false, 'Should throw on 500');
} catch (CalloutException e) {
System.assert(true, 'Exception expected on server error');
}
Test.stopTest();
}
}
Rules
- NEVER hardcode record IDs — always query or create in @TestSetup
- ALWAYS use
Test.startTest()andTest.stopTest()to reset governor limits - ALWAYS use
System.assertEquals/System.assertNotEqualswith descriptive messages - ALWAYS test with 200 records minimum for bulk scenarios
- Use
@TestVisibleon private methods/variables instead of making them public - Create a
TestDataFactoryclass if one doesn't exist - NEVER use
SeeAllData=trueunless testing specific platform features - Test both synchronous and asynchronous paths (future, queueable, batch)
TestDataFactory Pattern
@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
));
}
return accounts;
}
public static User createStandardUser() {
Profile p = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1];
return new User(
FirstName = 'Test',
LastName = 'User',
Email = '[email protected]',
Username = 'testuser' + DateTime.now().getTime() + '@example.com',
Alias = 'tuser',
TimeZoneSidKey = 'America/Los_Angeles',
LocaleSidKey = 'en_US',
EmailEncodingKey = 'UTF-8',
ProfileId = p.Id,
LanguageLocaleKey = 'en_US'
);
}
}
Async Testing Patterns
- @future: Runs after
Test.stopTest()— assert side effects after stopTest - Batch: Call
Database.executeBatch()betweenTest.startTest()/Test.stopTest() - Queueable: Call
System.enqueueJob()between startTest/stopTest — chaining limited to depth 1 in test - Schedulable: Call
System.schedule()between startTest/stopTest — assert CronTrigger afterward
Platform Event & CDC Testing
- Platform Events: Call
Test.getEventBus().deliver()after publishing to force synchronous delivery - Change Data Capture: Call
Test.enableChangeDataCapture()in test setup, thenTest.getEventBus().deliver()after DML
Stub API (Dependency Injection)
Use System.StubProvider interface + Test.createStub() to mock dependencies without hitting the database.
Test.loadData()
Load bulk test data from CSV in a Static Resource: Test.loadData(Account.sObjectType, 'TestAccounts')
Mixed DML Workaround
Use System.runAs() to separate setup object DML (User, Profile) from non-setup objects in the same test.
Special Object Testing
- Use
Test.getStandardPricebookId()for Product2/PricebookEntry tests - Use
RestContext.request = new RestRequest()for @RestResource endpoint tests
Gotchas
@TestSetupdata is shared (NOT isolated) across test methods — each method gets a copy that resetsSeeAllData=trueexposes production data — almost never use it- Future/Batch/Queueable execute AFTER
Test.stopTest(), not during - Callout mock (
Test.setMock()) must be registered BEFORETest.startTest() - Platform Event ordering is NOT guaranteed in tests
Test.startTest()/Test.stopTest()can only be called ONCE per test method- Batch Apex
finish()method also runs afterTest.stopTest() - Mixed DML throws
MIXED_DML_OPERATION— useSystem.runAs()to workaround
Workflow
- Read the class under test using Read/Glob tools
- Identify all public/global methods and code paths
- Check if TestDataFactory exists; create if not
- Generate test class with all scenario types
- Run tests:
sf apex run test -n MyClassTest --synchronous --code-coverage - Report coverage and fix any failures
References
- Test Patterns — async testing, Platform Events, CDC, Stub API, REST endpoints, mixed DML, Flow test coverage
- Governor Limits — per-transaction limits for test context
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?