.cursor/skills/sf-e2e-testing/SKILL.md
Use when writing Salesforce Apex end-to-end integration tests, deployment verification, or bulk scenario validation. Do NOT use for unit tests or TDD.
npx skillsauth add jiten-singh-shahi/salesforce-claude-code sf-e2e-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive testing patterns for Salesforce applications including Apex integration tests, LWC component tests, and deployment verification.
Reference: @../_reference/TESTING_STANDARDS.md
@IsTest
private class OpportunityWorkflowTest {
@TestSetup
static void setup() {
Account acc = new Account(Name = 'E2E Test Account');
insert acc;
Opportunity opp = new Opportunity(
AccountId = acc.Id,
Name = 'E2E Test Opp',
StageName = 'Prospecting',
CloseDate = Date.today().addDays(30),
Amount = 50000
);
insert opp;
}
@IsTest
static void shouldProgressThroughFullSalesCycle() {
Opportunity opp = [SELECT Id, StageName FROM Opportunity LIMIT 1];
Test.startTest();
opp.StageName = 'Qualification';
update opp;
opp.StageName = 'Proposal/Price Quote';
update opp;
opp.StageName = 'Closed Won';
update opp;
Test.stopTest();
Opportunity result = [SELECT StageName, IsClosed, IsWon FROM Opportunity WHERE Id = :opp.Id];
Assert.areEqual('Closed Won', result.StageName);
Assert.isTrue(result.IsClosed, 'Should be closed');
Assert.isTrue(result.IsWon, 'Should be won');
// Verify downstream automation fired
List<Task> followUpTasks = [SELECT Id FROM Task WHERE WhatId = :opp.Id];
Assert.isTrue(!followUpTasks.isEmpty(), 'Should have created follow-up tasks');
}
@IsTest
static void shouldHandleBulkStageChanges() {
List<Opportunity> opps = new List<Opportunity>();
Account acc = [SELECT Id FROM Account LIMIT 1];
for (Integer i = 0; i < 200; i++) {
opps.add(new Opportunity(
AccountId = acc.Id,
Name = 'Bulk Opp ' + i,
StageName = 'Prospecting',
CloseDate = Date.today().addDays(30)
));
}
insert opps;
Test.startTest();
for (Opportunity opp : opps) {
opp.StageName = 'Closed Won';
}
update opps;
Test.stopTest();
Integer closedCount = [SELECT COUNT() FROM Opportunity WHERE StageName = 'Closed Won'];
Assert.isTrue(closedCount >= 200, 'All opportunities should be closed');
}
}
import { createElement } from 'lwc';
import OpportunityPipeline from 'c/opportunityPipeline';
import getOpportunities from '@salesforce/apex/OpportunityController.getOpportunities';
jest.mock('@salesforce/apex/OpportunityController.getOpportunities', () => ({
default: jest.fn()
}), { virtual: true });
const MOCK_OPPS = [
{ Id: '006xx0001', Name: 'Opp 1', StageName: 'Prospecting', Amount: 10000 },
{ Id: '006xx0002', Name: 'Opp 2', StageName: 'Closed Won', Amount: 50000 },
];
describe('c-opportunity-pipeline (integration)', () => {
afterEach(() => { while (document.body.firstChild) document.body.removeChild(document.body.firstChild); });
it('renders pipeline with correct stage grouping', async () => {
getOpportunities.mockResolvedValue(MOCK_OPPS);
const element = createElement('c-opportunity-pipeline', { is: OpportunityPipeline });
document.body.appendChild(element);
await Promise.resolve();
await Promise.resolve();
const stages = element.shadowRoot.querySelectorAll('.stage-column');
expect(stages.length).toBeGreaterThan(0);
});
});
# Full E2E deployment verification
sf project deploy validate --test-level RunLocalTests --target-org staging
sf project deploy start --test-level RunLocalTests --target-org staging
sf apex run test --test-level RunLocalTests --result-format human --target-org staging
@IsTest
private class CaseEscalationFlowTest {
@IsTest
static void shouldEscalateHighPriorityCases() {
Account acc = new Account(Name = 'Flow E2E Account');
insert acc;
Case c = new Case(
AccountId = acc.Id,
Subject = 'Urgent Issue',
Priority = 'High',
Status = 'New'
);
insert c;
Test.startTest();
c.Status = 'Escalated';
update c;
Test.stopTest();
List<Task> tasks = [SELECT Subject, Priority FROM Task WHERE WhatId = :c.Id AND Subject LIKE '%Escalation%'];
Assert.isTrue(!tasks.isEmpty(), 'Flow should have created escalation task');
Assert.areEqual('High', tasks[0].Priority, 'Task priority should match case priority');
}
}
@IsTest
private class OrderEventTest {
@IsTest
static void shouldPublishEventOnOrderCompletion() {
Order_Complete__e event = new Order_Complete__e(
Order_Id__c = 'ORD-001',
Total_Amount__c = 5000.00
);
Test.startTest();
Database.SaveResult sr = EventBus.publish(event);
Test.stopTest();
Assert.isTrue(sr.isSuccess(), 'Event should publish successfully');
}
@IsTest
static void shouldProcessEventInSubscriber() {
Order_Complete__e event = new Order_Complete__e(
Order_Id__c = 'ORD-002',
Total_Amount__c = 10000.00
);
Test.startTest();
EventBus.publish(event);
Test.getEventBus().deliver(); // Force trigger subscriber to execute
Test.stopTest();
List<Fulfillment__c> fulfillments = [
SELECT Order_Id__c FROM Fulfillment__c WHERE Order_Id__c = 'ORD-002'
];
Assert.areEqual(1, fulfillments.size(), 'Subscriber should have created fulfillment');
}
}
@IsTest
private class AsyncWorkflowTest {
@IsTest
static void shouldProcessQueueableChain() {
Account acc = new Account(Name = 'Async E2E');
insert acc;
Test.startTest();
System.enqueueJob(new AccountEnrichmentJob(acc.Id));
Test.stopTest();
Account result = [SELECT Description, Industry FROM Account WHERE Id = :acc.Id];
Assert.isNotNull(result.Description, 'Enrichment job should populate description');
}
}
@IsTest
private class PermissionE2ETest {
@IsTest
static void shouldRestrictAccessForStandardUser() {
// Note: Profile names are locale-dependent. For non-English orgs,
// query by Profile.UserType or use a custom permission set.
Profile stdProfile = [SELECT Id FROM Profile WHERE Name = 'Standard User'];
User testUser = new User(
Alias = 'stdu', Email = '[email protected]',
EmailEncodingKey = 'UTF-8', LastName = 'StdUser',
LanguageLocaleKey = 'en_US', LocaleSidKey = 'en_US',
ProfileId = stdProfile.Id, TimeZoneSidKey = 'America/Los_Angeles',
UserName = 'stduser' + DateTime.now().getTime() + '@test.com'
);
insert testUser;
System.runAs(testUser) {
Test.startTest();
try {
ConfidentialRecord__c rec = new ConfidentialRecord__c(Name = 'Secret');
insert rec;
Assert.fail('Should have thrown insufficient access exception');
} catch (DmlException e) {
Assert.isTrue(e.getMessage().contains('INSUFFICIENT_ACCESS') ||
e.getMessage().contains('access'),
'Should get access denied: ' + e.getMessage());
}
Test.stopTest();
}
}
}
@IsTest
private class PerformanceE2ETest {
@IsTest
static void shouldStayWithinGovernorLimits() {
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < 200; i++) {
accounts.add(new Account(Name = 'Perf Test ' + i));
}
insert accounts;
Test.startTest();
Integer queriesBefore = Limits.getQueries();
AccountService.processAccounts(new Map<Id, Account>(accounts).keySet());
Integer queriesUsed = Limits.getQueries() - queriesBefore;
Test.stopTest();
Assert.isTrue(queriesUsed <= 5,
'Should use <= 5 SOQL queries for 200 records, used: ' + queriesUsed);
}
}
| Anti-Pattern | Problem | Fix | |-------------|---------|-----| | Testing with 1 record only | Misses bulkification bugs | Test with 200+ records | | No Test.startTest/stopTest | Governor limits from setup count against test | Wrap test logic in startTest/stopTest | | Using SeeAllData=true | Tests depend on org data, fail unpredictably | Use @TestSetup with test-specific data | | Hardcoded IDs | Break across orgs and sandboxes | Query for IDs or use TestDataFactory | | No downstream verification | Trigger fires but automation not verified | Assert on records created by automation | | Ignoring async results | Queueable/Batch results not checked | Test.stopTest forces execution, then assert |
Test.setMock(HttpCalloutMock.class, mock) -- see the sf-integration skill for mock patternssf-testing-constraints -- test isolation rules, assertion requirements, coverage gatesdevelopment
Update Salesforce platform reference docs with latest release features and deprecation announcements. Use when SessionStart hook warns docs are outdated or a new Salesforce release has shipped. Do NOT use for Apex or LWC development.
development
Use when syncing documentation after Salesforce Apex code changes. Update README, API docs, and deploy metadata references to match the current org codebase.
development
Use when managing context during long Salesforce Apex development sessions. Suggests manual compaction at logical intervals to preserve deploy and org context across phases.
tools
Visualforce development — pages, controllers, extensions, ViewState, JS Remoting, LWC migration. Use when maintaining VF pages, building PDFs, or planning VF-to-LWC migration. Do NOT use for LWC, Aura, or Flow.