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
larkinrichardslarkinrichards 

Behavior testing a sendEmail statement

Hi, I'm wondering how I can write an assertion in a test method that shows that an email was sent.  I have code that sends an email if certain criteria are met, but does not do any updates to the object in question.  If the only outcome from the code firing is that an email is sent, how can I verify that the email was sent?

 

I understand that if I wrap the test like so:

 

test.startTest();
insert aCase;
test.stopTest();

Then the stopTest() should make all asynchronous tasks run - including sendEmail.  My question is, how do I query the send email log in a test method to assert it sent the email?

 

Thanks in advance!

 

kiranmutturukiranmutturu

you have a method in Messaging.SendEmailResult Object called isSuccess.. u can make use of this.. to check whether mail was sent r not .. this can be used in system.assert .....

larkinrichardslarkinrichards

Thanks Kiran, unfortunately that requires rewriting methods just to test them and while it is useful for unit testing, I wanted to do a behavior test of a full user interaction.

 

I ended up looking at the EmailUtil code from the CodeShare and rewrote it as a wrapper for the base email methods, with enhancements that make it easier to bulkify a trigger, and some public static variables that can be accessed in a test to verify outcomes.

 

I'm tidying it up a bit and then I'll submit it to CodeShare/Post it here.

 

 

larkinrichardslarkinrichards

Here's some pseudo code representing a simple behavior and a test for that behavior with the revised EmailUtil:

 

 

trigger onContact on Contact (after insert) {
	for (Contact c : Trigger.new) {
		if (meets_criteria_for_send_email(c)) {
			EmailUtil.to(new String[]{'test@spam.com'})
				.stashForBulk();
		}
	}
	EmailUtil.sendBulkEmail();
}

private static testMethod void should_send_email_when_meets_criteria() {
	// given
	Contact c = new_contact_that_meets_criteria();
	
	// when
	insert c;
	
	// then
	system.assertEquals(1, EmailUtil.last_sendEmail_result.size());
}

 

Here's the full code of the updated utility, and some tests.

 

/* 
This utility originally found on the force.com Cookbook here:
http://developer.force.com/cookbook/recipe/email-utility-class
Thanks to Anup Jadhav!
	
Updated by Pete Richards, pete@pete-richards.com, May 2011
Added some features:
* Added static ".to" methods that construct and return an instance of a Email Util to 
  save you the trouble of instantiating an EmailUtil when sending email.
* Added variables needed for constructing emails using templates. (whatId, templateId, targetObjectId) 
* Updated build method to build using a template or plaintext/html. 
* Added bulk send functionality with the .stashForBulk() terminator and .sendBulkEmail() static method.
* Added static that stores the last send email result so you can verify emails sent during tests.	

	
*********** Sending a Single Message ***********
Here's how you could compose a single email and send it immediately:
	
List<String> toAddresses = new List<String> {'john.doe@acme.org'};
String replyToAddress = 'john.duh@acme.org';

emailUtil.to(toAddresses)
	.plainTextBody('Bilbo Bagginses has stolen our Preciousssss!!')
	.senderDisplayName('Gollum Gollum')
	.replyTo(replyToAddress)
	.sendEmail();
			
			
*********** Sending Bulk Mail in a Trigger ***********
Here's a pseudo-code example of the bulk send inside a trigger:
	
trigger onContact on Contact (after insert) {
	for (Contact c : Trigger.new) {
		if (meets_criteria_for_send_email(c)) {
			EmailUtil.to(new String[]{'test@spam.com'})
				.stashForBulk();
		}
	}
	EmailUtil.sendBulkEmail();
}

*/
public class EmailUtil {
	/* ===================================
		Default settings for email content
	======================================= */
	// defaults for email content  
	private String subject = '';
	private String htmlBody = ''; 
	private String plainTextBody = '';
	private Boolean useSignature = false;
	private Boolean SaveAsActivity = false;
	private List<Messaging.EmailFileAttachment> fileAttachments = null;
	// Default replyTo is current user's email address
	// using a static to save this result across multiple email constructions.
	private static User currentUser {
		get {
			if (currentUser == null)
				currentUser = [Select email from User where username = :UserInfo.getUserName() limit 1];
			return currentUser;
		} set;
	}
	private String replyTo = currentUser.email; //replyTo defaults to current user's email 
	private String senderDisplayName = UserInfo.getFirstName()+' '+UserInfo.getLastName();
	
	// Template options
	private Id templateId;
	private Id whatId;
	
	// defaults for recipient types
	private final Id targetObjectId; // Contact, Lead, or User.
	private final List<String> toAddresses; 
	
	// Used to temporarily hold the email during the build command
	private Messaging.SingleEmailMessage singleEmailMessage; 
	
	
		
	/* ===================================
		Chain Starters
	======================================= */
	public static EmailUtil to(List<String> addresses) {
		return new EmailUtil(addresses);
	}
	
	public static EmailUtil to(Id target) { 
		// warning: can't override this with a string handler, apex can't tell an Id from a String.
		return new EmailUtil(target);
	}
	
	/* ===================================
		Attribute Setters
	======================================= */
	
	/*
	.saveAsActivity(Boolean val)
	.senderDisplayName(String val)
	.subject(String val)
	.htmlBody(String val)
	.useSignature(Boolean bool)
	.replyTo(String val)
	.plainTextBody(String val)
	.fileAttachments(List<Messaging.Emailfileattachment> val)
	
	for use in template:
	.templateId(Id an_id)
	.whatId(Id an_id)
	*/
	
	public EmailUtil saveAsActivity(Boolean val) {
		saveAsActivity = val;
		return this;
	}
	
	public EmailUtil senderDisplayName(String val) {
		senderDisplayName = val;
		return this;
	}
	
	public EmailUtil subject(String val) {
		subject = val;
		return this;
	}
	
	public EmailUtil htmlBody(String val) {
		htmlBody = val;
		return this;
	}
	
	public EmailUtil templateId(Id an_id) {
		templateId = an_id;
		return this;
	}
	
	public EmailUtil whatId (Id an_id) {
		whatId = an_id;
		return this;
	}
	
	public EmailUtil useSignature(Boolean bool) {
		useSignature = bool;
		return this;
	}
	
	public EmailUtil replyTo(String val) {
		replyTo = val;
		return this;
	}
	
	public EmailUtil plainTextBody(String val) {
		plainTextBody = val;
		return this;
	}
	
	public EmailUtil fileAttachments(List<Messaging.Emailfileattachment> val) {
		fileAttachments = val;
		return this;
	}
	
	/* ===================================
		Chain Terminators: call to send now or stash for bulk send.
	======================================= */
	public void sendEmail() {
		// build and send email.
		build();
		last_sendEmail_result = Messaging.sendEmail(new Messaging.SingleEmailMessage[] { singleEmailMessage });
	} 
	public void stashForBulk() {
		//build and stash email.
		build();
		bulk_stash.add(singleEmailMessage);
	}
	
	/* ===================================
		Other Bulk Actions
	======================================= */
	
	public static void sendBulkEmail() {
		// send emails in bulk_stash, empty it.
		last_sendEmail_result = Messaging.sendEmail(bulk_stash);
		bulk_stash.clear();
	}
	
	public static Boolean hasEmailsToSend() {
		return bulk_stash.size() != 0 ? true : false;		
	}
	
	// static method for holding email results, so I can test when triggers send emails
	public static Messaging.SendEmailResult[] last_sendEmail_result {get; private set;}
	
	

	/* ===================================
		Helpers & private constructors
	======================================= */

	// private constructors, force you to use the static chain methods.
	private EmailUtil(List<String> addresses) {
		this.toAddresses = addresses;
	}
	
	private EmailUtil(Id target) {
		this.targetObjectId = target; 
	}
	
	// build method, constructs a single email message.
	// this method is private and is called from sendEmail() or stashForBulk()
	private EmailUtil build() {
		singleEmailMessage = new Messaging.SingleEmailMessage();
		singleEmailMessage.setTargetObjectId(this.targetObjectId);
		singleEmailMessage.setWhatId(this.whatId);
		singleEmailMessage.setToAddresses(this.toAddresses);
		singleEmailMessage.setSenderDisplayName(this.senderDisplayName);
		singleEmailMessage.setUseSignature(this.useSignature);
		singleEmailMessage.setReplyTo(this.replyTo);
		singleEmailMessage.setFileAttachments(this.fileAttachments);
		singleEmailMessage.setSaveAsActivity(this.saveasactivity);
		// use template if one exists, else use html and plain text body
		if (this.templateId == null) {
			singleEmailMessage.setHtmlBody(this.htmlBody);
			singleEmailMessage.setPlainTextBody(this.plainTextBody);
			singleEmailMessage.setSubject(this.subject);
		} else {
			singleEmailMessage.setTemplateId(this.templateId);
		}
		return this;
	}
	
	private static Messaging.SingleEmailMessage[] bulk_stash {
		get {
			if (bulk_stash == null) 
				bulk_stash = new Messaging.SingleEmailMessage[]{};
			return bulk_stash;
		} private set;
	}
	
	
	
	
	/* ===================================
		Tests:  
		
	======================================= */
	
	private static testMethod void should_store_email_results() {
		// Given an email util that hasn't sent emails
		System.assertEquals(null, EmailUtil.last_sendEmail_result);
		
		// When you send an email
		EmailUtil.to(new String[]{'test@spam.com'}).sendEmail();
		
		// Then the email util should store the send result.
		System.assertNotEquals(null, EmailUtil.last_sendEmail_result);
		System.assertEquals(1, EmailUtil.last_sendEmail_result.size());
		for (Messaging.SendEmailResult ser : EmailUtil.last_sendEmail_result)
			System.assertEquals(true, ser.isSuccess());
	}
	
	private static testMethod void should_work_with_templates() {
		// Given an email util that has not sent emails, and a template for emails
		System.assertEquals(null, EmailUtil.last_sendEmail_result);
		
		Folder test_template_folder = 
			[Select Id from Folder Where Type = 'Email' And IsReadOnly = false Limit 1];
		
		EmailTemplate test_template = new EmailTemplate(
			Name = 'test email template', DeveloperName = 'test_template_uniqueasdfbahkls',
			TemplateType = 'text', isActive = true, Description = 'test template',
			Subject = 'test email', FolderId = test_template_folder.Id,
			Body = 'Hi {!Receiving_User.FirstName}, this is a test email to a user.'
		);
		insert test_template; 
		
		// When an email is constructed & sent to the current user with that template
		EmailUtil.to(UserInfo.getUserId())
			.templateId(test_template.Id)
			.sendEmail();
		
		// Then it should be sent successfully
		System.assertEquals(1, EmailUtil.last_sendEmail_result.size());
		for (Messaging.SendEmailResult ser : EmailUtil.last_sendEmail_result)
			System.assertEquals(true, ser.isSuccess());
	}
	
	private static testMethod void should_bulk_stash() {
		// Given an EmailUtil that doesn't have emails to send.
		System.assertEquals(false, EmailUtil.hasEmailsToSend());
		
		// When you stash an email to send
		EmailUtil.to(new String[]{'test@spam.com'})
			.stashForBulk();
			
		// Then EmailUtil should have emails to send
		System.assertEquals(true, EmailUtil.hasEmailsToSend());
	}
	
	private static testMethod void should_bulk_send() {
		// Given an Email util with  bulk email to send
		EmailUtil.to(new String[]{'test1@spam.com'}).stashForBulk();
		EmailUtil.to(new String[]{'test2@spam.com'}).stashForBulk();
			
		// When you send bulk
		EmailUtil.sendBulkEmail();
		
		// Then two emails should be sent successfully
		system.assertEquals(2, EmailUtil.last_sendEmail_result.size());
		for (Messaging.SendEmailResult ser : EmailUtil.last_sendEmail_result)
			System.assertEquals(true, ser.isSuccess());
	}
	
	private static testMethod void should_gracefully_handle_empty_bulk_send() {
		// Given an emailutil with no emails to send
		System.assertEquals(false, EmailUtil.hasEmailsToSend());

		try {
			// When you try and do a bulk send
			EmailUtil.sendBulkEmail();
		} catch (Exception e) {
			// then it shouldn't fail horribly.
			System.assert(false);
		}
	}
	
	private static testMethod void setters_should_not_throw_exceptions() {
		try {
			// When using all the setters
			EmailUtil.to(new String[]{currentUser.email})
				.saveAsActivity(false)
				.senderDisplayName('test sender')
				.subject('test email')
				.htmlBody('this is html')
				.useSignature(false)
				.replyTo(currentUser.email)
				.plainTextBody('this is plaintext')
				.fileAttachments(null);	
		} catch (Exception e) {
			// Then it should not throw an exception.
			system.assert(false);
		}
	}
}

 

 

 

I'd love to hear feedback on the code - hit me up here and I'll happily revise it.

 

 

 

carlocarlo

Test fails at line 263:  List has no rows for assignment to SObject.  This is the line -

 

Folder test_template_folder =  [Select Id from Folder Where Type = 'Email' And IsReadOnly = false Limit 1];

 

I guess because I need to add a Folder of type Email?

carlocarlo

I added a new Folder and now the Test completes with the result 3 lines not tested:

 

    public EmailUtil whatId (Id an_id) {
        whatId = an_id;
        return this;
    }

larkinrichardslarkinrichards

Hi Carlo,

 

Thanks for noting those bugs.  I recall that I wasn't able to create an email template folder via apex and so I had to do that manually prior to testing.  I'm not sure if there is a better way to populate test data for email templates-- if anyone has suggestions for how to improve the test so that it doesn't require test data that would be great.

 

Also, re: the 3 lines not being tested:  As each organization could have data validation rules or other things which would prevent a test object from being inserted, I wasn't sure the best way to test that line.  AFAIK there isn't a way to create mock objects in apex.

carlocarlo

I have just tried using the replyTo option.  It does not seem to work.  It just uses the email address setup for the user who sends the email.  I want to to use a system type address like noreply@ourcompany.com.  I added 

 

.replyTo('noreply@ourcompany.com')

 

Am I missing something.

 

Thanks

 

Carlo

larkinrichardslarkinrichards

Hi Carlo,

 

Interesting... I just tested this and it sets the reply-to address correctly, but Gmail is not respecting the reply-to address.  When I try to reply to the email address in Outlook, it works fine.  Are you using Gmail, Outlook, or a different mail client?

 

Best,

 

Pete

 

For more details on my steps to recreate, read on:

 

I just ran the following anonymous apex(with email addresses substituted):

 

List<String> toAddresses = new List<String> {'myaddress@mydomain.org'};
String replyToAddress = 'absolutelynoreply@mydomain.org';

emailUtil.to(toAddresses)
	.plainTextBody('test reply to')
	.senderDisplayName('my name')
	.replyTo(replyToAddress)
	.sendEmail();

 And here's the applicable section of the email header(again, with email addresses substituted):

From: my name <myname@mydomain.org>
Reply-To: absolutelynoreply@mydomain.org

In Gmail, when I press reply, it replies to "myname@mydomain.org" and not "absolutelynoreply@mydomain.org"

In Outlook, when I press reply, it replies to "absolutelynoreply@mydomain.org"

carlocarlo

I have tried it on Hotmail web, Gmail web and my Mac Mail client.  All do not work.

larkinrichardslarkinrichards

It would help if I can review the code--  Can you recreate the issue with an anonymous apex snippet and post that snippet here?  Or, if it's easier for you, just post the code you're using.

carlocarlo

I just copied your code and changed the 2 email addresses and executed it as anonymous apex.  The address I am sending to is my Hotmail addess.  When I receive the email in Hotmail it shows my normal SF email address.  There is no mention of the noreply address anywhere.

 

I dont believe its your code thats the problem.  I'm thinking its a SF thing.

carlocarlo

Edit

 

When I recieve the email into my web based Gmail it shows the email as coming from my normal SF address via some long weird SF address.   When I hit reply it uses the absolutelynoreply address.

 

Similarly in Hotmail.  When I hover over the sender's name 'my name' it displays my normal SF email in a pop up.  When I hit reply it does use the absolutelynoreply address.

larkinrichardslarkinrichards

Okay, that sounds correct-- if you check the email headers you'll see that "Reply-To" is getting set correctly.  Reply-To doesn't override the From header, but when you reply it should reply to the Reply-To address.

 

If you want the From address to show "noreply," then you should use an organization wide email address to send the email.  Unfortunately, the emailUtil above does not have code for using an organization wide email address.

 

Best,

 

Pete

carlocarlo

I have added owa support to your utility class.  There is an extra option for OrgWideEmailAddress.  If the user enters one it searches for it in the owa records which already exist and if it finds a match it uses the ID of the object to attach it to the email.  I have also added a test for this.  The test will only run if there is an owa added to the org.  I could not add one using DML in the test.

 

Edit - I am hitting a 20,000 character limit trying to paste this into here.  Any ideas?

carlocarlo

Pete

 

Sorry to bother you again.  I'm trying to use the sendEmail util with a Template.  I am getting the following error:

 

System.EmailException: SendEmail failed. First exception on row 0; first error: REQUIRED_FIELD_MISSING, Missing targetObjectId with template: []: Class.EmailUtil.sendEmail: line 172, column 1

 

I am using sendEmail like this:

 

emailUtil.to(toAddresses)

    .templateId('00XQ0000000MKEz')

    .sendEmail();

 

How do I use a template with sendEmail?

 

Thanks

 

Carlo


carlocarlo

I figured this out if anyone is interested.  You have to use a target ID when using a template rather than an email address.

 

emailUtil.to(ContactId)

    .templateId('00XQ0000000MKEz')

    .whatId(CampaignId)

    .sendEmail();