У меня есть некоторый код в триггере After Insert, который выполняет некоторые условные обновления DML для вставляемых записей, если почта успешно отправлена. Я ищу надежный модульный способ проверить это, однако я не уверен, что моя стратегия верна, так что, возможно, кто-то сможет меня исправить?

Во-первых, вот часть кода в триггере, который обрабатывает службу обмена сообщениями и обновляет записи:

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

Я абстрагировал обмен сообщениями в интерфейс для большей модульности следующим образом (многое из этого взято из этого поста 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;
        }
    }
}

Я думаю, что моя стратегия состоит в том, чтобы условно реализовать фиктивную версию IMessagingService и использовать ее примерно так?:

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

        }
    }
}

Что-то с этой стратегией кажется подозрительным...

0
mpaler 26 Окт 2018 в 02:49
3
У меня есть целый пост в блоге на эту тему - cropredysfdc.com/2017/10 /23/apexmocks-и-электронная почта
 – 
cropredy
26 Окт 2018 в 03:40

1 ответ

Я смог исправить свою проблему. Это было действительно о масштабировании. В обработчике триггера (код сокращен для удобочитаемости)

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

Затем в моем тестовом классе:

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 fail@fail.com
        // 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;
    }
}

Почти готово, теперь, когда я пытаюсь сделать MockContractMessagingService немного более функциональным, я получаю сообщение об ошибке при попытке создать SendEmailResult. Это тупик???

1
mpaler 26 Окт 2018 в 23:07