Plenty of orgs hit the 75 percent coverage requirement with tests that check nothing real at all.
Coverage counts which lines of code ran during a test, not whether the test verified anything meaningful. A test that inserts a record, calls a method, and confirms only that no exception was thrown will happily push coverage past 75 percent while catching zero actual regressions. Real Apex unit test best practices start from a different question: whether the test would fail the moment the underlying logic breaks, not whether it satisfies a percentage.
The gap between passing and protecting
A weak test looks productive. It compiles, it runs, it shows green in the deployment log, and the coverage number goes up. What it usually skips is any assertion tied to business outcome. It checks that a record exists, not that a field landed on the value the logic was supposed to produce. Six months later, someone changes a trigger, every existing test still passes, and the bug ships anyway — because nothing in the suite was actually watching for it.
Assert patterns that actually catch something
A test worth keeping asserts on the specific outcome the code is supposed to produce, not just on the absence of an error. That means asserting the field value that should have changed, the record count that should have resulted, or the exception that should have been thrown on a bad input. Here is a pair of tests for a trigger that updates a custom forecast category once an opportunity crosses an amount threshold.
@isTest
private class OpportunityRoundingTest {
@isTest
static void updatesForecastWhenAmountChanges() {
Account acc = new Account(Name = 'Test Account');
insert acc;
Opportunity opp = new Opportunity(
Name = 'Renewal Deal',
AccountId = acc.Id,
StageName = 'Prospecting',
CloseDate = Date.today().addDays(30),
Amount = 10000
);
insert opp;
Test.startTest();
opp.Amount = 25000;
update opp;
Test.stopTest();
Opportunity updated = [
SELECT Forecast_Category_Custom__c, Amount
FROM Opportunity WHERE Id = :opp.Id
];
Assert.areEqual(25000, updated.Amount,
'Amount should reflect the update');
Assert.areEqual('Best Case', updated.Forecast_Category_Custom__c,
'Forecast category should move once the deal crosses the threshold');
}
@isTest
static void doesNotDefaultAmountWhenLeftBlank() {
Account acc = new Account(Name = 'Test Account 2');
insert acc;
Opportunity opp = new Opportunity(
Name = 'Early Deal',
AccountId = acc.Id,
StageName = 'Prospecting',
CloseDate = Date.today().addDays(60)
);
Test.startTest();
insert opp;
Test.stopTest();
Opportunity result = [SELECT Amount FROM Opportunity WHERE Id = :opp.Id];
Assert.isNull(result.Amount,
'A deal with no amount should stay null, not default to zero');
}
}Bulk data catches what a single record never will
A test that inserts one record will pass even when the underlying code runs a query or a DML statement inside a loop, because one iteration never comes close to a governor limit. Two hundred records will. Building bulk data into a test is the difference between finding that bug in a sandbox and finding it in production during a mass import.
@isTest
static void handlesBulkInsertWithoutHittingLimits() {
List<Account> accounts = new List<Account>();
for (Integer i = 0; i < 200; i++) {
accounts.add(new Account(Name = 'Bulk Account ' + i));
}
Test.startTest();
insert accounts;
Test.stopTest();
List<Account> inserted = [
SELECT Id FROM Account WHERE Name LIKE 'Bulk Account%'
];
Assert.areEqual(200, inserted.size(),
'All 200 accounts should insert without hitting a governor limit');
}Mocking callouts without touching a real endpoint
Any test that reaches out to a real external system is slow, flaky, and eventually blocked by Salesforce itself, since live callouts aren't allowed inside a test context. The fix is the HttpCalloutMock interface, which lets a test hand back a fake response and check that the code parses and handles it correctly — success and failure alike.
@isTest
private class ShippingRateMockTest {
private class SuccessMock implements HttpCalloutMock {
public HttpResponse respond(HttpRequest req) {
HttpResponse res = new HttpResponse();
res.setStatusCode(200);
res.setBody('{"rate": 14.50, "carrier": "Standard"}');
return res;
}
}
@isTest
static void parsesRateFromSuccessfulCallout() {
Test.setMock(HttpCalloutMock.class, new SuccessMock());
Test.startTest();
ShippingRate rate = ShippingService.getRate('90210');
Test.stopTest();
Assert.areEqual(14.50, rate.amount,
'Rate should match the mocked response body');
Assert.areEqual('Standard', rate.carrier,
'Carrier should be parsed from the JSON response');
}
}Wiring tests into CI before anything merges
None of this holds up if tests only run when someone remembers to run them by hand. Wiring Apex tests into a CI pipeline means every pull request triggers a deploy to a scratch org or sandbox followed by a full test run, and a merge simply can't happen if a test fails or coverage drops below the threshold.
name: apex-tests
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sf org login sfdx-url --sfdx-url-file authFile.txt --alias ci-org
- run: sf project deploy start --target-org ci-org
- run: sf apex run test --target-org ci-org \
--code-coverage --result-format human --wait 20A coverage number without CI is a report nobody reads until deploy day. A coverage number wired into every pull request is a gate that actually stops a broken assumption from shipping.
Send your test suite our way through the contact form for a custom development and code review pass, and follow TrueSolv on LinkedIn for more Apex notes.