I am having some issues with writing a test class for my trigger and helper class, which sends an email and .pdf attachment to a contact specified in the custom setting.
The trigger and helper class produce the desired result in sandbox, but it keeps failing validation when deploying to Production.
There doesn't seem to be a huge amount of documentation on how to write such a test class, but it seems that one cannot send the email from the test class but should rather 'mock' it and retrieve the result?
Any direction would be really helpful. Previously, I was hitting this error 'REQUIRED_FIELD_MISSING, Missing target address (target, to, cc, bcc): []' with my test class at the time, which just wasn't working at all.
This is my trigger:
/*===============================
* Purpose: The X Team need to receive emails with Data Schedule attachments whenever a Data Schedule attachment has been created against an Opportunity.
* 1) This trigger evaluates all Opportunities that have been updated and which have the number of attachments changed and have a number of attachments >=1 and number of data schedules >=1.
* 2) Then, all the Attachments which are related to such Opportunities are selected, which are also .pdf attachments only and were created in the last few minutes.
* 3) Then, these Attachments are attached to a single email, which leverages an Email Template, and sent to the required recipients.
=================================*/
/*===============================
* Design: Important notes about the design of this trigger:
* 1) This trigger leverages the Messaging.SingleEmailMessage class because it is NOT possible (as of Q4, 2016) to leverage the Message.MassEmailMessage clas with the .EmailFileAttachment method.
* 2) This trigger stores the EmailTemplateId and Contact (which may have an email of the distribution list to email mass recipients) in custom settings, so that these can be changed in future easily.
* 3) The contactId stored in the .setTargetObjectId custom setting is protected by a validation rule (it's very important the contactId specified in the .setTargetObjectId is not deleted).
* 4) The custom number opportunity fields 'of_Data_Schedules__c' and 'of_Attachments__c' are populated by the Lookup Rollup Summaries.
=================================*/
trigger EmailDataScheduleAttachment on Opportunity (after update) {
List<Id> opportunitiesWithAttachmentsToEmail = new List<Id>();
/* Only evaluate opportunities where there is at least 1 attachment against the opportunity (custom field 'of_attachments__c' populated via lookup rollup summary id = 'a0BD000001ptKZl')
AND where the custom field 'of_Data_Schedules__c' is >=1 (populated via lookup rollup summary id = 'a0BD000001ptKZl')
AND where the # of attachments has changed */
for (Opportunity opp : Trigger.new) {
Opportunity oldOpp = Trigger.oldMap.get(opp.Id);
if (opp.of_attachments__c >= 1 && opp.of_attachments__c != oldOpp.of_attachments__c && opp.of_Data_Schedules__c >= 1) {
opportunitiesWithAttachmentsToEmail.add(opp.Id);
}
}
// Select the Attachments which have a ParentId of one of the opportunities from above AND which are .pdfs (CPQ writes back in .pdf format, as default) AND which were created 3 minutes ago (to prevent already created Attachments from being re-sent)
List<Attachment> dataScheduleAttachmentsToEmail = [SELECT Id, ParentId, Name, Body FROM Attachment WHERE ParentId IN : opportunitiesWithAttachmentsToEmail AND ContentType = 'application/pdf' AND CreatedDate > :Datetime.now().addMinutes(-3)];
// Iterate over all the selected opportunities, so that their merge fields can be referenced in the Email template
for (Integer i=0; i<opportunitiesWithAttachmentsToEmail.size(); i++) {
if (opportunitiesWithAttachmentsToEmail[i]!=null) { // null pointer check
EmailUtility.dataScheduleAttachmentSend(opportunitiesWithAttachmentsToEmail[i], dataScheduleAttachmentsToEmail);
}
}
}
This is helper class
public class EmailUtility {
public static void dataScheduleAttachmentSend(Id whatId, List<Attachment> attList) {
// Instantiate the SingleEmailMessage class to create a single email to be sent
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
// Custom Settings
// .setEmailTemplate
setEmailTemplate__c sti = setEmailTemplate__c.getInstance('EmailTemplate1');
Id emailTemplateId = Id.valueOf(sti.email_template_id__c);
// .setTargetObjectId
setTargetObjectId__c sto = setTargetObjectId__c.getInstance('sto');
Id contactId = Id.valueOf(sto.contactId__c);
mail.setReplyTo('[email protected]');
mail.setSenderDisplayName('XYZ');
mail.setBccSender(false);
mail.setTemplateId(emailTemplateId);
mail.saveAsActivity = false;
mail.setTargetObjectId(contactId);
mail.setUseSignature(false);
List<Messaging.EmailFileAttachment> fileAttachments = new List<Messaging.EmailFileAttachment>();
for (Attachment a : attList) {
Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
efa.setFileName(a.Name);
efa.setBody(a.Body);
fileAttachments.add(efa);
}
mail.setFileAttachments(fileAttachments);
Messaging.sendEmail(new Messaging.SingleEmailMessage[] {mail});
}
}
This is my current test class
@isTest
public class EmailDataScheduleAttachmentTest {
/* Create User record */
public static User u = TestHelper.createAdminUser('ahitchings@DataAttachmentScheduleEmailSend');
public static testMethod void createSomeTestData() {
System.runAs(u) {
// Create & Insert Account record
Account a = new Account(Name = 'Test Account - Data Schedule Attachments', Company_Profile__c = 'Hedge Fund', Company_Profile_Type__c = 'Buy Side',
Status__c = 'Draft', ownerId = U.Id);
insert a;
// Create & Insert Contact record
Contact c = new Contact(Email = '[email protected]', LastName = 'HITCHINGS', AccountId = a.Id, OwnerId = u.Id);
insert c;
// Create & Insert Opportunity record
Opportunity opp = new Opportunity(AccountId = a.Id, OwnerId = u.Id, StageName = 'Qualification',
Name = 'TEST ABC XYZ Opportunity with Attachment and Data Schedule', CurrencyISOCode = 'GBP',
Amount = 500, Billing_Effective_Date__c = Date.today(), CloseDate = Date.today());
insert opp;
Test.startTest();
Opportunity oppJustCreated = [SELECT Id FROM Opportunity LIMIT 1];
oppJustCreated.of_Attachments__c = 1;
oppJustCreated.of_Data_Schedules__c = 1;
update oppJustCreated;
}
}
}
Although code coverage for the trigger is 100%, when I run this test class in sandbox, it's returning a stack trace error on the 'update oppJustCreated' line (line 33). The error message itself says:
caused by: System.NullPointerException: Attempt to de-reference a null object Class.EmailUtility.dataScheduleAttachmentSend: line 12, column 1 Trigger.EmailDataScheduleAttachment: line 37, column 1: []
which leads me to think it's do to with the custom settings somehow. In a previous test class, I referenced the custom settings in the createSomeTestData() method, and I was still returning an error which was:
SendEmail failed. First exception on row 0; first error: REQUIRED_FIELD_MISSING, Missing target address (target, to, cc, bcc):
I have four opportunity related test classes in Production which were hitting this error, and it all seemed to be around updating an opportunity.