Salesforce Dictionary - Free Salesforce GlossarySalesforce Dictionary
Full Test Method entry
How-to guide

Writing effective Apex Test Methods

Writing a good test method is a five-piece skill: set up the data the test needs, exercise the code under test, assert the outcome, isolate from external dependencies, and integrate into the team CI pipeline. Each piece is mechanical on its own, but combining them well takes practice. Most Apex bugs that reach production are missed by tests that exist but do not assert deeply enough. The pattern below covers the standard Test-Driven Development discipline applied to Salesforce Apex.

By Dipojjal Chakrabarti · Founder & Editor, Salesforce DictionaryLast updated May 19, 2026

Writing a good test method is a five-piece skill: set up the data the test needs, exercise the code under test, assert the outcome, isolate from external dependencies, and integrate into the team CI pipeline. Each piece is mechanical on its own, but combining them well takes practice. Most Apex bugs that reach production are missed by tests that exist but do not assert deeply enough. The pattern below covers the standard Test-Driven Development discipline applied to Salesforce Apex.

  1. Set up test data with @testSetup or inline DML

    For each test class, decide whether the data is shared (use @testSetup on a method that runs once per class) or test-specific (insert the data inside each test method). For shared data, write a @testSetup method that creates a baseline set of Accounts, Contacts, custom records, and any custom settings the tests need. For test-specific data, create the records inline at the top of the test method. Invest in a TestDataFactory class with reusable helper methods (createAccount, createContact, createOpportunity) so each test method only writes the data that is specific to its scenario, not all the boilerplate. The factory pattern is the single biggest productivity gain in Apex testing.

  2. Exercise the code under test inside Test.startTest and Test.stopTest

    Wrap the code being tested in Test.startTest() and Test.stopTest() calls. These markers reset governor limits for the test (so setup work in earlier statements does not consume the test execution quota) and force any asynchronous Apex (Queueable, Schedulable, Future, Batch) to complete synchronously before Test.stopTest() returns. Without these markers, async work the test triggered runs after the assertion phase and may not finish before the test ends. Inside the markers, call the production code under test with whatever inputs match the scenario. Keep this section short and focused; complex setup logic belongs in @testSetup, not in the action phase.

  3. Assert outcomes deeply

    After Test.stopTest() returns, query the records the production code should have created or modified. Use Assert.areEqual, Assert.isTrue, Assert.isNotNull to validate the expected outcome. Check multiple aspects: the primary outcome (the new record was created), the side effects (related records were updated, child records exist, parent rollups recalculated), the field values (every important field has the right value), the count (exactly the expected number of records, not more or fewer). Each assertion adds protection against future regressions. Aim for at least three or four meaningful assertions per test method; a single assertion test is rarely thorough enough.

  4. Isolate external dependencies with mocks

    For tests on code that makes HTTP callouts, implement HttpCalloutMock and call Test.setMock(HttpCalloutMock.class, new MyMock()) before the action phase. The mock returns synthetic responses you control, which keeps the test fast, reliable, and independent of network conditions. For tests on code that depends on Apex classes you do not want to exercise directly, use the StubProvider interface to mock the dependency. For Custom Settings and Custom Metadata, create the values you need inside the test method (Custom Metadata test records require the @TestVisible annotation pattern or platform-specific helper). Mature test suites use mocks consistently; without them, tests become slow and flaky.

Gotchas
  • Test methods do not see existing org data. Every test starts with an empty data set; the test has to create the records it needs through Apex DML.
  • Async Apex (Queueable, Future, Batch) does not run during a normal test method. Wrap the action in Test.startTest and Test.stopTest to force async work to complete synchronously.
  • HTTP callouts are blocked during test execution. Use HttpCalloutMock to provide synthetic responses; without it, the callout throws CalloutException.
  • Code coverage is measured per line, not per logical path. A test that exercises a method with an if-else branch may cover the lines but miss either branch logic. Aim for branch coverage by writing multiple test methods per behavior.
  • Test classes count toward the org Apex code limit but not toward coverage. Excessively long test classes do not improve coverage and can hit the per-org code limit.

See the full Test Method entry

Test Method includes the definition, worked example, deep dive, related terms, and a quiz.