How to Test Messaging.sendEmail

  • Looking for some help/advice in writing a test class. The class I need to test is an apex class to schedule a report that needs to be sent to an external email address once a month as a .csv attachment. I was able to build the test class using some advice from an old post on the dev community; I found the solution in TESTOURSFDC's comment to work sufficiently for us. Copy of my code below.

    What I'm struggling with is how to test that an email has been sent. The SF documentation for scheduled apex classes are all built with regular SF maintenance use cases (e.g. create tasks on opps that fulfill xyz criteria once a month), so their test classes are easy to re-create (query for tasks with related WhatIds). I've never worked with apex to send emails before, so I'm admittedly quite unfamiliar with the methods and metadata available.

    Has anyone tested something similar to this that could help me out?

    Thanks!!

    Class (updated):

    global class reportExporter implements System.Schedulable {
    
        global void execute(SchedulableContext sc) {
    
            ApexPages.PageReference report = new ApexPages.PageReference('/00OXXXXXXXXXXXX?csv=1');
    
            Messaging.EmailFileAttachment attachment = new Messaging.EmailFileAttachment();
    
            attachment.setFileName('Report.csv');
    
            //use getContent() only if not a test, otherwise test will fail
            //http://salesforce.stackexchange.com/questions/97223/test-fails-because-of-testmethod-do-not-support-getcontent-call
            if(!test.isRunningTest()){
                attachment.setBody(Blob.valueof(report.getContent().toString()));   
            } else {
                attachment.setBody(Blob.valueof('TEST BODY'));
            }
    
            attachment.setContentType('text/csv');
    
            Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
    
            message.setFileAttachments(new Messaging.EmailFileAttachment[] { attachment } );
    
            message.setSubject('Report');
    
            message.setPlainTextBody('The report is attached.');
    
            message.setToAddresses( new String[] { '[email protected]' } );
    
            message.setBccAddresses( new String[] { '[email protected]' } );
    
            Messaging.sendEmail( new Messaging.SingleEmailMessage[] { message } );
    
            List<Messaging.SendEmailResult> results = Messaging.SendEmail(new Messaging.Email[] { message });
                System.debug(results);
            if (!results.get(0).isSuccess()) {
                System.StatusCode statusCode = results.get(0).getErrors()[0].getStatusCode();
                String errorMessage = results.get(0).getErrors()[0].getMessage();
    
            }
    
        }
    
    }
    

    Test Class:

    @isTest
    private class testReportExporter {
    
       // CRON expression: midnight on March 15 2022
       // Because this is a test, job executes immediately after Test.stopTest() regardless of date
       public static String CRON_EXP = '0 0 0 15 3 ? 2022';
    
       static testmethod void test() {
          Test.startTest();
    
          reportExporter re = new reportExporter();
    
          // Schedule the test job
          String jobId = System.schedule('ScheduleApexClassTest',
                            CRON_EXP, re);
    
          // Get the information from the CronTrigger API object
          CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, 
             NextFireTime
             FROM CronTrigger WHERE id = :jobId];
    
          // Verify the expressions are the same
          System.assertEquals(CRON_EXP, 
             ct.CronExpression);
    
          // Verify the job has not run
          System.assertEquals(0, ct.TimesTriggered);
    
          // Verify the next time the job will run
          System.assertEquals('2022-03-15 00:00:00', 
             String.valueOf(ct.NextFireTime));
    
          // Verify the scheduled job hasn't run yet.
          Integer beforeInvocations = Limits.getEmailInvocations();
          System.assertEquals(0,beforeInvocations,'no email sent');
    
          //Verify class results are not false
          System.debug(re);
    
          Test.stopTest();
    }
    }
    

    Email is DML. You should be able to assert the DML operation at the end of your test method. Remember that in a schedulable, it's after the test.stopTest that the class executes and you'll be able to query for all of your results.

    Interesting, I hadn't observed that before. You can check a more specific limit though. @crm

    @AdrianLarson Yes, I saw your answer. I'm the one who gave it the 1st upvote (the only upvote at present in fact).

    Email really consumes limits fast! Dml, invocations, daily limits, oh my!

    @crmprogdev do you have any advice for how to query the worker class? I know I need to query the Messaging.SendEmailResult list from the above class (edited), but I'm not sure how to call that from the test class.

  • crmprogdev

    crmprogdev Correct answer

    5 years ago

    With respect to your edited question, as @AdrianLarson answered, your schedulable class still doesn't run until immediately after Test.stopTest(). Asserting the change in Limits.getEmailInvocations() should be sufficient for your requirements.

    That being said, to answer the question you asked me on how to query the Messaging.SendEmailResult, that's something one executes immediately following their SendEmail Method (you'd do this in a non-async class). It isn't something one can query as those methods are run in the context of Sending an email. Querying them wouldn't have any context unless you queryied the WhoId, WhatId, sender, target, Subject, and other fields, etc to narrow the scope. In doing that you'd presumably be querying an Email record that's related to some Object Record rather than the SendEmailEmailResult method.

    So, just add the code that @AdrianLarson provided to your test class and you should be all set. Additionally, you'll also want to assert that your scheduled class has run too. You already have the jobId.

    After Test.stopTest(), add something like what's below:

     // Get the information from the CronTrigger API object
    CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, 
         NextFireTime
         FROM CronTrigger WHERE id = :jobId];
    
    // Verify the job has run
    System.assertEquals(1, ct.TimesTriggered);
    
    // Assert emails have been sent
    system.assertEquals(1, invocations,  ' An email should be sent');
    

    Edit

    Your chron expression schedules the job for March 15th, 2022 at midnight which should work fine. Looking closer at your code, I see several things that could be the cause of your issues:

    The first one is minor, but should be corrected. Use Schedulable, not System.Schedulable when you declare your class.

    global class reportExporter implements System.Schedulable {
    

    I suspect the real source of your problem is in this section of code below. Adding debug statements should reveal if that's the case. It would seem that either more of this code should be wrapped in if(!test.isRunningTest()) or else you've not created the data you need for it to run.

    You begin by calling a page reference, creating the attachment, setting a file name for a blob, declare it's type, etc and actually attach it. It would seem to me that you'd need to create a blob in your test class called 'TEST BODY', but I don't see that in your test method.

        Messaging.EmailFileAttachment attachment = new Messaging.EmailFileAttachment();
    
        attachment.setFileName('Cost Plus Merchants.csv');
    
        //use getContent() only if not a test, otherwise test will fail
        //http://salesforce.stackexchange.com/questions/97223/test-fails-because-of-testmethod-do-not-support-getcontent-call
        if(!test.isRunningTest()){
            attachment.setBody(Blob.valueof(report.getContent().toString()));   
        } else {
            attachment.setBody(Blob.valueof('TEST BODY'));
        }
    
        attachment.setContentType('text/csv');
    
        Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
    
        message.setFileAttachments(new Messaging.EmailFileAttachment[] { attachment } );
    

    I appreciate the clarification on the query, that's helpful. As for those lines of code, I have to repeat what I told Adrian -- I tried adding them, but the asserts both fail. If I debug, that CronTrigger query returns the same results pre- and post- Test.stopTest(). Is there a trick to getting the schedulable class to run? Everything seems to suggest it happens automatically after Test.stopTest() I appreciate all your help!!

    See the edits to my answer.

License under CC-BY-SA with attribution


Content dated before 7/24/2021 11:53 AM