๐ Key Points
- Coverage Is Not Quality: Code coverage only counts the lines of code executed. It does not guarantee that the logic is correct.
- Assertions Are Mandatory: Use the modern
Assertclass (e.g.,Assert.areEqual()) to verify expected outcomes. - Bulkification Testing: Always test how your code handles 200 records at once, not just a single record.
- Negative Testing: Ensure your tests intentionally trigger and catch expected exceptions (like validation rule failures).
- Context Matters: Use
System.runAs()to verify that sharing rules and Field-Level Security (FLS) are strictly enforced.
Imagine a Salesforce org that proudly boasts 92% overall code coverage. Yet, a massive pricing bug still slips into production.
The Old/Bad Way: Developers write tests that merely invoke methods. One "test" executes a batch job and immediately finishes without a single assertion.
Why it fails: The platform counts the executed lines, but no behavior is verified. An assert-free test is like installing a smoke detector without a battery.
The Modern/Good Way:
- Every single test explicitly asserts an outcome:
Assert.areEqual(expectedPrice, quote.Total__c, 'Pricing calculation failed'); - Tests insert 200 records to prove bulkification limits hold up.
- Tests intentionally pass bad data to prove the correct error is thrown.
- CI/CD pipelines automatically reject pull requests containing assert-free tests.
Concept: Why Coverage Statistics Lie
In the Salesforce ecosystem, 75% code coverage is a hard deployment gate enforced by the platform. Unfortunately, many teams treat this number as a quality metric. It is not.
- Coverage strictly counts lines executed. It never checks if the behavior is correct.
- A test that lacks assertions—or one that only asserts that no exception was thrown—can easily green-light entirely broken business logic.
- When architects and interviewers look at your code, they are looking for meaning. They want to see detailed assertions with clear failure messages.
- They want to see boundary testing, permutation coverage of complex rules, and
System.runAs()blocks protecting sensitive data access.
- Rule: 75% is the entry ticket, not the end goal. The real goal is a test that turns red when the behavior breaks.
- Gain: A test suite you implicitly trust allows you to deploy on a Friday afternoon without panic. That peace of mind is the true ROI.
- Price: Meaningful tests take substantial time to write. Furthermore, the overall coverage percentage is often the only metric non-technical leadership understands.
- Limits: While 75% org-wide is the minimum, every Apex trigger needs at least some coverage. But remember, coverage blindly counts execution, not verification.
- The Mirror: Chasing an arbitrary 90% target blindly leads to a surge in assert-free tests. The metric climbs, but production keeps breaking.
- The Fix: Shift your target from overall percentage to "assertion density" in changed code, ensuring zero assert-free tests make it into the codebase.
Core Q&A
A: Start by sampling the test suite to identify common pathologies:
- Assert-Free Tests: Search the codebase for test methods missing modern
Assertmethods. You will often find they make up a massive portion of a legacy suite. - Happy-Path Monoculture: Look for a lack of negative testing. If error handling and validation logic are never exercised, bugs will slip through.
- Single-Record Bias: Check if triggers are only tested with one record, rendering bulkification bugs invisible.
- Missing Context: Check for missing
System.runAs()usage. If every test runs as a System Administrator, FLS and sharing rule regressions will bypass your tests entirely.
Once audited, you must re-anchor the development team's definition of a unit test. A test exists to fail when business rules regress. Introduce code review requirements that strictly enforce assertion density and perform "mutation" spot checks (intentionally breaking code in a sandbox to ensure a test actually fails).
A: A robust trigger test suite covers five mandatory categories. Completeness of these scenarios is far more important than just having a high volume of tests:
- Bulk Testing: Insert or update 200 records at once, and assert the expected outcome on all of them, not just the first record in the list.
- Branch Coverage: Write a single-record test for each major business logic branch so that when a failure occurs, it is highly readable and isolated.
- Negative Testing: Intentionally trigger validation rules or errors. Catch the
DmlExceptionand assert that the correct error message was surfaced to the user. - Recursion Protection: Create an update scenario that forces the trigger to fire twice, proving that your recursion guard mechanism works perfectly.
- Security Context: Run a test inside a
System.runAs()block using a standard, minimally-privileged user to prove the path respects real-world sharing and FLS.
// Example of a proper negative assertion using modern Apex syntax
@isTest
static void testDiscountValidation_NegativePath() {
Opportunity opp = new Opportunity(Name = 'Test', StageName = 'Prospecting', Discount__c = 150);
Test.startTest();
try {
insert opp;
Assert.fail('Expected a DmlException due to invalid discount');
} catch (DmlException e) {
Assert.isTrue(e.getMessage().contains('Discount cannot exceed 100%'), 'Wrong error message returned');
}
Test.stopTest();
}
A: You must redirect the metric toward things that actually predict system safety.
- Keep the 75%+ as a baseline floor, because the Salesforce platform demands it for deployment.
- Instead of raw coverage, set the OKR around Assert Density in all newly changed code.
- Implement a CI/CD linting rule that enforces zero new assert-free tests.
- Mandate that the top 5 critical business processes have complete critical-path test suites (incorporating the five categories mentioned above).
- Explain "Goodhart's Law" plainly to leadership: When a measure (like 90% coverage) becomes a target, it ceases to be a good measure. Developers will inevitably game the system by writing assert-free "coverage filler," wasting time making a number look good rather than making the system stable.
A: Automated enforcement is the only way to scale test quality across a growing team.
- Static Code Analysis: Integrate tools like PMD or SonarQube into your GitHub Actions, Bitbucket Pipelines, or Jenkins flow. Configure rules that flag test methods missing
Assertstatements. - Automated Pull Request Blocking: If the static analysis fails, the PR cannot be merged into the integration branch.
- Delta Testing: Use tools that run tests only on the modified code components. This speeds up the pipeline while ensuring developers are heavily scrutinizing the specific tests related to their immediate changes.
A: A mature, architect-level test strategy requires five distinct layers:
- Unit Tests: Assert core behavior using a Test Data Factory. Never rely on production data, and never use
@isTest(SeeAllData=true). - Mocked Integrations: Use
HttpCalloutMockso the suite runs predictably without relying on the uptime of real external endpoints. - Flow Automation Tests: Utilize Salesforce's native Flow Tests for declarative logic, reserving Apex-driven tests for complex orchestration.
- UI Automation: Implement targeted Selenium or Playwright tests exclusively over critical business paths. (Attempting 100% UI coverage is a massive maintenance trap).
- Performance & UAT: Run volume tests in a Full Copy Sandbox before major go-lives, paired with business sign-off via scripted User Acceptance Testing (UAT) scenarios.