0

I'm getting this error: System.DmlException: Insert failed. First exception on row 0; first error: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): EmailTemplate, original object: Account: []

Please help me

Handler:

public class SendRegistrationFormHandler {
    public void emailRegistrationMethod(List<CustomObject__c> scope) {
        String registrationForm = 'Registration Form Sent';
        EmailTemplate template = [SELECT Id FROM EmailTemplate WHERE Name = :registrationForm];

        Set<Id> regIds = new Set<Id>();
        for (CustomObject__c c : scope) {
            regIds.add(c.Id);
        }

        List<Messaging.SingleEmailMessage> emails = new List<Messaging.SingleEmailMessage>();
        List<CustomObject__c> regList = new List<CustomObject__c>();

        for(CustomObject__c reg : [SELECT Id, Name, Send_Email__c, Registrant__r.PersonContactId, Status__c, Date_Form_sent__c, Email__c FROM CustomObject__c WHERE Id IN :regIds]) {
            if (Trigger.isUpdate) {
                if (reg.Send_Email__c == true) {
                    if (reg.Name != null || reg.Registrant__c != null || reg.Email__c != null) {
                        Messaging.SingleEmailMessage singleMail = new Messaging.SingleEmailMessage();

                        singleMail.setWhatId(reg.Id);
                        singleMail.setTargetObjectId(reg.Registrant__r.PersonContactId); //Lookup to Person Account
                        singleMail.setTemplateId(template.Id);
                        singleMail.setToAddresses(new String[]{reg.Email__c});
                        emails.add(singleMail);

                        reg.Status__c = 'Registration sent';
                        reg.Date_Form_sent__c = Date.today();
                        regList.add(reg);
                    } else {
                        Trigger.newMap.get(reg.Id).addError('Please complete required fields first.');
                    }
                }
            }

        }
        Messaging.sendEmail(emails);
        update regList;
    }
}

Test Class:

@isTest
public class SendRegistrationForm_Test {
    @testSetup static void setup() {
        String personAcc = Schema.getGlobalDescribe().get('Account').getDescribe().getRecordTypeInfosByName().get('Person Account').getRecordTypeId();
        Account acc = new Account(
            RecordTypeID = personAcc,
            FirstName = 'FName',
            LastName = 'LName',
            PersonMobilePhone = '008101234567',
            Phone = '1234567',
            PersonBirthdate = Date.newInstance(1988, 1, 1),
            PersonEmail = '[email protected]'
        );
        insert acc;

        EmailTemplate et = new EmailTemplate(); 
        et.isActive = true; 
        et.Name = 'Test Template'; 
        et.DeveloperName = 'Test_Template'; 
        et.TemplateType = 'text';
        et.Subject = 'Test Subject';
        et.FolderId = UserInfo.getUserId(); 
        et.Body = 'Test Body'; 

        insert et;  //error in this line

        //insert case
        //insert other related objects


        CustomObject__c co = new CustomObject__c(
            Name = 'Test Registration',
            Registrant__c = acc.Id,
            Case__c = c.Id,
            Status__c = 'Registration received',
            Date_Registration_Completed__c = Date.today(),
            Date_Form_received__c = Date.today(),
            Email__c = '[email protected]'
        );
        insert co;
    }

    static testMethod void testSend() {
        CustomObject__c co = [SELECT Id, Name FROM CustomObject__c WHERE Name =: 'Test Registration'];
        co.Send_Email__c = true;
        Test.startTest();
        update co;
        Test.stopTest();

        Integer invocations = Limits.getEmailInvocations();
        System.assertEquals(1, invocations, 'Success');
    }
}
4
  • You have to use System.runAs to get rid of Mixed DML exception. Commented Jul 19, 2019 at 10:07
  • Where should I put it? I tried it inside testSend() but still getting the same error
    – Almond
    Commented Jul 19, 2019 at 10:36
  • 1
    for an alternate approach using fflib - see my blog on asserting email sends with apexmocks
    – cropredy
    Commented Jul 19, 2019 at 17:52
  • why are you mocking an EmailTemplate in testmethod when underlying code depends on a specific template Registration Form ?
    – cropredy
    Commented Jul 19, 2019 at 20:41

2 Answers 2

3

In your setup() method, use System.runAs first to just insert the email template. ie Segregate Setup Object in one block and then rest are non setup objects which can be upserted properly without any issues.

isTest
public class SendRegistrationForm_Test {

    @testSetup 
    static void setup() {

        System.runAs(new User(Id=UserInfo.getUserId())){
            EmailTemplate et = new EmailTemplate(); 
            et.isActive = true; 
            et.Name = 'Test Template'; 
            et.DeveloperName = 'Test_Template'; 
            et.TemplateType = 'text';
            et.Subject = 'Test Subject';
            et.FolderId = UserInfo.getUserId(); 
            et.Body = 'Test Body'; 

            insert et;  //error in this line


        }



        String personAcc = Schema.getGlobalDescribe().get('Account').getDescribe().getRecordTypeInfosByName().get('Person Account').getRecordTypeId();
        Account acc = new Account(
            RecordTypeID = personAcc,
            FirstName = 'FName',
            LastName = 'LName',
            PersonMobilePhone = '008101234567',
            Phone = '1234567',
            PersonBirthdate = Date.newInstance(1988, 1, 1),
            PersonEmail = '[email protected]'
        );
        insert acc;       

        //insert case
        //insert other related objects


        CustomObject__c co = new CustomObject__c(
            Name = 'Test Registration',
            Registrant__c = acc.Id,
            Case__c = c.Id,
            Status__c = 'Registration received',
            Date_Registration_Completed__c = Date.today(),
            Date_Form_received__c = Date.today(),
            Email__c = '[email protected]'
        );
        insert co;
    }

    static testMethod void testSend() {
        CustomObject__c co = [SELECT Id, Name FROM CustomObject__c WHERE Name =: 'Test Registration'];
        co.Send_Email__c = true;
        Test.startTest();
        update co;
        Test.stopTest();

        Integer invocations = Limits.getEmailInvocations();
        System.assertEquals(1, invocations, 'Success');
    }
}
8
  • I could never get Limits.getEmailInvocations() to work reliably for me in testmethods ..has this been fixed? does email deliverability affect this (System v All)?
    – cropredy
    Commented Jul 19, 2019 at 20:00
  • Thanks for clarifying. But I'm getting this error: System.AssertException: Assertion Failed: Success: Expected: 1, Actual: 0 This means that the email was not sent?
    – Almond
    Commented Jul 22, 2019 at 0:33
  • Then when I put the Integer invocations = Limits.getEmailInvocations(); inside startTest(), below update co;, the actual result is 2. Does this means that 2 emails were sent? Why?
    – Almond
    Commented Jul 22, 2019 at 0:54
  • @cropredy Yes, its still the same. Test classes break if deliverability is No or System Only. ffLib is excellent for such use case. Commented Jul 23, 2019 at 20:06
  • @Almond maybe your trigger is sending email twice. Invesigate it Commented Jul 23, 2019 at 20:07
0

In case of Handler having the Mixed DML exception that occurs when a non setup and setup object updated/inserted in same transaction. In order to solve that try using the @future approach.

Links to guide:

Using Future: DML operation on setup object is not permitted after you have updated a non-setup object

Setup/Nonsetup objects: https://success.salesforce.com/answers?id=90630000000ZdmwAAC

https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/reference_objects_setup.htm

You must log in to answer this question.

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