DEV Community

Cover image for Apex Test Coverage in 2026: Rules, Gotchas, and How to Write Tests That Actually Help
Rohan Mehta
Rohan Mehta

Posted on Originally published at way2force.com

Apex Test Coverage in 2026: Rules, Gotchas, and How to Write Tests That Actually Help

If you've ever tried to push Apex code to a production org, you've met Salesforce's deployment gatekeeper: the mandatory test run. In 2026, those rules haven't relaxed at all — and that's deliberate. Tests aren't bureaucracy; they're how a shared, multi-tenant platform stays trustworthy when thousands of developers ship code every day.

Why Salesforce enforces test coverage

Salesforce executes your code alongside everyone else's in a multi-tenant architecture. A careless trigger on your org can't be allowed to undermine the platform's reliability for your users. Mandatory tests serve two purposes:

  1. Deployment safety. Production deployments require a passing test run. If your tests fail, your metadata stays in the sandbox. Simple as that.
  2. Regression insurance. Orgs evolve constantly — admins add flows, validation rules, and required fields. A solid test suite catches the breakage before your users do.

The coverage rules, precisely

Here is what the platform actually demands before a production deployment:

  • At least 75% of all Apex code in the org must be executed by tests.
  • Every trigger must have some coverage — an untested trigger blocks deployment even if the org-wide number looks fine.
  • All test methods must pass. No failures, no uncaught exceptions.
  • Test classes themselves don't count toward the 75%.

Notice what's missing from that list: any requirement that your tests be good. A test that executes code without asserting anything still inflates the percentage. Which brings us to the real problem.

Stop gaming coverage; start asserting

Coverage is a floor, not a finish line. A test without System.assert calls proves nothing except that your code doesn't crash on one narrow path. Meaningful tests assert outcomes:

  • Assert the business result, not the implementation detail. ("The opportunity moved to Closed Won" beats "the method returned true.")
  • Test the unhappy paths: validation failures, null inputs, and governor-limit-adjacent bulk sizes.
  • Cover every branch of your conditionals — both sides of each if/else and every ternary outcome.

Write tests you'd trust to run at 2 AM during a release window. We walk through the full setup in our test classes and code coverage guide.

Bulk-safe test data patterns

Tests that insert a single record and call it a day are lying to you. Real orgs process data in bulk — triggers fire on up to 200 records at a time. Build that reality into your tests from the start:

  • Create your own data. Never depend on existing org data (SeeAllData=true is a trap that makes tests fail in fresh sandboxes). Generate records that mirror real business scenarios, including related records.
  • Use @TestSetup. One setup method builds shared data once per test class, keeping tests fast and consistent.
  • Centralize with a Test Data Factory. A single utility class that produces Accounts, Contacts, and Opportunities with sensible defaults saves everyone on the team from reinventing fixtures.
  • Test in bulk. Insert 200 records, not one. That's where SOQL-in-loop bugs and governor-limit failures hide.
@isTest
private class AccountTriggerTest {
    @TestSetup
    static void makeData() {
        List<Account> accts = new List<Account>();
        for (Integer i = 0; i < 200; i++) {
            accts.add(new Account(Name = 'Test ' + i));
        }
        insert accts;
    }

    @isTest
    static void bulkInsertSetsIndustry() {
        Test.startTest();
        // trigger logic under test runs against the @TestSetup data
        Test.stopTest();

        Integer flagged = [SELECT COUNT() FROM Account WHERE Industry = 'Technology'];
        System.assertEquals(200, flagged, 'All bulk-inserted accounts should be flagged');
    }
}
Enter fullscreen mode Exit fullscreen mode

Test.startTest() and Test.stopTest() also reset governor limits and force asynchronous code — future methods, queueables, batch jobs — to run synchronously, which is essential for testing anything @future or scheduled.

Deployment gotchas that still bite in 2026

  • Coverage evaporates on edit. The moment you modify a class, its coverage data is cleared until tests run again. Re-run after every change.
  • Mixed DML errors. Creating a User and an Account in the same transaction blows up. Wrap user-context setup in System.runAs(), which also lets you verify sharing rules and profile behavior.
  • Run test classes individually when debugging. One class at a time makes failures far easier to isolate than a full-org run.
  • Aim above the minimum. 75% gets you deployed; targeting 90% or more across classes and triggers gives you headroom for the inevitable coverage dip when someone edits a class the night before a release.

Good tests are documentation that executes. They tell the next developer what the code is supposed to do — and they scream when it stops doing it. Write them like the next person reading them is you, six months from now, during an incident.

Originally published at Way2Force.

Top comments (0)