
Sf Test
Write compilable Salesforce Apex tests for @future, batch, and related async patterns using Test.startTest and Test.stopTest correctly.
Overview
Sf-test is an agent skill for the Ship phase that supplies Apex test pattern examples for async code including @future and batch jobs.
Install
npx skills add https://github.com/clientell-ai/salesforce-skills --skill sf-testWhat is this skill?
- Complete compilable examples for major Apex test patterns in one reference
- @future methods run synchronously inside Test.startTest() / Test.stopTest() boundaries in tests
- Batch Apex executes between startTest and stopTest with documented 200-record execute limit in test context
- Only one Database.executeBatch call allowed per test method
- Examples use modern Assert.areEqual style assertions with descriptive messages
- Batch execute() receives at most 200 records per batch in test context
- Only one Database.executeBatch call allowed per Apex test method
Adoption & trust: 1 installs on skills.sh; 7 GitHub stars; 3/3 security scanners passed (skills.sh audits); trending (+100% hot-view momentum).
What problem does it solve?
Your Apex async logic ships without tests that properly bracket @future or batch execution, so coverage looks fine but behavior is unverified.
Who is it for?
Solo Salesforce developers writing or reviewing Apex tests before deploy or package submission.
Skip if: Non-Salesforce stacks, pure Lightning Web Component Jest tests without Apex, or orgs that forbid pattern copy without org-specific data factories.
When should I use this skill?
Writing or fixing Apex tests for @future, batch, or related async Salesforce patterns.
What do I get? / Deliverables
You get copy-ready @IsTest classes that respect Salesforce test semantics for futures and batch jobs with clear assertions.
- Compilable @IsTest class for async Apex
- Documented test pattern snippets for batch and @future
Recommended Skills
Journey fit
Apex unit tests gate deployments and quality in the Ship phase before production releases on Salesforce orgs. Skill content is reference patterns for automated Apex testing, not feature implementation or security review alone.
How it compares
Apex-specific async test cookbook, not generic Jest or Playwright end-to-end guidance.
Common Questions / FAQ
Who is sf-test for?
Indie and small-team Salesforce builders who need reliable Apex unit tests for asynchronous and batch code paths.
When should I use sf-test?
During Ship testing when adding @future or batch Apex, fixing flaky async tests, or onboarding an agent to Salesforce test idioms.
Is sf-test safe to install?
The skill is reference text only; deploying generated Apex still runs in your org—review the Security Audits panel on this Prism page and your own code review process.
SKILL.md
READMESKILL.md - Sf Test
# Apex Test Patterns Reference Complete, compilable code examples for every major Apex test pattern. --- ## 1. Testing @future Methods `@future` methods execute asynchronously. In tests, they run synchronously between `Test.startTest()` and `Test.stopTest()`. ```apex public class AccountProcessor { @future public static void updateAccountRating(Set<Id> accountIds) { List<Account> accounts = [SELECT Id, Rating FROM Account WHERE Id IN :accountIds]; for (Account a : accounts) { a.Rating = 'Hot'; } update accounts; } } @IsTest private class AccountProcessorTest { @IsTest static void testUpdateAccountRating() { Account acc = new Account(Name = 'Test Account', Rating = 'Cold'); insert acc; Test.startTest(); AccountProcessor.updateAccountRating(new Set<Id>{ acc.Id }); Test.stopTest(); // @future method has now completed Account updated = [SELECT Rating FROM Account WHERE Id = :acc.Id]; Assert.areEqual('Hot', updated.Rating, 'Rating should be updated to Hot'); } } ``` --- ## 2. Testing Batch Apex Batch jobs run between `Test.startTest()` and `Test.stopTest()`. In test context, `execute()` receives at most 200 records regardless of scope size. Only one `Database.executeBatch` call is allowed per test method. ```apex public class AccountCleanupBatch implements Database.Batchable<SObject> { public Database.QueryLocator start(Database.BatchableContext bc) { return Database.getQueryLocator('SELECT Id, Description FROM Account WHERE Description = null'); } public void execute(Database.BatchableContext bc, List<Account> scope) { for (Account a : scope) { a.Description = 'Cleaned by batch'; } update scope; } public void finish(Database.BatchableContext bc) { Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); mail.setToAddresses(new String[]{ 'admin@example.com' }); mail.setSubject('Batch Complete'); mail.setPlainTextBody('Account cleanup finished.'); Messaging.sendEmail(new Messaging.SingleEmailMessage[]{ mail }); } } @IsTest private class AccountCleanupBatchTest { @TestSetup static void setup() { List<Account> accounts = new List<Account>(); for (Integer i = 0; i < 50; i++) { accounts.add(new Account(Name = 'Test ' + i)); } insert accounts; } @IsTest static void testBatchExecution() { Test.startTest(); Id batchId = Database.executeBatch(new AccountCleanupBatch(), 200); Test.stopTest(); // Batch has completed — start, execute, finish all ran List<Account> updated = [SELECT Description FROM Account]; for (Account a : updated) { Assert.areEqual('Cleaned by batch', a.Description, 'Description should be set by batch'); } // Verify the batch job completed AsyncApexJob job = [ SELECT Status, NumberOfErrors, JobItemsProcessed, TotalJobItems FROM AsyncApexJob WHERE Id = :batchId ]; Assert.areEqual('Completed', job.Status); Assert.areEqual(0, job.NumberOfErrors); } } ``` --- ## 3. Testing Queueable Apex Queueable jobs execute synchronously between `Test.startTest()` and `Test.stopTest()`. In test context, chaining is limited to depth 1 (a queueable can enqueue at most one additional queueable). ```apex public class AccountEnrichmentQueueable implements Queueable { private List<Id> accountIds; public AccountEnrichmentQueueable(List<Id> accountIds) { this.accountIds = accountIds; } public void execute(QueueableContext context) { List<Account> accounts = [SELECT Id, Industry FROM Account WHERE Id IN :accountIds]; for (Account a : accounts) { a.Industry = 'Technology'; } update accounts; //