
Generating Apex Test
Generate Salesforce Apex test classes with bulk, positive, negative, and exception coverage templates for your agent to fill in.
Overview
Generating Apex Test is an agent skill for the Ship phase that scaffolds Salesforce @isTest classes with bulk (251+), positive, negative, and exception test sections.
Install
npx skills add https://github.com/forcedotcom/afv-library --skill generating-apex-testWhat is this skill?
- Skeleton @isTest class with @TestSetup and TestDataFactory for 251+ Account records
- Separated positive tests including bulk processing (251 records) with Assert.areEqual
- Negative and exception-path sections with Given/When/Then structure
- Placeholder hooks for methodUnderTest and descriptive assertion messages
- Aligned with Salesforce bulk-operation and exception-handling test expectations
- 251+ bulk test records in scaffold
- Positive, negative, and exception test sections
Adoption & trust: 1.4k installs on skills.sh; 512 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You have Apex to ship but lack a consistent test class that exercises bulk limits, happy paths, and failures the way Salesforce reviewers expect.
Who is it for?
Salesforce solo builders or tiny teams adding or extending Apex who want agent-guided test skeletons that already encode bulk and assertion conventions.
Skip if: Non-Salesforce stacks, teams that only need integration UI tests, or repos where tests are already complete and passing in CI.
When should I use this skill?
You need a new or expanded Apex test class with bulk (251+) coverage and structured positive/negative tests for a specific class under test.
What do I get? / Deliverables
You get a fill-in-ready Apex test template with setup data, bulk test, and negative stubs so your agent can implement assertions and run coverage toward deploy.
- Private @isTest class file with setup, bulk, and negative test stubs
Recommended Skills
Journey fit
Test-class scaffolding belongs on the Ship shelf because it is produced right before or alongside release-ready Apex validation. Testing is the canonical home for @isTest patterns, bulk governor-limit checks, and assertion structure—not general Apex feature coding.
How it compares
Use as a procedural test scaffold instead of asking the agent to invent Apex test structure from scratch each time.
Common Questions / FAQ
Who is generating-apex-test for?
It is for developers building on Salesforce who want their coding agent to produce standardized Apex unit tests with bulk and negative-case slots.
When should I use generating-apex-test?
Use it in Ship/testing when you add or change Apex and need @isTest coverage, especially before promoting to sandbox or production.
Is generating-apex-test safe to install?
Review the Security Audits panel on this Prism page and treat it like any third-party skill: inspect the SKILL.md and generated code before running in your org.
SKILL.md
READMESKILL.md - Generating Apex Test
/** * Test class for {ClassUnderTest}. * Tests bulk operations (251+ records), positive/negative paths, * and exception handling. */ @isTest private class {ClassUnderTest}Test { @TestSetup static void setupTestData() { List<Account> accounts = TestDataFactory.createAccounts(251, true); } // ─── Positive Tests ─────────────────────────────────────────────────── @isTest static void shouldPerformExpectedBehavior_WhenValidInput() { // Given List<Account> accounts = [SELECT Id, Name FROM Account]; // When Test.startTest(); // {ClassUnderTest}.methodUnderTest(params); Test.stopTest(); // Then // Assert.areEqual(expected, actual, 'Descriptive failure message'); } @isTest static void shouldHandleBulkRecords_WhenProcessing251() { // Given List<Account> accounts = [SELECT Id FROM Account]; Assert.areEqual(251, accounts.size(), 'Should have 251 test records'); // When Test.startTest(); // {ClassUnderTest}.bulkMethod(accounts); Test.stopTest(); // Then // List<Account> results = [SELECT Id, Status__c FROM Account]; // for (Account acc : results) { // Assert.areEqual('Processed', acc.Status__c, 'All records should be processed'); // } } // ─── Negative Tests ─────────────────────────────────────────────────── @isTest static void shouldThrowException_WhenNullInput() { Test.startTest(); try { // {ClassUnderTest}.methodUnderTest(null); Assert.fail('Expected exception for null input'); } catch (MyCustomException e) { Assert.isTrue(e.getMessage().contains('cannot be null'), 'Exception message should mention null input'); } Test.stopTest(); } @isTest static void shouldReturnEmpty_WhenEmptyInput() { Test.startTest(); // List<SObject> results = {ClassUnderTest}.methodUnderTest(new List<Id>()); Test.stopTest(); // Assert.isTrue(results.isEmpty(), 'Should return empty list for empty input'); } // ─── Edge Case Tests ────────────────────────────────────────────────── @isTest static void shouldHandleMixedRecords_WhenSomeQualify() { // Given List<Account> accounts = [SELECT Id, Status__c FROM Account]; Integer half = accounts.size() / 2; // for (Integer i = 0; i < half; i++) { // accounts[i].Status__c = 'Qualifying'; // } // update accounts; // When Test.startTest(); // {ClassUnderTest}.conditionalMethod(accounts); Test.stopTest(); // Then // List<Account> qualifying = [SELECT Id FROM Account WHERE Processed__c = true]; // Assert.areEqual(half, qualifying.size(), 'Only qualifying records should be processed'); } } /** * @description Centralized factory for creating test data with sensible defaults. * All methods accept a doInsert flag for flexibility. * Bulk methods create multiple records; single-record methods delegate to bulk. */ @isTest public class TestDataFactory { // ─── Accounts ───────────────────────────────────────────────────────── public static List<Account> createAccounts(Integer count, Boolean doInsert) { List<Account> accounts = new List<Account>(); for (Integer i = 0; i < count; i++) { accounts.add(new Account( Name = 'Test Account ' + i, BillingCity = 'San Francisco', BillingState = 'CA', BillingCountry = 'USA', Industry = 'Technology', Type = 'Customer' )); } if (doInsert) insert accounts; return accounts; } public static Account createAccount(Boolean doInsert) { return createAccounts(1, doInsert)[0]; }