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[] { 'name@example.com' } );

        message.setBccAddresses( new String[] { 'name@example.com' } );

        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();
}
}

Answer

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 } );

Attribution
Source : Link , Question Author : mlpSFadmin , Answer Author : crmprogdev

Leave a Comment