1

I am trying to write a test class for the Apex Class with try - catch block and this is my first time coding test classes. The Apex Class is like below which has trycatch

public class updateCaseRecord {    
     @AuraEnabled
    public static void updateCheck(String caseId){
        String msg = '';
        try
        {
        List<Case> records = [select id,Status from Case WHERE Id = :caseId];
        for (Case record : records)
        {
            record.Status = 'Closed';
        }
        update records; 
        }
        catch(DmlException e) 
            {
               for (Integer i = 0; i < e.getNumDml(); i++) {
                    msg =+ e.getDmlMessage(i) +  '\n' ; 
                   System.debug('getDmlFieldNames=' + e.getDmlFieldNames(i));
                   System.debug('getDmlMessage=' + e.getDmlMessage(i));  
                }
                throw new AuraHandledException(msg);
            }
            catch(Exception e)
            {
            throw new AuraHandledException(e.getMessage());
            }
            finally { }      
    }}

And I wrote my test class like

@IsTest
public class updateCaseRecordTest {
@IsTest static void testIsUpdateCheck() {

    ID parentRecType = '012U0000000PqA9IAK';
    ID csRecType     = '012U0000000QGv4IAG'; 

    Account pa = new Account(Name='Test Parent Account', recordTypeId=parentRecType);
    insert pa;

    Account testAccount = new Account(Name='test', Industry='Biotech',Territory__c='EUR',BillingCity='Test',BillingCountry='Germany',ParentId=pa.id);
    insert testAccount; 

    Contact c = new Contact(LastName='Test',LeadSource='Customer Service',AccountId=testAccount.Id);
    insert c;

    Case newCase1 = new Case();
    newCase1.Subject = 'testStatusClosed';
    newCase1.Status = 'New';
    newCase1.Category__c = 'Inquiry';
    newCase1.Sub_Category__c = 'Other';
    newCase1.AccountId = testAccount.Id;
    newCase1.ContactId = c.id;
    newCase1.recordTypeId=csRecType;
    insert newCase1;


    Test.startTest();
try
    {
    updateCaseRecord.updateCheck(newCase1.Id);
    List<Case> lCases = [SELECT Id,Status,OwnerId FROM Case WHERE Id =: newCase1.Id];
    System.assertEquals('Closed', lCases[0].Status); 
    System.assertEquals(UserInfo.getUserId(), lCases[0].OwnerId);
    }

    catch(DMLException e) {
        System.assertEquals(e.getMessage(), e.getMessage());
    }

    Test.stopTest();   
}}

I thought I captured the try catch but when I run the test class it has only 57% code coverage and shows like below

enter image description here

Following one of the answers here

enter image description here

  • 2
    One quick note. Even if your test behaved the way you think it should, trying to catch a DmlException in your test is incorrect. The code you're testing catches the DmlException and then throws an AuraHandledException. AuraHandledException is what you'd want to catch in your test, then. – Derek F Apr 13 at 15:39
  • When the code in your try block is ONLY doing DML operation, then it's pointless to have two catch blocks to begin with. just DMLException suffices – mritzi Apr 22 at 13:38
  • just a friendly advice : learn to properly indent code. It's super easy (using shortcuts in most editors) and super helpful in increasing code readability – mritzi Apr 22 at 13:40
0

Exceptions are probably one of the harder things to test (or at least test "properly"). Like with most things, the design of the code being tested has a big impact on how easy or hard that code is to test.

To test this properly, I might break your SUT (System Under Test, i.e. the code of the class you've written) into two classes. One "parent" class to handle errors/exceptions, and one "child" class that inherits from the "parent" class to implement the behavior.

Something like

// The "parent" class remains mostly the same
// We make it "virtual" so we can inherit from it later
public virtual class UpdateCaseRecord {
    @AuraEnabled
    public static void updateCheck(String caseId){
        String msg = '';
        try{
            // We'll rely on child classes to override this method (and actually
            //   define its implementation)
            doWork(caseId);
        } catch(DmlException e) {
            for (Integer i = 0; i < e.getNumDml(); i++) {
                msg =+ e.getDmlMessage(i) +  '\n' ; 
                System.debug('getDmlFieldNames=' + e.getDmlFieldNames(i));
                System.debug('getDmlMessage=' + e.getDmlMessage(i));  
            }
            throw new AuraHandledException(msg);
        } catch(Exception e){
            throw new AuraHandledException(e.getMessage());
        }
        finally { }
    }

    // To override a method (which we will be doing), the method in the parent class
    //   needs to be either "virtual" or "abstract"
    public virtual void doWork(String caseId){}

    // I've made this an inner class because I think it makes the example clearer given
    //   the formatting options we have on SFSE.
    // This doesn't _need_ to be an inner class. It could be a completely separate class
    public class CloseCase extends UpdateCaseRecord{
        override public void doWork(String caseId){
            // If you have the recordId, you don't need a query to perform an update
            //List<Case> records = [select id,Status from Case WHERE Id = :caseId];

            // We can provide key=value pairs in the constructor of SObjects
            List<Case> records = new List<Case>{new Case(Id = caseId)};

            for (Case record : records){
                record.Status = 'Closed';
            }

            update records;
        }
    }
}

That should give you the same behavior you have now...but what's the point of adding that extra complexity and typing?

The answer is that it gives us a lot more flexibility in testing. Since we can inherit from the parent class, our test class can itself define a class that inherits from that parent class. What's more, we have basically complete control over what our special test class can do.

Test classes can have inner classes too, so...

@isTest private class MyTestClass{
    // When called, we want this inner class to throw a DML exception.
    // That's hard to do, normally, and not really appropriate to do in non-test code
    //   but here, we have complete control over what happens
    private class MyDMLExceptionTest extends UpdateCaseRecord{
        public override void doWork(String caseId){
            throw new DMLException();
        }

    @isTest
    void testDMLException(){
        // You could instead declare the type here to be UpdateCaseRecord, and that
        //   might, in fact, be better design.
        // This works for purpose of example just fine though.
        MyDMLExceptionTest testUnit = new MyDMLExceptionTest();

        Test.startTest();
        // Because MyDMLExceptionTest inherits from UpdateCaseRecord, we have access to
        //   the updateCheck method.
        // The updateCheck method contains the try/catch, and our test implementation ensures
        //   we throw one of those exceptions
        testUnit.updateCheck(null);
        Test.stopTest();

        // Don't forget to make assertions about the results of the test!
        // A test without assertions won't tell you if your method that adds 2 and 2
        //   erroneously gives you 5 as a result, but that's something you'd be interested
        //   in knowing.
    }
}

You can basically add as many inner classes as you need, so it's just rinse and repeat for other exceptions (and more) that you find yourself facing.

The other part of this is your test itself. Right now, you have no good way to know if your test caused an exception to be thrown or if that exception was handled.

In my tests that stress exception paths, I generally apply the following pattern.

@isTest
void myTest(){
    MyClass testUnit = new MyClass();

    // Not 100% sure if this needs to be defined outside the try/catch, but
    //   I think block scoping rules require it to be outside of the try/catch.
    // We need something to keep track of whether we encountered an exception or not
    //   when producing an exception is the expected result because just letting
    //   the exception remain uncaught would lead to a test failure.
    Boolean exceptionCaught = false;

    try{
        testUnit.myMethod();
    }catch(MyClass.MyException e){
        // Only set our variable if we encounter the exception we expected
        // This allows the assertion below to be useful
        exceptionCaught = true;
    }

    System.assert(exceptionCaught, 'Expected exception was not caught');
}
| improve this answer | |
  • IMHO this is complicating an otherwise simple task. Since the TRY block just does DML operation asper the OPs code, DMLException is enough for this use case. The other catch is un-necessary addition to begin with. – mritzi Apr 22 at 13:46
  • @mritzi Technically, there is a little more than just DML happening (there is a query, a loop, and setting a field on an object), but I can't argue that this approach isn't more complex. The point of this answer was to illustrate how we can design code with exceptions with ease of testing in mind. The specific code OP is working with might not need all of the benefits of this approach, but this approach is broadly applicable to other scenarios. – Derek F Apr 22 at 13:55
  • Perhaps some of the more important benefits (that I didn't go over) is that this approach means 1) that we don't need to pollute the class being tested with test-specific code, and 2) that changes to the test code don't necessarily require changes to the code being tested. In a general sense, the less code that needs to undergo changes, the better. – Derek F Apr 22 at 14:05
  • SF guarantees that the query will not crash even if you pass null value for Id and instead return an empty list, so there is nothing beforel the UPDATE statement that's going to crash. I myself don't support the idea to clutter a class with variables/code just to cover the exception part. Instead the try/catch should be judiciously used at places where it's required, and used exception type fits the use case. There is no point in having irrelevant catch blocks and then fret about covering those irrelevant catch blocks in Test. – mritzi Apr 22 at 16:33
  • @mritzi I feel that we're talking about two different things here. I'm focusing on how to test exceptions, and I feel you're focusing on whether or not try/catch is warranted at all in this particular case. In situations where exception handling is needed in code, I feel that this approach is best practice (at the cost of some additional complexity). Feel free to write an answer to explain to OP why you feel exception handling isn't appropriate here. You can also pull me into chat if you want to further discuss this approach for general testing of exceptions. – Derek F Apr 22 at 19:34
0

Now that I've gotten what I believe to be the "correct" way to design and test this out of the way, there is a simpler (but more crude) approach you could also take.

You can simply have a private variable (with the @testVisible annotation) to dictate when to throw an exception (and which one to throw).

public class updateCaseRecord {
    // private so that normal code can't set this variable
    // testvisible so that we can do so in tests
    @testVisible
    private String throwExceptionType;

    private Map<String, Exception> exceptionsMap = new Map<String, Exception>{
        'dml' => new DMLException(),
        'other' => new MathException() // normally thrown for things like divide by 0
    };

    @AuraEnabled
    public static void updateCheck(String caseId){
        String msg = '';
        try{
            // This is the operative bit
            if(exceptionsMap.containsKey(throwExceptionType)){
                throw exceptionsMap.get(throwExceptionType);
            }

            List<Case> records = [select id,Status from Case WHERE Id = :caseId];
            for (Case record : records){
                record.Status = 'Closed';
            }
            update records; 
        }
    // and the rest of the class remains unchanged

You'd then set that private, testvisible variable like you would any other variable.

updateCaseRecord testUnit = new updateCaseRecord();
testUnit.throwExceptionType = 'dml';

Et voila, one guaranteed exception, able to be crafted to your liking. Quick and easy to implement, hard to accidentally cause issues when it's being run for real, minimally invasive.

The price you pay though is that it's not great design. It can cover up issues that could be solved with better design.

| improve this answer | |
  • I tried following this solution. But there was lots of error I wasnt able to resolve it. I added the screenshot of the errors in the question. – user81642 Apr 22 at 13:46
  • @user81642 I could've sworn that I was able to get this to work via anonymous apex. I'll see if I can find some time to re-visit this today. – Derek F Apr 22 at 13:58
  • @user81642 Found the issue, edited my answer to fix it. Needed to add = new Map<String, Exception> between exceptionsMap and the opening curly brace { – Derek F Apr 22 at 14:43
  • Sorry I see errors again. Can you please look in to the updated screenshot in the question – user81642 Apr 22 at 16:02
  • @user81642 Looks like I missed that updateCheck() is static. Instance variables can't be referenced in a static method (not directly, at least). I'll encourage you to try to research and resolve this one on your own. – Derek F Apr 22 at 16:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.