function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
ChrisSargentChrisSargent 

Unit Test Help (My first Apex class and Test)

Hello,

I'm writing a simple Apex class that will change the field of a contact when an email is sent to a BCC salesforce email services address. I've written the class in my Sandbox and it all seems to be working fine so I now need to test it to I can deploy it to my production environment. I've written a test as far as I can so far and I'm getting no errors, with 50% code coverage.

I'm not quite sure what else I need to test to be able to get to 100% code coverage; any help would therefore be much appreciated.

Here's my Class:

global class UpdateToutAppStage implements Messaging.InboundEmailHandler {
 
  global Messaging.InboundEmailResult handleInboundEmail(Messaging.inboundEmail email, Messaging.InboundEnvelope env){
 
    // Create an InboundEmailResult object for returning the result of the Apex Email Service
    Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();
    
    String emailSubject = email.Subject;
    String [] emailToList = email.toAddresses;
    String newStage = null;
    String toEmail = null;
     
    // Run a for loop through the "email to" list, in case of sending to multiple people.
    for(Integer i = 0; i < emailToList.size(); i++){
        toEmail = emailToList[i];


        // Try to look up any contacts based on the email to address
        // If there is more than one contact with the same email address, an exception will be thrown and the catch statement will be called.
    
        try {
          Contact vCon = [SELECT Id, Name, Email
            FROM Contact
            WHERE Email = :toEmail
            LIMIT 1];
          
         // Check the Email Subject and Adjust the newStage variable accordingly.
    
         if (emailSubject == 'Congratulations on your recent funding' || emailSubject == 'Hiring awesome people for amazing companies'){
                 newStage = 'Stage 1';
             }
         else if (emailSubject == 'Re: Congratulations on your recent funding' || emailSubject == 'Re: Hiring awesome people for amazing companies'){
                 newStage = 'Stage 2';
             }    
         else if (emailSubject == 'Trying to connect'){
                 newStage = 'Stage 3';
             }
         else if (emailSubject.contains('take this thing offline?')){
                 newStage = 'Stage 4';
             }
         else if (emailSubject.contains('Am I not getting the hint?')){
                 newStage = 'Completed';
             }
         else {
                 newStage = 'Undefined';
              }
         // Set the field to be equal to the variable string
         vCon.Marketing_Campaign__c = newStage;
         
         // Update the Contact record
         update vCon;
         
         // Writes a note to the the debugging log
         System.debug('ToutApp Stage Field Updated To: ' + newStage );   
         }
         
         // If an exception occurs when the query accesses the contact record, a QueryException is called. The exception is written to the Apex debug log.
         catch (QueryException e) {
                System.debug('ToutApp Stage Failed: ' + e);
            }
 
     } // End the For loop
   
     // Set the result to true. No need to send an email back to the user with an error message
     result.success = true;
   
     // Return the result for the Apex Email Service
     return result;
  }
}

And my test class:

@isTest
private class TestUpdateToutAppStage {
    static testMethod void TestUpdateToutApp() {
        // create a new email and envelope object
         
        Messaging.InboundEmail email = new Messaging.InboundEmail();
        Messaging.InboundEnvelope env = new Messaging.InboundEnvelope();
       
        // setup the data for the email
        
        email.subject = 'Congratulations on your recent funding';
        String[] toAddresses = new String[] {'me@email1.com','you@email2.com'};
        email.toAddresses = toAddresses;
        
        // call the ToutApp class and test it with the data in the testMethod
        UpdateToutAppStage testInbound = new UpdateToutAppStage ();
        testInbound.handleInboundEmail(email, env);
        }
}

Thanks in advance!

Chris
MTBRiderMTBRider
One thing that pops out right away is that you have a fair number of if else statements that your test method is not hitting because it is only creating one email with one subject.  Since your if else statements fire based on subject, you will need to create emails with all of those subject lines to get into the code in them.

Also, not related to testing, you may consider using emailSubject.contains() instead of emailSubject == in all of your if else statements.  It may not be an issue because maybe the emails coming in are generated by a system and will always have a consistently worded subject but it will only take one character added to a subject line for a email to not hit the first 3 if else statements
ChrisSargentChrisSargent
Hi,

Thanks for your answer. I tried writing several test methods, basically duplicating the above code and testing with several different subject lines - covering 4 of the if statements and one that would hit the else statement. I stuck at 50% code coverage.

So then I turned my attention to checking the status was being updated so I changed the test method to this:

@isTest
private class TestUpdateToutAppStage {
    static testMethod void TestUpdateToutApp1() {
        // create a new email and envelope object
         
        Messaging.InboundEmail email = new Messaging.InboundEmail();
        Messaging.InboundEnvelope env = new Messaging.InboundEnvelope();
       
        // setup the data for the email
        email.subject = 'Re: Congratulations on your recent funding';
        String[] toEmAddresses = new String[] {'*****@btinternet.com','you@email2.com'};
        email.toAddresses = toEmAddresses;
        
        // call the ToutApp class and test it with the data in the testMethod
        Messaging.InboundEmailResult inboundEmailResultItem;
        UpdateToutAppStage testInbound = new UpdateToutAppStage ();
        inboundEmailResultItem = testInbound.handleInboundEmail(email, env);
        
        system.assertEquals(true, inboundEmailResultItem.Success);
        
        Contact[] vCons = [SELECT Id
                             FROM Contact
                             WHERE Email = :toEmAddresses[1]
                             LIMIT 1];
        if (vCons.size() > 0) {                             
            system.assertEquals('Stage 2', vCons[0].Marketing_Campaign__c);
            }                             
        }
}

Again, the tests pass but I'm still stuck at 50%. I suspect that the final 'if' statement is not being fired though because I don't think the SOQL query is returning a result, even though there is a record with the *****@btinternet.com email address (edited for privacy!). Also, before I put the if statement in, I was getting an out of bounds error.

I take your point on the subjects having to match but they are system generated and I could use contains for the first one as the second email is a reply which will contain it. Probably better for me to have seperate classes firing from different email addresses but I can look at that later.

Any other thoughts or pointers please?

Chris
ChrisSargentChrisSargent
hmm.... although taking out all but one of the If statements of the original code results in a 76% code coverage so evidently I'm not testing different email subjects properly.
ChrisSargentChrisSargent
Okay.... scratch most of the above. I wasn't so keen on having things in Try statements as it seemed difficult to test, plus I've moved my SELECT and DML statements outside of any for loops, to comply with best practice. With the following APEX code, tested with the tes code below, I get 12/13 lines of coverage. The line that isn't getting tested is c.Marketing_Campaign__c = newStage;

Apex Class:
global class UpdateToutAppStage implements Messaging.InboundEmailHandler {
 
  global Messaging.InboundEmailResult handleInboundEmail(Messaging.inboundEmail email, Messaging.InboundEnvelope env){
 
    // Create an InboundEmailResult object for returning the result of the Apex Email Service
    Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();
    
    String emailSubject = email.Subject;
    String [] emailToList = email.toAddresses;
    String newStage = null;
    
    // Check the Email Subject and Adjust the newStage variable accordingly.       
    if (emailSubject == 'Congratulations on your recent funding' || emailSubject == 'Hiring awesome people for amazing companies'){
        newStage = 'Stage 1';
        }
 
    // Select a list of contacts from the database whose email address matches
    List<Contact> EmailMatches = [SELECT Id FROM Contact WHERE Email = :emailToList];

    // Loop through the list of contacts and update the MarketingCampaign field
    for(Contact c : EmailMatches){
        c.Marketing_Campaign__c = newStage;
        }
             
    // Update the Contact record
    update EmailMatches;

    // Set the result to true. No need to send an email back to the user with an error message
    result.success = true;
   
    // Return the result for the Apex Email Service
    return result;
  }
}

The test class:
@isTest
private class TestUpdateToutAppStage {
    static testMethod void TestUpdateToutApp() {
        // create a new email and envelope object
         
        Messaging.InboundEmail email = new Messaging.InboundEmail();
        Messaging.InboundEnvelope env = new Messaging.InboundEnvelope();
       
        // setup the data for the email
        
        email.subject = 'Congratulations on your recent funding';
        String[] toAddresses = new String[] {'me@email1.com','you@email2.com'};
        email.toAddresses = toAddresses;
        
        // call the ToutApp class and test it with the data in the testMethod
        UpdateToutAppStage testInbound = new UpdateToutAppStage ();
        testInbound.handleInboundEmail(email, env);
        }
}


MTBRiderMTBRider
Your test method is not hitting that line becuase the query [SELECT Id FROM Contact WHERE Email = :emailToList] is not returning anythng because emailToList is a String[].

The query should be [SELECT Id FROM Contact WHERE Email IN :emailToList]


ChrisSargentChrisSargent
Hi,

Thanks for the answer. I changed my strategy slightly and set up different Inbound Mail addresses for each of the different stages. Then I have seperate inbound handler apex classes for each email address (because it's always BCC'd there's no other way of seeing which email it was sent to) which then call a single Apex class passing the New Stage in a variable, which then updates the stage on the record.

Made my testing fairly simple as there were way less if then else statements etc. I don't think my test is that efficient at the moment, I can probably change it to a for loop. For reference, here's my new code.

One of the inbound mail handlers:
public class akProcessInboundStageM1 implements Messaging.InboundEmailHandler {
    public Messaging.InboundEmailResult handleInboundEmail(Messaging.inboundEmail email, Messaging.InboundEnvelope env){
                
        // Create an InboundEmailResult object for returning the result of the Apex Email Service
        Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();        
        
        String [] emailToList = email.toAddresses;
        String newSalesStage;
        String newMarketingStage;
        
        newSalesStage = null;
        newMarketingStage = 'Stage 1';
        
        akUpdateToutAppStages c = new akUpdateToutAppStages();
        c.processStage(newSalesStage, newMarketingStage, emailToList);
        
        return result;
    }
}

The Updating Stage Apex Class:
public class akUpdateToutAppStages {
    
    public void processStage(String newSalesStage, String newMarketingStage, String[] emailToList){
                
        String currentSalesStage;
        String currentMarketingStage;
        
        // Select a list of contacts from the database whose email address matches
        List<Contact> EmailMatches = [SELECT Id, Name, ToutApp_Marketing_Stage__c, ToutApp_Sales_Stage__c FROM Contact WHERE Email = :emailToList];
        
        if (EmailMatches.size() > 0){
            // Loop through the list of contacts and update the MarketingCampaign field
            for(Contact c : EmailMatches){
                currentSalesStage = c.ToutApp_Sales_Stage__c; // Stores the current value of Sales Campaign
                currentMarketingStage = c.ToutApp_Marketing_Stage__c; // Stores the current value of Marketing Campaign
                if (newSalesStage == null) newSalesStage = currentSalesStage; // If the new value is blank then it keeps the old value
                if (newMarketingStage == null) newMarketingStage = currentMarketingStage; // If the new value is blank then it keeps the old value
                c.ToutApp_Sales_Stage__c = newSalesStage;
                c.ToutApp_Marketing_Stage__c = newMarketingStage;
                System.debug('Updated Marketing Campaign Field for ' + c.Name );
            }
            
            // Update the Contact record
            update EmailMatches;
         }      
        
        else {
            System.debug('No Matching Email Addresses Found');
        }

    }
}

And the Test Class (as I said, I can probably put the individual tests in a for loop but just haven't got round to it.
@isTest
private class akProcessInboundTest {
    static testMethod void akProcessInboundTest() {
        // create a new email and envelope object
         
        Messaging.InboundEmail email = new Messaging.InboundEmail();
        Messaging.InboundEnvelope env = new Messaging.InboundEnvelope();
       
        // setup the data for the email
        
        String[] TestToList = new String[] {'me@email1.com','you@email2.com'};
        email.toAddresses = TestToList;
        env.fromAddress = 'someaddress@email.com';
        email.subject = 'Congratulations on your recent funding';
                
        // call the class and test it with the data in the testMethod
        Test.startTest();
        akProcessInboundStageS1 s1 = new akProcessInboundStageS1();
        s1.handleInboundEmail(email, env);
      
        akProcessInboundStageS2 s2 = new akProcessInboundStageS2();
        s2.handleInboundEmail(email, env);

        akProcessInboundStageS3 s3 = new akProcessInboundStageS3();
        s3.handleInboundEmail(email, env);

        akProcessInboundStageS4 s4 = new akProcessInboundStageS4();
        s4.handleInboundEmail(email, env);

        akProcessInboundStageS5 s5 = new akProcessInboundStageS5();
        s5.handleInboundEmail(email, env);

        akProcessInboundStageS6 s6 = new akProcessInboundStageS6();
        s6.handleInboundEmail(email, env);
        
        akProcessInboundStageM1 m1 = new akProcessInboundStageM1();
        m1.handleInboundEmail(email, env);

        akProcessInboundStageM2 m2 = new akProcessInboundStageM2();
        m2.handleInboundEmail(email, env);
        
        akProcessInboundStageM3 m3 = new akProcessInboundStageM3();
        m3.handleInboundEmail(email, env);

        akProcessInboundStageM4 m4 = new akProcessInboundStageM4();
        m4.handleInboundEmail(email, env);
        
        akProcessInboundStageM5 m5 = new akProcessInboundStageM5();
        m5.handleInboundEmail(email, env);
        
        akProcessInboundStageM6 m6 = new akProcessInboundStageM6();
        m6.handleInboundEmail(email, env);
        
        akProcessInboundStageM7 m7 = new akProcessInboundStageM7();
        m7.handleInboundEmail(email, env);
        
        akProcessInboundStageM8 m8 = new akProcessInboundStageM8();
        m8.handleInboundEmail(email, env);
        
        akProcessInboundStageM9 m9 = new akProcessInboundStageM9();
        m9.handleInboundEmail(email, env);
        
        akProcessInboundStageM10 m10 = new akProcessInboundStageM10();
        m10.handleInboundEmail(email, env);

        akProcessInboundStageR1 r1 = new akProcessInboundStageR1();
        r1.handleInboundEmail(email, env);

        
        Test.stopTest();
    }
    static testMethod void UpdateToutAppTest() {     
    
        List<String> emailToList = new List<String>{'test1@email.com','test2@email.com'};
        String newSalesStage;
        String newMarketingStage;
        
        newSalesStage = 'Stage 1';
        newMarketingStage = null;
        
        Contact newContact = new Contact(LastName='TestContact', FirstName='TestContactF', Email='test1@email.com', MailingCountryCode='US', Funnel_Stage__c='Lead');
        insert newContact;
        
        Test.startTest();
        akUpdateToutAppStages c = new akUpdateToutAppStages();
        c.processStage(newSalesStage, newMarketingStage, emailToList);        
        Test.stopTest();
    }
}

I must have learnt something because I wrote a Trigger and a class later in the day, then wrote my test code which passed with 100% on the 2nd attempt!

Thanks for the help.