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 = 'testname@example.com'
        );
        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 = 'testname@example.com'
        );
        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');
    }
}
  • You have to use System.runAs to get rid of Mixed DML exception. – Pranay Jaiswal Jul 19 '19 at 10:07
  • Where should I put it? I tried it inside testSend() but still getting the same error – Almond Jul 19 '19 at 10:36
  • 1
    for an alternate approach using fflib - see my blog on asserting email sends with apexmocks – cropredy Jul 19 '19 at 17:52
  • why are you mocking an EmailTemplate in testmethod when underlying code depends on a specific template Registration Form ? – cropredy Jul 19 '19 at 20:41
2

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 = 'testname@example.com'
        );
        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 = 'testname@example.com'
        );
        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');
    }
}
  • 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 Jul 19 '19 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 Jul 22 '19 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 Jul 22 '19 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. – Pranay Jaiswal Jul 23 '19 at 20:06
  • @Almond maybe your trigger is sending email twice. Invesigate it – Pranay Jaiswal Jul 23 '19 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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.