Test Method
A Test Method in Salesforce Apex is a method annotated with @isTest that verifies the behavior of production Apex code.
Definition
A Test Method in Salesforce Apex is a method annotated with @isTest that verifies the behavior of production Apex code. Each test method sets up the data the test needs, executes the operation under test, and uses System.assert methods (Assert.areEqual, Assert.isTrue, Assert.fail) to confirm the result matches expectations. Test methods live inside test classes, which themselves carry the @isTest annotation at the class level. The Salesforce platform requires at least 75 percent code coverage across all triggers and most Apex classes before any deployment to production, which makes test methods a mandatory part of every Salesforce development workflow.
Test methods run in a special test execution context that isolates them from the rest of the org. The platform inserts the test data, runs the test, and rolls back every record change at the end so the test does not pollute production data. This isolation lets developers run thousands of test methods without worrying about side effects, and lets CI pipelines run the full test suite on every commit.
Test Methods in Apex: structure, data, assertions, and CI integration
Test class and test method structure
A test class is an Apex class with @isTest at the class level. Inside it, each test method is annotated with @isTest at the method level (or, in older code, declared as static testMethod void method-name). Test methods must be static and must return void. They cannot take parameters. The body of a test method usually follows a three-section pattern: setup (insert test data, configure custom settings, mock external service callouts), action (call the production code under test), and assert (verify the result with System.assert calls). The pattern is the Arrange-Act-Assert convention from broader software testing, applied to the Salesforce Apex context. Each test method covers one specific behavior or scenario; many small focused tests beat a few large ones.
Test data and the @testSetup annotation
Test methods do not see records that exist in production; each test runs in isolation with no pre-existing data. The test method has to create the data it needs through Apex DML statements (insert account = new Account()). For tests that share common setup data, the @testSetup annotation on a separate method runs the setup once per class and shares the resulting data across every test method in that class. This is more efficient than recreating the same data in every test method. The platform automatically rolls back all test-created records at the end of the test class, so even @testSetup data does not persist. Test data creation is the largest source of boilerplate in Apex testing; teams that invest in test data factory classes save substantial time.
Assertions and what counts as a real test
A test method without assertions is not a real test; it just runs the code and confirms no exceptions were thrown. The Salesforce Code Analyzer flags this as a code quality issue. Real tests assert specific outcomes: Assert.areEqual(expectedValue, actualValue) for value comparison, Assert.isTrue(condition) for boolean check, Assert.areNotEqual to confirm a difference, Assert.fail to mark the test as failed unconditionally. The newer Assert class (introduced in Spring 24) replaced the older System.assertEquals with a more readable API. Mature tests check multiple aspects of the result: not just the primary outcome but also side effects, related records, field updates on parent records, and any data the platform created during the operation. The richer the assertions, the more useful the test for catching regressions.
Code coverage and the 75 percent rule
Salesforce requires at least 75 percent code coverage across all Apex code in the org before any deployment to production. Coverage is measured at the line level: each executable line in production Apex must be exercised by at least one test method. The 75 percent threshold is org-wide, but each individual trigger must have at least one test method that exercises it (not necessarily 75 percent coverage on the trigger itself). Test methods themselves do not count toward coverage. The Developer Console, the Salesforce CLI (sf apex run test), and the Salesforce Setup Apex Test Execution page all report coverage per class and per trigger. CI pipelines should fail any commit that drops coverage below the threshold; without this gate, coverage degrades over time.
Mocking, stubbing, and external dependencies
Real production code often calls external services (HTTP callouts, custom metadata, custom settings, Platform Events). Tests cannot make real callouts during test execution (the platform blocks them) and benefit from controlled stubs even for internal dependencies. Salesforce provides several mocking mechanisms. HttpCalloutMock interface lets tests provide synthetic HTTP responses. Test.setMock() activates a mock implementation for the duration of a test. The Stub API (StubProvider interface) lets tests provide synthetic implementations of any Apex class for the duration of the test. Custom Settings and Custom Metadata can be created in the test method to control configuration-driven behavior. Mature test suites use mocks consistently to isolate the code under test from external dependencies; this makes tests faster, more reliable, and more focused on the actual behavior being verified.
Test execution, the CI pipeline, and the operational concerns
Test methods run in several contexts. From Developer Console, an Apex developer runs them interactively. From the Salesforce CLI (sf apex run test), CI pipelines run the full suite on every commit. From the Setup Apex Test Execution page, admins run tests as part of deployment validation. Tests run synchronously (up to 30 minutes max) or asynchronously through Apex Hours. Mature Salesforce development pipelines run the full Apex test suite on every PR, fail the build on any test failure or coverage drop, and require a passing test run before merge. Some teams also run mutation testing (flip a line of production code, confirm at least one test fails) to verify test quality. Test quality is the single biggest determinant of refactor confidence; teams with good tests can ship changes quickly.
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.
- 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.
- 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.
- 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.
- 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.
- 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.
Trust & references
Straight from the source - Salesforce's reference material on Test Method.
- Testing Apex CodeSalesforce Developer Docs
- @testSetup MethodSalesforce Developer Docs
- Apex Stub APISalesforce Developer Docs
About the Author
Dipojjal Chakrabarti is a B2C Solution Architect with 29 Salesforce certifications and over 13 years in the Salesforce ecosystem. He runs salesforcedictionary.com to help admins, developers, architects, and cert/interview candidates sharpen their fundamentals. More about Dipojjal.
Test your knowledge
Q1. What is a Test Method?
Q2. What makes a good test method?
Q3. Do test methods persist data?
Discussion
Loading discussion…