Apex Test Coverage in the Age of AI: Stop Writing Boilerplate
Most Apex teams still treat test coverage like a tax. They write enough boilerplate to hit 75%, merge the story, and pray the next admin change does not detonate production.
Here’s the unpopular take: AI should make that behavior impossible to justify in 2026.
With claude-sonnet-4-7, gpt-5.5, and solid local options like Llama 4 Scout, there is no reason for a senior Salesforce developer to hand-write repetitive test data setup from scratch. But there is also no excuse for shipping AI-generated tests that only assert that Salesforce did not throw an exception.
That is the line I draw with AI-assisted Apex testing:
- AI can draft fixtures.
- AI can identify branches.
- AI can propose negative cases.
- AI can generate boring boilerplate.
- I still own the assertions, risk model, mocks, and governor limit strategy.
If you searched for apex test coverage ai generated 2026, this is the practical version. Not “AI replaces developers.” Not “prompt your way to quality.” This is how I use AI to move faster without turning my Salesforce org into a test-shaped liability.
Coverage Was Never the Goal
Salesforce still requires Apex tests for deployment, and the platform still enforces coverage rules. But coverage is a crude deployment gate, not a quality strategy.
I care about four things more than the percentage:
- Does the test prove the business rule?
- Does it fail when the implementation is wrong?
- Does it cover bulk behavior?
- Does it isolate external dependencies?
A 92% coverage class with System.assert(true) is worse than a 78% class with strong assertions around money, permissions, ownership, and integration failure paths.
AI makes this distinction more important because model-generated Apex often looks clean. It creates test classes, factories, setup methods, and happy-path inserts. That can trick reviewers into thinking quality improved. It may have only improved formatting.
The Test Work AI Should Own
I use AI aggressively for the parts of Apex testing that are repetitive but not strategically valuable.
Good AI tasks:
- Generate
@TestSetuprecords from object relationships. - Build test factories for
Account,Contact,Opportunity, custom objects, and junctions. - Identify missing branches from an Apex class.
- Suggest negative test scenarios.
- Convert old inline test data into reusable factory methods.
- Draft mocks for callouts.
- Generate bulk tests for 1, 2, and 200 records.
- Explain why a test is fragile.
Bad AI tasks:
- Deciding what the business should assert.
- Inventing object model details.
- Guessing validation rule behavior.
- Writing tests without source code context.
- Creating fake permissions instead of using real permission set assumptions.
- Ignoring async, sharing, and limits.
My rule: AI writes the first draft. I write the final meaning.
A Bad AI-Generated Test Looks Fine Until It Matters
This is the type of test I still see in enterprise orgs. Sometimes humans write it. Sometimes AI writes it faster.
@IsTest
private class CaseEscalationServiceTest_Bad {
@IsTest
static void testEscalatesCase() {
Account acct = new Account(Name = 'Acme Manufacturing');
insert acct;
Contact con = new Contact(
LastName = 'Buyer',
AccountId = acct.Id,
Email = 'buyer@example.com'
);
insert con;
Case c = new Case(
Subject = 'Machine down',
Status = 'New',
Priority = 'High',
ContactId = con.Id,
Origin = 'Web'
);
insert c;
Test.startTest();
CaseEscalationService.escalateHighPriorityCases(new Set<Id>{ c.Id });
Test.stopTest();
System.assert(true, 'Code executed');
}
}This gets coverage. It proves almost nothing.
It does not prove the case was escalated. It does not prove the owner changed. It does not prove a platform event was published. It does not prove the method handles 200 cases. It does not prove low-priority cases are ignored.
That is boilerplate pretending to be quality.

A Better Pattern: Give AI the Boring Work, Then Tighten the Assertions
Here is the kind of test structure I want AI to help produce. Notice the difference: setup is reusable, behavior is explicit, and assertions map to business outcomes.
@IsTest
private class CaseEscalationServiceTest {
@TestSetup
static void setupData() {
Group supportQueue = new Group(
Name = 'Tier 2 Support',
DeveloperName = 'Tier_2_Support',
Type = 'Queue'
);
insert supportQueue;
QueueSobject queueObject = new QueueSobject(
QueueId = supportQueue.Id,
SobjectType = 'Case'
);
insert queueObject;
Account acct = new Account(Name = 'Acme Manufacturing');
insert acct;
Contact con = new Contact(
LastName = 'Buyer',
AccountId = acct.Id,
Email = 'buyer@example.com'
);
insert con;
}
@IsTest
static void escalatesHighPriorityWebCasesToTier2Queue() {
Contact con = [SELECT Id FROM Contact WHERE Email = 'buyer@example.com' LIMIT 1];
Group queue = [SELECT Id FROM Group WHERE DeveloperName = 'Tier_2_Support' LIMIT 1];
Case c = new Case(
Subject = 'Production machine down',
Status = 'New',
Priority = 'High',
Origin = 'Web',
ContactId = con.Id
);
insert c;
Test.startTest();
CaseEscalationService.escalateHighPriorityCases(new Set<Id>{ c.Id });
Test.stopTest();
Case updated = [
SELECT Status, OwnerId, Priority, Origin
FROM Case
WHERE Id = :c.Id
];
System.assertEquals('Escalated', updated.Status, 'High priority web case should be escalated');
System.assertEquals(queue.Id, updated.OwnerId, 'Case should be assigned to Tier 2 queue');
System.assertEquals('High', updated.Priority, 'Priority should not be downgraded');
}
@IsTest
static void ignoresLowPriorityCases() {
Contact con = [SELECT Id FROM Contact WHERE Email = 'buyer@example.com' LIMIT 1];
Case c = new Case(
Subject = 'Question about invoice',
Status = 'New',
Priority = 'Low',
Origin = 'Web',
ContactId = con.Id
);
insert c;
Test.startTest();
CaseEscalationService.escalateHighPriorityCases(new Set<Id>{ c.Id });
Test.stopTest();
Case updated = [
SELECT Status, OwnerId
FROM Case
WHERE Id = :c.Id
];
System.assertEquals('New', updated.Status, 'Low priority case should not be escalated');
}
@IsTest
static void handlesBulkEscalationWithoutExtraDmlPerRecord() {
Contact con = [SELECT Id FROM Contact WHERE Email = 'buyer@example.com' LIMIT 1];
List<Case> cases = new List<Case>();
for (Integer i = 0; i < 200; i++) {
cases.add(new Case(
Subject = 'Bulk outage ' + i,
Status = 'New',
Priority = 'High',
Origin = 'Web',
ContactId = con.Id
));
}
insert cases;
Set<Id> caseIds = new Map<Id, Case>(cases).keySet();
Test.startTest();
Integer dmlBefore = Limits.getDmlStatements();
CaseEscalationService.escalateHighPriorityCases(caseIds);
Integer dmlUsed = Limits.getDmlStatements() - dmlBefore;
Test.stopTest();
System.assert(
dmlUsed <= 2,
'Escalation should be bulkified and not perform DML per Case'
);
Integer escalatedCount = [
SELECT COUNT()
FROM Case
WHERE Id IN :caseIds
AND Status = 'Escalated'
];
System.assertEquals(200, escalatedCount, 'All high priority cases should be escalated');
}
}This is where AI helps. I do not want to type the 200-record loop again. I do not want to manually scaffold ten similar object graphs. I want the model to generate that, then I inspect the assertions like an architect.
The Prompt Contract I Use for Apex Test Generation
Most bad AI-generated tests come from vague prompts.
“Write test coverage for this Apex class” is not enough. That invites generic setup, fake assumptions, and weak assertions.
I use a prompt contract. It tells the model what it can do, what it must not invent, and what output I expect.
Here is a TypeScript example using Anthropic’s current API version and claude-sonnet-4-7. I use this style in internal tooling when I want repeatable output from source files.
import Anthropic from "@anthropic-ai/sdk";
import fs from "node:fs/promises";
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
const apexClass = await fs.readFile("./force-app/main/default/classes/CaseEscalationService.cls", "utf8");
const objectNotes = await fs.readFile("./docs/object-model-case-escalation.md", "utf8");
const response = await anthropic.messages.create({
model: "claude-sonnet-4-7",
max_tokens: 5000,
temperature: 0.2,
system: [
"You are a senior Salesforce Apex test engineer.",
"Generate Apex tests for Salesforce API v64.0.",
"Do not invent fields, validation rules, permission sets, queues, or record types.",
"Prefer @TestSetup, reusable factories, strong assertions, bulk tests, and negative tests.",
"Every test method must assert business outcomes, not just code execution.",
"If required metadata is missing, output TODO comments instead of guessing."
].join("\n"),
messages: [
{
role: "user",
content: `
Apex class:
${apexClass}
Known object model and business rules:
${objectNotes}
Return:
1. A complete @IsTest class.
2. A list of assumptions.
3. A list of missing edge cases.
4. Any production-code testability issues.
`
}
]
});
console.log(response.content);If I use OpenAI for the same workflow, I use gpt-5.5 with the same constraints. I do not ask models to be creative with tests. I want boring, deterministic, reviewable output.
Low temperature. Explicit rules. Source code plus object model context. No guessing.
That last part matters in Salesforce because metadata is the system. A model that does not know your validation rules, required fields, duplicate rules, flows, triggers, platform events, and record-triggered automation is working with partial truth.
Enterprise Example: Case Escalation in a Manufacturing Support Org
On one enterprise Service Cloud project, we had a support org for industrial equipment customers. Cases came from Experience Cloud, email-to-case, and a partner portal. High-priority web cases for premium accounts needed to be escalated to a Tier 2 queue, notify an integration layer, and create an audit record.
The original test suite had good coverage on paper. Around 88%.
The problem: most tests only validated that records inserted successfully. Then the business changed the escalation rule to include premium entitlement status. The class still deployed. Tests passed. Production behavior was wrong for a subset of partner-created cases.
We refactored the tests around scenarios instead of methods:
- Premium account, high priority, web origin: escalate.
- Standard account, high priority, web origin: do not escalate.
- Premium account, low priority, web origin: do not escalate.
- Premium account, high priority, email origin: route differently.
- 200 premium high-priority cases: one bulk update path.
- Integration failure: case still escalates, audit record stores failure.
AI helped generate the scenario matrix and initial test data factories. It saved real time. But the important work was human: deciding that entitlement status and origin were risk dimensions.
That is the difference between code coverage and business coverage.
Use Factories, But Do Not Build a Monster
Apex test factories are useful until they become another framework nobody wants to touch.
I prefer small, explicit factories with safe defaults and override methods. AI is good at producing these quickly.
@IsTest
public class SupportTestDataFactory {
public static Account premiumAccount(String name) {
return new Account(
Name = name,
Type = 'Customer',
Customer_Tier__c = 'Premium'
);
}
public static Contact contactFor(Account acct, String email) {
return new Contact(
LastName = 'Support Contact',
Email = email,
AccountId = acct.Id
);
}
public static Case supportCase(Id contactId, String priority, String origin) {
return new Case(
Subject = 'Factory generated support case',
Status = 'New',
Priority = priority,
Origin = origin,
ContactId = contactId
);
}
public static List<Case> supportCases(Id contactId, Integer count, String priority, String origin) {
List<Case> cases = new List<Case>();
for (Integer i = 0; i < count; i++) {
cases.add(new Case(
Subject = 'Factory generated support case ' + i,
Status = 'New',
Priority = priority,
Origin = origin,
ContactId = contactId
));
}
return cases;
}
}The anti-pattern is a factory with 40 optional parameters, hidden inserts, and object creation side effects nobody remembers. I want factories that make tests shorter, not mysterious.
My preference:
- Factory methods should return records, not always insert them.
- Method names should describe business meaning.
- Defaults should be valid but obvious.
- Tests should still show the scenario clearly.
- Avoid “magic” factories that create half the org.
AI will happily over-engineer factories if you let it. Tell it not to.

Where Agentforce 2.0 Changes the Testing Conversation
Agentforce 2.0 and the Atlas Reasoning Engine v2 push more enterprise logic into agent actions, orchestration, and custom reasoning steps. That does not remove Apex testing. It increases the need for clean boundaries.
If an agent action invokes Apex, I want that Apex tested like any other service layer:
- What inputs are valid?
- What happens when the user lacks access?
- What records can the action mutate?
- What audit trail is written?
- What happens when the downstream system times out?
- Can the method handle bulk or repeated invocation?
The mistake is treating AI agents as a separate universe from Salesforce engineering discipline. They are not. If Agentforce calls Apex, the Apex still needs deterministic tests.
For agent-facing Apex, I also push harder on negative tests because bad inputs are normal. Agent workflows can pass incomplete context. Users can phrase intent ambiguously. Permissions can differ across channels.
Here is a simple pattern I like: keep the invocable boundary thin and test the service class deeply.
public with sharing class CaseEscalationAgentAction {
public class Request {
@InvocableVariable(required=true)
public Id caseId;
}
public class Response {
@InvocableVariable
public Boolean escalated;
@InvocableVariable
public String message;
}
@InvocableMethod(label='Escalate Support Case')
public static List<Response> escalate(List<Request> requests) {
Set<Id> caseIds = new Set<Id>();
for (Request req : requests) {
if (req != null && req.caseId != null) {
caseIds.add(req.caseId);
}
}
CaseEscalationService.EscalationResult result =
CaseEscalationService.escalateHighPriorityCases(caseIds);
Response res = new Response();
res.escalated = result.escalatedCount > 0;
res.message = result.message;
return new List<Response>{ res };
}
}I do not want massive logic inside the invocable action. I want the action to translate agent input into a service call. Then I test the service with normal Apex tests and add a few boundary tests for nulls, invalid IDs, and permission behavior.
My AI Review Checklist for Apex Tests
When AI generates a test class, I do not review it like normal code only. I review it like suspicious code that happens to compile.
My checklist:
1. Are the assertions meaningful?
Every test should assert a business result. Status changed. Owner changed. Amount calculated. Error logged. Record not created. Event published. Permission denied.
If the only assertion is that something is not null, I push back.
2. Did the model invent metadata?
This is common. AI will create fields like Tier__c, Region__c, or Is_Premium__c because they sound plausible.
I only accept generated tests against known schema. In a real pipeline, give the model object metadata, field lists, and validation rule notes. Salesforce API v64.0 makes metadata extraction straightforward if you wire it into your toolchain.
3. Does it test bulk behavior?
If production code processes records, the test needs a 200-record path. Not 3 records. Not 10. Two hundred.
Governor limit bugs hide in small tests.
4. Does it isolate callouts?
No real HTTP callouts in tests. Use mocks. If the class is not mockable, that is a production-code design problem.
5. Does it account for automation?
In enterprise orgs, Apex is rarely alone. Flow, validation rules, duplicate rules, assignment rules, and Agentforce actions can all affect outcomes. AI needs that context or it will produce fragile tests.
6. Is the test readable?
A test is documentation. If I cannot understand the scenario in 30 seconds, it is too clever.
Add AI to the Pipeline, But Keep a Human Gate
I do not recommend letting AI commit test code directly. I prefer a pull-request assistant workflow.
A useful flow:
- Developer opens PR with Apex changes.
- Tool detects changed
.clsfiles. - AI reviews branches and existing test coverage.
- AI proposes new test methods or factory updates.
- Developer accepts, edits, or rejects.
- CI runs Apex tests in the target scratch org or sandbox.
- Human reviewer checks assertions and risk coverage.
For CI, the old quality gates are not enough. I like adding review checks such as:
- No
System.assert(true). - No test method without at least one assertion.
- No hardcoded production record IDs.
- No
SeeAllData=trueunless explicitly approved. - Bulk test required for service methods accepting collections.
- Mock required for callout paths.
Some of these can be static checks. Some require reviewer discipline. AI can help detect them, but it should not be the final authority.
What I Still Write by Hand
Even with strong AI tooling, I still write these parts myself:
- The test method names.
- The most important assertions.
- The negative business scenarios.
- The permission and sharing assumptions.
- The integration failure expectations.
- The deployment risk notes.
Why? Because those encode intent. AI can infer intent from code, but code often reflects yesterday’s misunderstanding. Enterprise Salesforce work is full of invisible business rules.
The model can see the Apex class. It cannot attend the steering committee call where the VP of Support said premium customers must never wait behind standard warranty claims.
That detail belongs in the test.
Final Position
AI-generated Apex tests are not a shortcut around engineering discipline. They are a shortcut around typing.
That is a big win if you use it correctly.
In 2026, I expect Salesforce developers to use AI for test scaffolding the same way I expect them to use autocomplete. But I also expect architects and senior developers to be ruthless about assertion quality.
The worst outcome is not that AI writes bad tests. Humans have been doing that for years.
The worst outcome is that AI writes bad tests that look professional enough to avoid scrutiny.
Do not optimize for coverage. Optimize for confidence. Let AI remove boilerplate. Keep ownership of correctness.
TL;DR
- Use AI to generate Apex test setup, factories, branch ideas, and bulk scaffolding — not to decide business truth.
- Strong AI-generated tests need explicit prompts, real metadata context, meaningful assertions, and human review.
- In 2026, coverage without confidence is still technical debt, just generated faster.
Salesforce Certified Application Architect · 9+ years · Building AI agents & SaaS products.
