How to cover Catch block in test class in Salesforce?

How to cover Catch block in test class in Salesforce?

Achieving 100% Apex Test Coverage: A Step-by-Step Guide to Covering Catch Blocks in Salesforce

As a Salesforce Developer, you know the mantra: Test your code. While aiming for the minimum 75% Apex test coverage is mandatory for deployment, striving for 100% is a hallmark of high-quality, reliable code. One of the most common hurdles developers face in reaching that 100% mark is covering the elusive catch block.

How do you test for a failure that you’ve designed your code to handle gracefully?

The answer lies in intentionally causing the failure within your test method. In this guide, we’ll break down exactly how to force an exception to ensure your error-handling logic is tested and your code coverage is complete.

Why is Testing the Catch Block So Important?

Beyond simply satisfying the code coverage metric, testing your catch block is crucial for ensuring application robustness. Your catch block isn’t just dead code; it’s a safety net. It dictates what happens when things go wrong.

Testing it confirms that:

  • Your application can handle unexpected errors without crashing.
  • The user experience is managed gracefully during a failure.
  • Your error-logging or fallback mechanisms work as designed.

The Scenario: A Simple Lead Creation Class

Let’s look at a common scenario: a simple Apex class designed to create a Lead record.

Apex Class: LeadCreation.cls

Apex

public class LeadCreation {
    public Lead objLead;
    public String lastName;

    public LeadCreation() {
        
    }

    public PageReference newLead() {
        objLead = new Lead(Company = 'Test', LastName = lastName, Status = 'Open - Not Contacted');
        try {
            // This is the line we want to succeed AND fail
            insert objLead; 
            PageReference pg = new PageReference('/' + objLead.Id);
            pg.setRedirect(true);
            return pg;
        } catch(DMLException e) {
            // This is the block we need to cover
            return null;
        }
    }
}

In this class, the newLead() method attempts to insert a new Lead. If the DML operation is successful, it returns a PageReference to the new record. If it fails (e.g., due to a validation rule, missing required field, etc.), it catches the DMLException and returns null.

The Challenge: How to Force a DMLException?

To cover the catch block, we need to make the insert objLead; line fail inside our test context. How can we do that? We need to find a rule that the insert operation will violate.

Looking at the Lead object, we know that LastName is a required field. Our code sets the LastName from the public property lastName. If we call the newLead() method without setting this property first, it will be null. Attempting to insert a Lead with a null LastName will cause the system to throw a DMLException—exactly what we need!

The Solution: The Strategic Test Class

Our test class will have two parts:

  1. The “Negative Test”: Intentionally cause the DML to fail to cover the catch block.
  2. The “Positive Test”: Provide the required data to ensure the try block is also covered.

Here is how to structure the test class to achieve this.

Test Class: LeadCreationTest.cls

Apex

@isTest
private class LeadCreationTest {

    @isTest static void leadTest() {
        LeadCreation obj = new LeadCreation();

        // --- Part 1: Covering the CATCH Block (Negative Test) ---
        // We call newLead() when obj.lastName is still null.
        // This will cause a DMLException, which our main class catches.
        PageReference resultFailure = obj.newLead();

        // Assert that the catch block's logic (return null) was executed.
        System.assertEquals(null, resultFailure, 'The method should return null on failure.');

        // --- Part 2: Covering the TRY Block (Positive Test) ---
        // Now, we set the required field.
        obj.lastName = 'Testing';
        
        // This call should succeed.
        PageReference resultSuccess = obj.newLead();

        // Assert that the try block's logic was executed.
        System.assertNotEquals(null, resultSuccess, 'A valid PageReference should be returned on success.');

        // Optional: Further verify the record was created
        Lead createdLead = [SELECT Id, LastName FROM Lead WHERE Id = :resultSuccess.getUrl().substring(1)];
        System.assertEquals('Testing', createdLead.LastName);
    }
}

Note: I’ve updated your test logic slightly to follow best practices by asserting the results of each call (resultFailure and resultSuccess). This makes the test more robust.

Breaking Down the Test Method

  1. Instantiate the Class: We create an instance of LeadCreation. At this point, the lastName property is null.
  2. Covering the catch Block:
    • We immediately call obj.newLead().
    • Inside the method, new Lead(..., LastName = null, ...) is created.
    • The insert objLead; statement fails because LastName is required.
    • A DMLException is thrown by the system.
    • The catch block in LeadCreation.cls executes, and the method returns null.
    • System.assertEquals(null, resultFailure, ...) confirms that our error-handling logic worked perfectly. The catch block is now covered!
  3. Covering the try Block:
    • We now set the missing piece of information: obj.lastName = 'Testing';.
    • We call obj.newLead() a second time.
    • This time, the Lead is valid: new Lead(..., LastName = 'Testing', ...).
    • The insert objLead; statement succeeds.
    • The try block continues to execute, creating and returning a valid PageReference.
    • System.assertNotEquals(null, resultSuccess, ...) confirms the method’s success path. The try block is now covered!

Key Takeaways

  • Think Like a Saboteur: To test error handling, you must find a way to break your own code intentionally. Look for required fields, validation rules, governor limits, or unique constraints that you can violate in your test.
  • Isolate Test Scenarios: Test the failure path and the success path separately, even if it’s within the same method. This makes your tests clearer and easier to debug.
  • Assert Everything: Don’t just run the code; assert the outcome. For a failure, assert that the expected error state occurred (e.g., a null return). For a success, assert that the expected positive outcome was achieved.

By following this simple pattern of “fail first, then succeed,” you can easily write tests that cover your catch blocks, pushing you closer to that 100% coverage goal and ensuring your Salesforce applications are as resilient as they are functional.

Leave a Reply