Salesforce Testing in Apex: Writing Test Classes

Salesforce has a firm rule: no Apex code deploys to a Production org without test coverage. At least 75% of all Apex code must be covered by passing test methods before a deployment succeeds. This is not just a bureaucratic hurdle — testing saves your production org from broken automation, data corruption, and governor limit failures. Skilled Apex developers treat test classes as a first-class part of every feature they build.

Why Testing Matters

Imagine a trigger that automatically creates a follow-up task every time an Opportunity is closed. It works perfectly when one Opportunity closes. But what happens when a Data Loader batch closes 500 Opportunities at once? Without a proper bulk test, you might not discover the trigger violates governor limits until it crashes in production — affecting real customer data.

A well-written test class catches this before any code reaches production.

What Is a Test Class?

A test class is an Apex class annotated with @isTest. It contains test methods — methods annotated with @isTest (or the older testMethod keyword) that create test data, call the code being tested, and verify the results using assertions.

The Quality Control Analogy

  MANUFACTURING PLANT:
  Factory builds cars (your Apex code).
  QC Inspector tests brakes, lights, engine (your test methods).
  If brakes fail the test → car does not ship.

  SALESFORCE:
  Developer writes trigger (your Apex code).
  Test class verifies trigger behavior (your test methods).
  If test fails → code does not deploy to Production.

Anatomy of a Test Class

@isTest
public class OpportunityTriggerTest {

    // Setup method runs once before all tests in this class
    @TestSetup
    static void setupTestData() {
        Account testAccount = new Account(Name = 'Test Corp', Industry = 'Technology');
        insert testAccount;
    }

    @isTest
    static void testClosedWonCreatesCase() {
        // Step 1: Arrange — Get setup data and create an Opportunity
        Account acc = [SELECT Id FROM Account WHERE Name = 'Test Corp' LIMIT 1];

        Opportunity opp = new Opportunity(
            Name        = 'Test Deal',
            AccountId   = acc.Id,
            StageName   = 'Prospecting',
            CloseDate   = Date.today().addDays(30),
            Amount      = 500000
        );
        insert opp;

        // Step 2: Act — Change stage to Closed Won (this fires the trigger)
        Test.startTest();
        opp.StageName = 'Closed Won';
        update opp;
        Test.stopTest();

        // Step 3: Assert — Verify the trigger created a Case
        List<Case> createdCases = [SELECT Id, Subject, Priority
                                    FROM Case
                                    WHERE AccountId = :acc.Id];

        System.assertEquals(1, createdCases.size(),
            'Expected exactly one Case to be created.');
        System.assertEquals('High', createdCases[0].Priority,
            'Case priority should be High.');
        System.assert(createdCases[0].Subject.contains('Onboarding'),
            'Case subject should contain the word Onboarding.');
    }
}

The Three-Step Test Pattern

Every good test method follows this structure, often called Arrange, Act, Assert:

  • Arrange — set up all the data the test needs. Create records, set field values, prepare the starting state.
  • Act — call the code being tested. This is the operation that should trigger the behavior you want to verify.
  • Assert — verify the outcome. Check that the right records were created, updated, or deleted. Confirm field values are correct.

Test.startTest() and Test.stopTest()

Test.startTest() and Test.stopTest() are two crucial methods that bracket the code you are testing. They serve two purposes:

  • Fresh governor limits — the code between startTest and stopTest gets its own fresh set of governor limits, separate from the setup code. This ensures your test accurately simulates a production transaction.
  • Asynchronous executionTest.stopTest() forces all asynchronous operations (future methods, batch jobs, queueable jobs) to complete before the assertions run. Without it, asynchronous code would not finish before the test checks results.

System.assertEquals and Other Assertions

Assertions are the statements that verify your expected outcome matches the actual result. If an assertion fails, the test method fails and the whole deployment is blocked.

Assertion MethodWhat It Checks
System.assertEquals(expected, actual, message)Expected value equals actual value
System.assertNotEquals(val1, val2, message)Two values are NOT equal
System.assert(condition, message)A boolean condition is TRUE

Always include a descriptive message as the third parameter. When a test fails in production deployment, the message tells you exactly what went wrong without needing to re-read the code.

@TestSetup: Shared Data for All Test Methods

The @TestSetup annotation marks a method that runs once before any test method in the class executes. The data created inside @TestSetup is available to all test methods — but each test method gets a fresh copy of it (any changes one test makes are rolled back before the next test runs). This keeps tests isolated and avoids duplicating setup code in every method.

Test Data: Never Use Real Data

By default, test methods run in a completely isolated environment — they cannot see your real Salesforce records. This isolation is intentional. Tests create their own records, use them during the test, and those records are automatically deleted when the test finishes. Your production data is never touched.

There is one exception: you can add @isTest(seeAllData=true) to a test class to allow it to query real org data. Avoid this practice — it makes tests dependent on specific data existing in the org, which breaks when data changes.

Test Factories: Reusable Data Creation

When multiple test classes need the same standard records (an Account with certain fields, a standard Opportunity), developers build a Test Factory — a utility class with static methods that return pre-configured records.

@isTest
public class TestDataFactory {

    public static Account createAccount(String name, String industry) {
        Account acc = new Account(
            Name     = name,
            Industry = industry,
            Phone    = '+91-22-1234-5678'
        );
        insert acc;
        return acc;
    }

    public static Opportunity createOpportunity(Id accountId, String stage) {
        Opportunity opp = new Opportunity(
            Name      = 'Test Opportunity',
            AccountId = accountId,
            StageName = stage,
            CloseDate = Date.today().addDays(30),
            Amount    = 100000
        );
        insert opp;
        return opp;
    }
}

Any test class can now call TestDataFactory.createAccount('Infosys', 'Technology') instead of repeating the same Account setup code everywhere. When the required fields for Account change, you update only the factory — not every test class.

Running Tests and Checking Coverage

You run test classes from:

  • Developer Console — Test menu → Run All Tests or run specific test classes
  • VS Code Salesforce Extensions — right-click a test class and select Run Apex Tests
  • Setup → Apex Test Execution — queue and run tests from the browser

After running, view code coverage by going to Setup → Apex Classes → the specific class → Code Coverage column. Lines highlighted in red have no test coverage. Lines in blue are covered. Aim for 85% or higher in practice — the 75% minimum is the floor, not the goal.

Key Points

  • Salesforce requires at least 75% Apex code coverage to deploy to Production — test classes are mandatory, not optional.
  • Every test method follows the Arrange, Act, Assert pattern: set up data, execute the code, verify the result.
  • Use Test.startTest() and Test.stopTest() to give test logic its own governor limits and to complete asynchronous operations before assertions.
  • @TestSetup creates shared test data that each test method receives in a fresh, isolated state.
  • Build a Test Data Factory class to centralize record creation and keep test classes clean and maintainable.

Leave a Comment

Your email address will not be published. Required fields are marked *