0

I have some code in an After Insert trigger that does some conditional DML updates to the records being inserted if a mail is successfully sent out. I'm looking for a solid, modular way to test this, however I'm not confident my strategy is sound, so perhaps someone can set me straight?

First here's the section of code in the trigger that is handling the Messaging service and updating the records:

@testVisible static IMessagingService messagingService;

public static void mailOutSalesOrderContract(List<signature__Signature__c> signatureList) {

    // Aggregate mails here

    messagingService = new MessagingService();
    List<Messaging.SendEmailResult> sendEmailResults = new 
    List<Messaging.SendEmailResult>();
    sendEmailResults = messagingService.sendEmail(mails, false);

    if (sendEmailResults != null) {
        // Mails are sent out in the order they are added to the mails list.
        // And they are added to the mails list in the order they come in
        // via the mailOutSalesOrderContract signatureList parameter. 
        // Knowing this we can update the correct signature record and
        // mark that it has been sent.
        Integer i=0; 
        List<signature__Signature__c> updatedSignatures = new List<signature__Signature__c>();
        signature__Signature__c updatedSignature = new signature__Signature__c();

        for(Messaging.SendEmailResult sendEmailResult : sendEmailResults){
            if (sendEmailResult.isSuccess()) {
                updatedSignature = new signature__Signature__c(Id=signatureList[i].Id);
                updatedSignature.signature__Emailed__c = DateTime.now();
                updatedSignature.signature__Status__c = 'Emailed';
            }
            else {
                for (Messaging.Sendemailerror sendEmailError : sendEmailResult.getErrors()){
                    updatedSignature = new signature__Signature__c(Id = signatureList[i].Id);
                    updatedSignature.signature__Emailed__c = DateTime.now();
                    updatedSignature.signature__Status__c = 'Failed';
                    updatedSignature.Comments__c = 'Send Email Result Error: ' + sendEmailError.Message;
                }
            }
            updatedSignatures.add(updatedSignature);
            i++;
        }
        update updatedSignatures;
    }
}

I've abstracted the messaging into an interface for more modularity as follows (alot of this is taken from this post https://salesforce.stackexchange.com/a/73316/26381

public class MessagingService implements IMessagingService {
    public Messaging.SendEmailResult[] sendEmail(Messaging.Email[] emails, Boolean allOrNothing) {
        if (!Test.isRunningTest()) {
            // If we aren't in a test, run Messaging.sendEmail normally
            // passing results back to our implemenation.
            return Messaging.sendEmail(emails, allOrNothing);
        } 
        else {
            System.Debug('!!! MessagingService called inside a test. Consider using a mock of IMessagingService.');
            return null;
        }
    }
}

I'm thinking my strategy is to conditionally implement a mock version of IMessagingService and utilize it something like this?:

public class MessagingService implements IMessagingService {
    public Messaging.SendEmailResult[] sendEmail(Messaging.Email[] emails, Boolean allOrNothing) {
        if (!Test.isRunningTest()) {
            // If we aren't in a test, run Messaging.sendEmail normally
            // passing results back to our implemenation.
            return Messaging.sendEmail(emails, allOrNothing);
        } 
        else {

            IMessagingService messagingServiceMock = new MessagingServiceMock();
            return messagingServiceMock.sendEmail(emails, allOrNothing);

        }
    }
}

Something seems fishy with this strategy though...

1

1 Answer 1

1

I was able to fix my problem. It was really about scoping. In the trigger handler (code abbreviated for readability)

public inherited sharing class SignatureTriggerHandler implements TriggerHandler {

    public static Boolean triggerDisabled = false;

    public Boolean isDisabled() {
        return triggerDisabled;
    }


    public void afterInsert(Map<Id, SObject> newItems) {
        mailOutSalesOrderContract((List<signature__Signature__c>)newItems.values());
    }

    /**
    * Send out email to customer and cc Account Manager when new contract is inserted.
    * Before the method, we implement a @testVisible, static instance of the
    * messagingService class. This allows us to override the core messagingService
    * with a mock implementation of the service in our test class.
    */
    @testVisible private static IMessagingService messagingService;
    static {
        messagingService = new MessagingService();
    }

    public static void mailOutSalesOrderContract(List<signature__Signature__c> signatureList) {

        //Set<Id> contactSet = new Set<Id>();
        List<Messaging.SingleEmailMessage> mails = new List<Messaging.SingleEmailMessage>();

        for (signature__Signature__c signature : signatureList) {

            // Let's do some checking before add the contract to list of items to mail.
            if (signature.signature__Contact__c != null && signature.SalesOrder__c != null
                && signature.signature__AttachmentID__c != null && signature.signature__EmailPDF__c != null) {

                // code to create mail instance
                mails.add(mail);
            }
        }

        if (!mails.isEmpty()) {

            // Use messagingService instance we instantiated in the class constructor.
            List<Messaging.SendEmailResult> sendEmailResults = new List<Messaging.SendEmailResult>();
            sendEmailResults = messagingService.sendEmail(mails, false);

            if (sendEmailResults != null) {
                // Mails are sent out in the order they are added to the mails list.
                // And they are added to the mails list in the order they come in
                // via the mailOutSalesOrderContract signatureList parameter. 
                // Knowing this we can update the correct signature record and
                // mark that it has been sent.
                Integer i=0; 
                List<signature__Signature__c> updatedSignatures = new List<signature__Signature__c>();
                signature__Signature__c updatedSignature = new signature__Signature__c();

                system.debug(sendEmailResults);
                for(Messaging.SendEmailResult sendEmailResult : sendEmailResults){

                    system.debug(sendEmailResult);
                    if (sendEmailResult.isSuccess()) {
                        updatedSignature = new signature__Signature__c(Id=signatureList[i].Id);
                        updatedSignature.signature__Emailed__c = DateTime.now();
                        updatedSignature.signature__Status__c = 'Emailed';
                    }
                    else {
                        for (Messaging.Sendemailerror sendEmailError : sendEmailResult.getErrors()){
                            updatedSignature = new signature__Signature__c(Id = signatureList[i].Id);
                            updatedSignature.signature__Emailed__c = DateTime.now();
                            updatedSignature.signature__Status__c = 'Failed';
                            updatedSignature.Comments__c = 'Send Email Result Error: ' + sendEmailError.Message;
                        }
                    }
                    updatedSignatures.add(updatedSignature);
                    i++;
                }
                update updatedSignatures;
            }
        }
    }
}

Then in my test class:

private static testMethod void testMailOutSingleSalesOrderSuccess() {

    KNDY4__Sales_Order__c salesOrder = [SELECT Id,KNDYPNTR__Ship_To_Contact__c FROM KNDY4__Sales_Order__c];

    // Disable attachment trigger that automatically creates a Sales Order Contract
    // when attachment is made to Sales order. We do this so we can take control of the
    // Automatic contract generation.
    AttachmentTriggerHandler.triggerDisabled = true;
    SignatureTriggerHandler.triggerDisabled = true;

    // Override the real-world messagingService with our mock service.
    // so SignatureTriggerHandler.mailOutSalesOrderContract will use the
    // mock service instead.
    MockContractMessagingService mockMessagingService = new MockContractMessagingService();
    SignatureTriggerHandler.messagingService = mockMessagingService;

    Attachment objAtt = new Attachment();
    objAtt.Name = 'SO-' + salesOrder.Id;
    objAtt.ParentId = salesOrder.Id;
    objAtt.Body = Blob.valueOf('Test Body');
    insert objAtt;

    signature__Signature__c contract = new signature__Signature__c();
    contract.signature__Contact__c = salesOrder.KNDYPNTR__Ship_To_Contact__c;
    contract.signature__AttachmentID__c = objAtt.Id;
    contract.SalesOrder__c = salesOrder.Id;
    contract.signature__Status__c = 'Requested';
    contract.signature__Requested__c = system.now();
    contract.signature__EmailPDF__c = true;
    insert contract;

    List<signature__Signature__c> contracts = new List<signature__Signature__c>();
    contracts.add(contract);

    Test.startTest();
        SignatureTriggerHandler.mailOutSalesOrderContract(contracts);
    Test.stopTest();

    System.assertEquals(true, mockMessagingService.sendEmailCalled,'Expecting email messaging service to be fired.');
    insertedContract = [SELECT Id, signature__Emailed__c, signature__Status__c FROM signature__Signature__c WHERE SalesOrder__c=:salesOrder.Id];
    System.assertEquals(true, insertedContract.signature__Emailed__c != null,'Expecting signature__Emailed__c to be set upon contract creation.');

}

public class MockContractMessagingService implements IMessagingService {

    public Boolean sendEmailCalled = false;
    public Messaging.Email[] sendEmailEmails = null;
    public Messaging.SendEmailResult result = null;

    public Messaging.SendEmailResult[] sendEmail(Messaging.Email[] emails, Boolean allOrNothing) {

        sendEmailCalled = true;
        sendEmailEmails = emails;

        // In here we're gonna handle out two test case scenarios for the 
        // MessagingService: Successful mail sending and failed sending.
        // Let's fail if the send to address contains [email protected]
        // handler...
        List<Messaging.SendEmailResult> sendEmailResults = new List<Messaging.SendEmailResult>();

        for (Messaging.Email mail : emails) {

            Messaging.SendEmailResult result = new Messaging.SendEmailResult();
            // This is throwing a compile error "Type cannot be constructed: Messaging.SendEmailResult"
            //result.success = true;
            //sendEmailResults.add(result);
        }
        return sendEmailResults;
    }

    public Messaging.SendEmailResult sendEmailResult() {
        return null;
    }
}

Almost there, now when I try to make MockContractMessagingService a bit more functional I'm getting an error when trying to construct an SendEmailResult. Is this a dead end???

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .