Unit Testing for Real: Beyond the 75% Coverage Trap

Master Mocks and Stubs to test complex business logic with lightning speed, without ever touching the Salesforce database.

André Rödel

5/14/20263 min read

If you’ve been in the Salesforce ecosystem for a while, you know the "75% dance." It’s that moment before a major deployment where you’re frantically adding @isTest classes just to hit a number, often sacrificing actual quality for the sake of the deployment gate.

But here is the hard truth: Code coverage is not the same as code quality.

Most "Unit Tests" in Salesforce are actually Integration Tests. They insert records, fire triggers, perform SOQL queries, and eventually assert a result. While these are necessary, they are slow, brittle, and depend entirely on the database state. If you want to build truly scalable architecture, you need to learn how to test your logic in complete isolation.

Let’s talk about how to move beyond the database and start writing "Real" Unit Tests.

1. The Problem with the Database

Salesforce is a database-centric platform, so our first instinct is always to use insert and select. However, relying on DML in your tests carries a high price:

  • Execution Time: Inserting 200 Accounts and 400 Contacts just to test one if statement in a service class takes seconds. Multiply that by 1,000 tests, and your deployment takes hours.

  • Data Dependencies: Your tests shouldn't fail because a new Validation Rule or Mandatory Field was added to an object that isn't even relevant to the logic you are testing.

  • The "Side Effect" Jungle: DML triggers an avalanche of automation (Flows, Triggers, Managed Packages) that can interfere with the specific logic you are trying to validate.

2. Mocks vs. Stubs: The Secret Weapons

To test in isolation, we use "Test Doubles." In Apex, the two most common types are Stubs and Mocks.

The Stub

A Stub is a "dumb" object that provides canned answers to calls made during the test. If your service needs to ask a Selector class for a list of Opportunities, you don't perform a SOQL query. You tell the Stub: "Whenever someone asks for Opportunities, just return these three records I created in memory."

The Mock

A Mock is more sophisticated. It not only provides data but also remembers how it was called. It allows you to verify behavior: "Did my Service class actually call the 'EmailNotification' method? Was it called exactly once? Was the subject line correct?"

3. Making Code "Testable" (Dependency Injection)

You can't mock what you can't control. If your logic looks like this:

You are trapped. You must insert an Order to test this. To fix it, you need to "Inject" your dependencies:

Now, in your test, you can pass a Mock Selector that returns a list of items without ever touching the database.

4. How to Implement it: fflib and StubProvider

In modern Apex, we have two primary ways to handle this:

  1. The fflib (Apex Enterprise Patterns) Framework: This is the gold standard for large-scale Orgs. It comes with built-in support for Apex Mocks, making it incredibly easy to mock Selectors, Domain classes, and Services. If you are considering this approach, I’ve previously written a critical analysis of fflib’s scalability vs. overhead that can help you decide if it’s the right fit for your architecture.

  2. Apex Stub API (System.StubProvider): This is the native Salesforce way. It allows you to create a "Proxy" object that intercepts method calls at runtime. It’s more "bare-bones" than fflib but extremely powerful for custom architectures.

5. The Result: Speed and Confidence

When you shift from Integration Testing to "Real" Unit Testing, the change is transformative:

  • Test Suite Speed: Tests that run in memory are nearly instantaneous. You can run hundreds of tests in seconds.

  • Isolated Failures: If a test fails, you know exactly where the bug is. It’s in the logic, not in a broken Flow or a changed picklist value.

  • Architectural Discipline: Writing testable code forces you to follow Separation of Concerns (SoC). If your code is hard to test, it’s probably poorly designed.

Final Verdict

The 75% requirement is a floor, not a ceiling. By mastering Mocks and Stubs, you stop testing "the platform" and start testing "your logic." This is the difference between being a developer who just "hits the coverage" and an Architect who builds resilient, high-performance systems.

Stop inserting. Start Mocking.