[SalesForce] How to write unit tests for emails being sent via Apex

If I send an email via Apex

Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setTargetObjectId(someContactId);
mail.setTemplateId(someTemplateId);
mail.setSaveAsActivity(true);
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });

How can I create a unit test and test whether it was sent? Checking e.g. Limits.getEmailInvocations() doesn't actually work, because the emails are not sent out for real during tests. Or should I simpy assume that it works for the given contact and emailtemplate?

Best Answer

Testing emails involves many things

  1. To test that the properties of an email are properly set and that your code actually tries to send an email, see this Stackexchange answer that exploits ApexMocks.
  2. To test that an email template is properly rendered before sending it, the savepoint - rollback hack is no longer required. You can use built-in Apex methods that render an email template. These are described in this stackexchange answer

The latter use case will mean you should create a wrapper method around your rendering of the template so you can unit test that method in a testmethod by passing in different arguments and verifying the proper application of merge fields to the template

For example

public Messaging.SingleEmailMessage render(ID templateId, ID whoId, ID whatId) {
 return Messaging.renderStoredEmailTemplate(templateId, whoId, whatId);
}

and the testmethod

// Given Sobjects corresponding to the whoId and whatId. Mock or fetch an email template and note the Id

Account a = new Account(...);
insert a;
Contact c = new Contact(...);
insert c;
EmailTemplate et;

System.runAs([select Id from User where Id = :Userinfo.getUserId() limit 1][0]) {       // creating setup objects requires diff context
  Folder[] folders = [select Id from Folder 
                        where AccessType IN: new list<String>{'Shared','Public'} 
                          AND Type = 'Email'];
  System.assert(!folders.isEmpty(),'test can\'t run wo Email Template folders. Folders can\'t be mocked in Apex');

  //    Given an EmailTemplate with merge fields
  et = new EmailTemplate(   Name = 'TestEmailTemplate ApexTestMethod',
                            Body = someBodyWithMergeFields, 
                            DeveloperName = 'TestEmailTemplate_ApexTestMethod', 
                            FolderId = folders[0].Id,
                            HtmlValue =someBodyWithMergeFields,
                            IsActive = true,
                            TemplateType = 'custom');
  insert et;            
} 

// When rendering...
Messaging.SingleEmailMessage res = new myClass().render(et.Id, c.id, a.Id);

// Then verify rendered template

System.assertEquals(someExpectedRenderedTemplate,res.getHtmlBody());

Note that you can't use mocked SObjects in memory here to test the above because the code under test actually queries the database given the Ids passed.

Related Topic