Apex Testing and Deployment

(4.8)
2717 Viewers

This tutorial walks you through the entire lifecycle of testing and deploying Apex code in Salesforce. We cover why testing matters, how to write test classes with real examples, what code coverage actually means and how it is calculated, how assertions work to validate your logic, and the rules Salesforce enforces before letting anything into production. Every section includes code you can run in your own org.

Apex Testing and Deployment
  • Blog Author:
    Kalla SaiKumar
  • Last Updated:
    08 May 2026
  • Views:
    2717
  • Read Time:
    33:32 Minutes
  • Share:
Salesforce Articles

Here is something that catches a lot of new Salesforce developers off guard.

You can write the most elegant, well-structured Apex trigger in the world. It can handle bulk data perfectly. It can process edge cases that no one even thought of. But none of that matters if you cannot get it into production.

And Salesforce will not let you deploy a single line of Apex to production unless your test classes meet a very specific bar. 

“According to Salesforce's own fiscal year 2025 earnings release, the company now serves over 150,000 customers, with roughly 90% of Fortune 500 companies running their operations on the platform. That is a massive amount of custom code sitting in production orgs around the world. And every single piece of it had to pass Salesforce's testing requirements before it got there”.

IDC's 2025 Worldwide Semiannual Software Tracker has ranked Salesforce as the #1 CRM provider for the 12th consecutive year, thereby holding a 20.7% global market share. Also, the platform has generated $37.9 billion in revenue for fiscal year 2025, according to their official investor relations page. 

Additionally, their SEC filing for Q3 fiscal 2026 raised the complete year revenue guidance to almost $41.45 billion. In a separate SEC filing from October 2025, Salesforce also announced a long-term revenue target of $60 billion, by fiscal 2030.

Table of Contents

Why am I Bringing this up in a Testing Tutorial?

The quality of code and testing is crucial in Apex programming. The deployment fails for many reasons, including:

  • The test coverage is less than 75%. 
  • A single test method throws an unhandled exception.
  • The assertions do not pass.

The production deployment may fail at any time if you don't complete a test, even for a trigger.

This tutorial covers Apex Testing and Deployment, including the fundamentals of Salesforce testing, how to write test classes, code coverage percentages, and more.

Let's get into it.

Why Testing Matters in Salesforce?

In most programming environments, testing is a best practice. In Salesforce, it is a hard requirement. You literally cannot skip it. The platform will block your deployment if the tests do not meet the threshold.

There are a couple of reasons Salesforce takes this approach.

Multi-tenant architecture: Your org shares server resources with thousands of other orgs. Salesforce needs to ensure that the code running on their infrastructure actually works before it goes live. Broken code in production does not just affect your org. It can impact performance for everyone on the same server pod.

Data integrity: Salesforce orgs store business critical data like financial records, customer information, healthcare data, legal documents. Untested code that modifies this data can cause serious damage that is extremely expensive to undo.

AppExchange distribution: Let’s say you are building a managed package for the AppExchange. According to Salesforce, Apex code in the package should have a minimum of 75% code coverage. Without that, the security review will not even begin.

Two Ways to Test

The source material from MindMajix describes two fundamental approaches to testing in Salesforce.

  1. User interface testing: You can manually click through the application, create records, trigger workflows, as well as verify results on screen. This works for quick checks but does not scale and cannot be automated.
  2. Bulk testing: You write Apex test methods that create up to 200 records programmatically and verify that your code handles them correctly. This is the approach Salesforce requires, and it is what we focus on in this tutorial.

Rules Salesforce Enforces

Well, before any Apex code is deployed to production or distributed through the AppExchange, these conditions need to be satisfied.

  • Unit tests must cover a minimum of 75% of your Apex code. This is an org wide average, not per class.
  • All test methods must complete without throwing unhandled exceptions.
  • Every trigger must have some test coverage. Even 1% is technically sufficient per trigger, but the overall 75% average still applies.
  • System.debug statements are not counted as part of Apex code coverage. Writing debug logs does not help your percentage.
  • Test classes and test methods themselves are not counted toward the coverage total.

For structured, hands-on training that walks you through all of this with real projects, MindMajix's Salesforce training program covers testing, code coverage strategies, and production deployment in depth.

Writing Test Classes in Salesforce

Think of a test class as a separate Apex class that has one job and one job only. It tests your actual production code. It does not do anything for the business. No records get created for real users. No emails go out and no workflows fire. 

  • Anatomy of a Test Class: Every test class is built around the same basic structure, and once you have written two or three of them, the pattern becomes second nature. The first thing you will notice is the @isTest annotation sitting right at the top of the class. It is basically a flag that tells Salesforce "hey, this class is not production code, do not treat it like one." 
  • Test methods: Each method inside the class tests a specific scenario. Methods are declared with the static testMethod keyword or the @isTest annotation.
  • Test data creation: You can create records according to the test needs inside each method. Test methods do not have access to real org data by default (unless you use @isTest(SeeAllData=true), which is generally discouraged).
  • DML and assertions: Here, you can insert your test data, trigger the code you want to test, and then use assertion methods to verify the results.

MindMajix YouTube Channel

A Complete Working Example

Let us see the example from the source material. We have a custom object called Levis__c (think of it as a jeans inventory tracker), an Apex class that processes records, and a trigger that fires on insert.

The Apex Class:

public class JeanClassDemonstartion {
    public static void processJeans(List<Levis__c> jeansList) {
        for (Levis__c jean : jeansList) {
            if (jean.Price__c >= 1000) {
                jean.Price__c = jean.Price__c - 100;
            }
        }
    }
}

This class takes a list of jeans records, checks if the price is 1000 or above, and applies a 100 rupee discount.

trigger JeanTrigger on Levis__c (before insert) {
    JeanClassDemonstartion.processJeans(Trigger.new);
}

Fires before insert and passes the incoming records to the class for processing.

The Test Class:

@isTest
public class JeanClassDemonstartionTest {

    static testMethod void testDiscountApplied() {
        // Create a record with price above 1000
        Levis__c j = new Levis__c();
        j.Name = 'Louis';
        j.Price__c = 1200;

        Test.startTest();
        insert j;
        Test.stopTest();

        // Retrieve the record from the database
        Levis__c result = [SELECT Price__c FROM Levis__c WHERE Id = :j.Id];

        // Verify the discount was applied
        System.assertEquals(1100, result.Price__c, 'Price should be reduced by 100');
    }

    static testMethod void testNoDiscountBelowThreshold() {
        // Create a record with price below 1000
        Levis__c j = new Levis__c();
        j.Name = 'BasicJean';
        j.Price__c = 500;

        Test.startTest();
        insert j;
        Test.stopTest();

        Levis__c result = [SELECT Price__c FROM Levis__c WHERE Id = :j.Id];

        // Price should remain unchanged
        System.assertEquals(500, result.Price__c, 'Price below 1000 should not change');
    }

    static testMethod void testExactThreshold() {
        // Create a record with price exactly at 1000
        Levis__c j = new Levis__c();
        j.Name = 'ThresholdJean';
        j.Price__c = 1000;

        Test.startTest();
        insert j;
        Test.stopTest();

        Levis__c result = [SELECT Price__c FROM Levis__c WHERE Id = :j.Id];

        // 1000 meets the >= condition, so discount applies
        System.assertEquals(900, result.Price__c, 'Price at 1000 should get discount');
    }
}

What is Happening in That Test Class

Let’s break it down because there are several important things going on.

  • @isTest annotation: Tells Salesforce this class is purely for testing. It will not count toward storage. It will not count toward the 6 MB Apex code limit.
  • Test.startTest() and Test.stopTest(): These two method calls are extremely important. Everything between them gets a fresh set of governor limits. So the DML you used to set up test data before startTest() does not eat into the limits available for the actual code being tested. This matters when your test class has many methods each doing their own data setup.
  • System.assertEquals(): This is an assertion. It compares an expected value against an actual value. If they do not match, the test fails with the message you provide. More on assertions in a later section.
  • Three separate test methods: Each one tests a different scenario. One tests the happy path (price above threshold). One tests the negative case (price below threshold). One tests the boundary condition (price exactly at threshold). Good test classes always cover these three angles.

Public Test Utility Classes

The source material on deployment mentions something that a lot of people miss. Test classes annotated with @isTest no longer have to be private. You can create public test utility classes that expose common methods for test data creation.

@isTest
public class TestUtil {
    public static Levis__c createJean(String name, Decimal price) {
        Levis__c j = new Levis__c();
        j.Name = name;
        j.Price__c = price;
        insert j;
        return j;
    }

    public static List<Levis__c> createBulkJeans(Integer count, Decimal price) {
        List<Levis__c> jeans = new List<Levis__c>();
        for (Integer i = 0; i < count; i++) {
            jeans.add(new Levis__c(Name = 'Jean' + i, Price__c = price));
        }
        insert jeans;
        return jeans;
    }
}

Test methods can call these public methods in other test classes. This restricts you from duplicating data creation logic across other test classes. This pattern saves a great deal of time on projects with 50 or more test classes.

Where to Run Tests

Salesforce gives you several options.

  • Developer Console: Go to the Test menu, then Run All or select specific classes. Results appear in the Tests tab.
  • Apex Test Execution Page: In Setup, type "Apex Test Execution" in the Quick Find box. Select the classes you want to run. You can run all tests in the org from here.
  • VS Code with Salesforce Extensions: Right click a test class and select Run Apex Tests. Results appear in the output panel.
  • API based execution: Two API objects (ApexTestQueueItem and ApexTestResult) allow you to trigger test runs programmatically. This is how continuous integration tools run Salesforce tests automatically on every code commit.

Code Coverage in Salesforce

This is the part that determines whether your deployment succeeds or fails. Code coverage refers to a percentage that tells you how much of your Apex code was actually executed by your test methods.

How Coverage is Calculated

The formula is simple.

Code Coverage = (Lines Executed by Tests / Total Executable Lines) × 100

Salesforce highlights your code in two colors after a test run.

  • Blue lines were executed by your test methods. They count as covered.
  • Red lines were not executed. They count as uncovered.

Lines that do not count toward the total include comments, blank lines, System.debug() statements, test class code, and curly braces on their own lines.

The 75% Rule

Salesforce requires an org wide average of at least 75% code coverage to deploy to production. This is not per class. It is the average across every Apex class and trigger in the deployment package.

Let us see how this works with a real example from the source material.

Scenario 1 (Fails):

TestClass1 covers ApexClass1 at 75%

TestClass2 covers ApexClass2 at 50%

TestClass3 covers ApexClass3 at 80%

Average = (75 + 50 + 80) / 3 = 68%

68% is below 75%. Deployment blocked. None of these classes go to production.

Scenario 2 (Passes):

TestClass1 covers ApexClass1 at 90%

TestClass2 covers ApexClass2 at 50%

TestClass3 covers ApexClass3 at 90%

Average = (90 + 50 + 90) / 3 = 76%

76% clears the 75% threshold. All three classes deploy successfully.

Notice something important here?

In Scenario 2, ApexClass2 only has 50% coverage on its own. That is technically fine because Salesforce takes the average of the entire package.

But this is a risky approach. One badly covered class can drag down the average, and adding new classes later can push you back below 75% unexpectedly.

Trigger Coverage Rules

Every trigger in the deployment package must have some coverage. Even 1% is technically enough per trigger.

But here is the catch. If a trigger has 0% coverage, meaning no test method exercises it at all, the deployment will be rejected regardless of the org wide average.

How to Check Your Coverage

1. Through Setup:

Go to Setup, then navigate to Apex Classes. You will see a column showing the percentage of Apex used. The source material references the path:

Login to Salesforce Org → Setup → Build → Develop → APEX Class → Percent of Apex Used.

2. Through Developer Console:

After running tests, open the class or trigger you want to inspect. Next, move to the Code Coverage dropdown and select the test class. Here, the editor will highlight covered lines in blue and uncovered lines in red.

Improving Coverage When You Are Stuck

You will find situations where coverage was sitting at 72% the night before a production deployment. Here is what actually works.

  • Test every branch of your if/else logic. If your code has three conditions, you need at least three test methods with different data that exercises each path.
  • Test both positive and negative scenarios. Do not just test what happens when things go right. Test what happens when they go wrong, too.
  • Test boundary conditions. If your code checks for Price >= 1000, test with 999, 1000, and 1001.
  • Test bulk operations. Create 200 records in your test method and process them all at once. This exercises your code with realistic volumes and often covers lines that single record tests miss.
@isTest
static void testBulkInsert() {
    List<Levis__c> jeans = new List<Levis__c>();
    for (Integer i = 0; i < 200; i++) {
        jeans.add(new Levis__c(
            Name = 'BulkJean' + i,
            Price__c = 1500
        ));
    }

    Test.startTest();
    insert jeans;
    Test.stopTest();

    List<Levis__c> results = [SELECT Price__c FROM Levis__c WHERE Price__c = 1400];
    System.assertEquals(200, results.size(), 'All 200 records should have discount applied');
}

Assertions in Salesforce

Assertions are basically how you prove your code actually did the right thing. See, here is what a lot of people do not realize. 

A test method can pass with flying colors even if the logic it covers is completely busted. Salesforce does not care what your code produced. It only cares that the method runs without crashing. That is it. So unless you add assertions, a "passing" test is really just telling you "nothing exploded." It is not telling you "the output was correct.”

Three Assertion Methods

There are three of these in the System class, and honestly you will use the first one about 90% of the time.

1. System.assertEquals(expected, actual, message) is the one you reach for most. You give it the value you expect, the value your code actually produced, and a message. If those two values do not match, the test dies right there.

Integer expected = 1100;

Integer actual = result.Price__c.intValue();

System.assertEquals(expected, actual, 'Discount should reduce price to 1100');

2. System.assertNotEquals(val1, val2, message) is the reverse. It fails when the two values ARE the same. You can use this one when you need to confirm that a record was actually created and got an ID assigned.

System.assertNotEquals(null, result.Id, 'Record should have been inserted with an ID');

3. System.assert(condition, message) is the simplest one. You pass it a true/false expression. False means failure.

System.assert(result.Price__c < 1200, 'Price should have been reduced');

What Happens When an Assertion Fails?

So let us say you have five test methods in a class and the assertion in method number two fails. Method two stops executing right at that line. Whatever comes after it in that method never runs.

But methods three, four, and five? Those still run like nothing happened. Salesforce isolates the failure to the method where it occurred, which is nice because you still get results from everything else.

Writing Meaningful Assertion Messages

This is a small thing that makes a big difference when you are debugging a failed deployment at midnight.

Bad:

System.assertEquals(1100, result.Price__c, 'Test failed');

Good:

System.assertEquals(1100, result.Price__c,
    'Expected price of 1100 after discount on jean: ' + result.Name
    + '. Actual price: ' + result.Price__c);

The second version tells you exactly what went wrong without you having to dig through the code. When you have 30 test methods and one of them fails, descriptive messages save a lot of time.

Deployment to Production

Everything we have covered so far leads to this point. You have written your Apex code. You have created test classes. Your coverage is above 75%. Your assertions all pass. Now you need to actually get it into production.

Salesforce Does Not Allow Direct Production Editing

This is a fundamental rule of the platform. Salesforce will not let you write or edit Apex code in a production org. So, you will have to build everything in a sandbox, test it there, and then push it to production through a deployment.

That is not an accident either. Salesforce set it up that way because they want your code tested before it gets anywhere near real users and real data. No shortcuts.

What Salesforce Checks During Deployment?

When you hit deploy, Salesforce does not just take your word for it that everything works. It runs its own checks first.

The first thing it looks at is compilation. Every class and trigger in your package needs to be compiled cleanly. One syntax error in any file and the whole thing gets sent back.

Syntax errors block everything.

  • All test methods in the org execute. Not just the ones related to your code. Every single test in the production org runs.
  • The org wide code coverage average must be at least 75%. If your new code drags the average below 75%, the deployment is rejected.
  • Every trigger in the package must have at least some test coverage.
  • No test method can fail. If even one test throws an unhandled exception or a failed assertion, the entire deployment is blocked.

Deployment Methods

  • Change Sets: This happens to be the most common method for admins and developers working within a single Salesforce instance. Here, you create an outbound change set in your sandbox, add the components (classes, triggers, fields, objects), and send it to production. In production, you accept the inbound change set and deploy it.
  • Salesforce CLI (SFDX): The command line tool for modern Salesforce development. You can retrieve, deploy, and run tests directly from the terminal. This is the standard for teams using version control and continuous integration.
sfdx force:source:deploy -p force-app -l RunLocalTests -u MyProductionOrg
  • VS Code with Salesforce Extensions: Right-click your project folder and deploy to a connected org. The extensions handle the packaging and test execution behind the scenes.
  • Metadata API: For advanced scenarios, you can use the Metadata API directly to package components and deploy them programmatically. CI/CD tools like Jenkins, GitHub Actions, and CircleCI use this approach.

Continuous Integration with Apex Tests

The source material on deployment mentions two API objects that are specifically designed for CI integration.

  • ApexTestQueueItem: Represents a single Apex class in the test execution queue. You can use this to programmatically queue test classes for execution.
  • ApexTestResult: Represents the result of a single test method. You query this object to check whether tests passed or failed after they finish running.

Additionally, the System class provides methods for determining the current execution context.

System.isBatch()      // true if running inside a batch job
System.isFuture()     // true if running inside a @future method
System.isScheduled()  // true if running inside a scheduled job

These are useful when your code needs to behave differently depending on the execution context, and your test methods need to account for that.

Putting It All Together

Let’s walk through the entire flow one more time, from start to finish and connect each piece.

Step 1: Write your Apex class or trigger in a sandbox. Build the business logic. Make it bulkified. Handle edge cases.

Step 2: Write your test class. Create test data inside each method. Use Test.startTest() and Test.stopTest() to get fresh governor limits. Add assertions for every scenario.

Step 3: Run the tests. Use the Developer Console or Apex Test Execution page. Check that every method passes. Look at the code coverage percentage.

Step 4: Verify coverage. Open your Apex class and trigger. Check the highlighted lines. Blue means covered. Red means not covered. If you are below 75%, add more test methods that exercise the uncovered lines.

Step 5: Deploy. Use a change set, the Salesforce CLI, or VS Code. Salesforce will re run all tests in the production org. If everything passes and coverage is above 75%, the deployment succeeds.

Step 6: Verify in production. Spot check the functionality. Confirm that the trigger fires correctly with live data. Monitor the debug logs for any unexpected behavior.

Wrapping Up

That is the complete picture of Apex Testing and Deployment in Salesforce.

Testing fundamentals: Salesforce requires all Apex code to be tested before deployment. There are no exceptions to this rule.

Test classes: Annotated with @isTest, containing test methods that create data, execute code, and verify results through assertions. Public test utility classes help share common data creation logic.

Code coverage: The percentage of executable lines your tests actually run. The org wide average must be at least 75%. Salesforce calculates this by averaging coverage across all classes and triggers in the deployment package.

Assertions: The three methods (assertEquals, assertNotEquals, assert) that verify your code produced the correct output. Without assertions, passing tests proves nothing.

Deployment: All development happens in sandboxes. Salesforce re-runs every test during deployment and blocks anything that falls below the coverage threshold or has a failing test.

Every one of these pieces is connected. You cannot deploy without tests. Tests are meaningless without assertions. Assertions are useless without proper code coverage. And coverage does not count if the deployment process catches a failure.

If you have been reading this tutorial and thinking, "I get the concepts, but I want someone to walk me through this on a real project," that is exactly what MindMajix built their Salesforce training program around. 

Test class design, coverage optimization, assertion strategies, CI/CD pipelines for Salesforce, all of it. And they did not just throw a curriculum together from old documentation. 

The whole thing is structured around what companies are actually asking in interviews right now and what real projects demand from day one.

What caught my attention is the placement track record. Over the last couple of years, a significant number of candidates who went through this program ended up getting placed in Bangalore, Hyderabad, Gurgaon, Pune, and Noida. Those are not small job markets.

The competition there is intense, and the fact that MindMajix-trained candidates are consistently getting through tells you something about the quality of preparation they are getting.

Frequently Asked Questions

1. What is the minimum code coverage required for production deployment?

Salesforce requires an average of at least 75% code coverage across all Apex classes and triggers. This is not per class. It is the average of all classes in the deployment package. If one class has 50% but others compensate, the deployment can still work out as long as the average is 75% or more.

2. Can I write Apex code directly in production?

No. Salesforce does not allow you to create or modify Apex code in production environments. All development must happen in a sandbox. You then deploy the tested code to production through change sets, the Salesforce CLI, or the Metadata API.

3. What happens if one test method fails during deployment?

The entire deployment is blocked. Salesforce runs all tests in the org during a production deployment. Even if a single method fails, nothing gets deployed. You need to fix the failing test and try again.

4. Do System.debug() statements count toward code coverage?

No. Salesforce explicitly excludes System.debug() statements from the coverage calculation. Adding debug statements will not improve your coverage percentage.

5. What is the purpose of Test.startTest() and Test.stopTest()?

These methods create a separate governor limit context. Everything between them gets a fresh set of limits. This is especially useful when your test setup consumes DML or SOQL, because the actual code under test gets its own limit allocation.

6. Where can I find more resources on Salesforce testing and deployment?

MindMajix offers several free and paid resources.

logoOn-Job Support Service

Online Work Support for your on-job roles.

jobservice
@Learner@SME

Our work-support plans provide precise options as per your project tasks. Whether you are a newbie or an experienced professional seeking assistance in completing project tasks, we are here with the following plans to meet your custom needs:

  • Pay Per Hour
  • Pay Per Week
  • Monthly
Learn MoreContact us
Course Schedule
NameDates
Salesforce TrainingMay 09 to May 24View Details
Salesforce TrainingMay 12 to May 27View Details
Salesforce TrainingMay 16 to May 31View Details
Salesforce TrainingMay 19 to Jun 03View Details
Last updated: 08 May 2026
About Author

Kalla Saikumar is a technology expert and is currently working as a Marketing Analyst at MindMajix. Write articles on multiple platforms such as Tableau, PowerBi, Business Analysis, SQL Server, MySQL, Oracle, and other courses. And you can join him on LinkedIn and Twitter.

read less