Sara Has No Limits

Good Programmers Evaluate, Simplify, Automate and Document

New Way to Avoid the Dreaded Mixed DMLError in Test Method

As of the Spring 16 release, there is now another way to avoid an error that would often occur when running Apex unit tests. The MIXED_DML_OPERATION error would occur because you can’t perform DML on a setup sObject (such as User, for example) and a non-setup object (such as Contact) in the same transaction.

So, if you had some code such as the following:


@isTest
public class UserAndContactTest {
    public testmethod static void testUserAndContact() {
        Profile p = [SELECT Id FROM Profile WHERE Name='Standard User'];
        UserRole r = [SELECT Id FROM UserRole WHERE Name='COO'];
        u = new User(alias = 'jsmith', email='jsmith@acme.com',
                emailencodingkey='UTF-8', lastname='Smith',
                languagelocalekey='en_US',
                localesidkey='en_US', profileid = p.Id, userroleid = r.Id,
                timezonesidkey='America/Los_Angeles',
                username='jsmith@acme.com');
            insert u;

        Contact currentContact = new Contact(
            firstName = String.valueOf(System.currentTimeMillis()),
            lastName = 'Contact');
        insert(currentContact);
    }
}

The unit test code above would fail with the MIXED_DML_OPERATION error.

Previously, the only way to get around it was to enclose all the operations within a System.runAs block, but now you have another alternative in which you can use @future to bypass the error. For example, the following class could contain the code used to insert the user:


public class InsertFutureUser {
    @future
    public static void insertUser() {
        Profile p = [SELECT Id FROM Profile WHERE Name='Standard User'];
        UserRole r = [SELECT Id FROM UserRole WHERE Name='COO'];
        User futureUser = new User(firstname = 'Future', lastname = 'User',
            alias = 'future', defaultgroupnotificationfrequency = 'N',
            digestfrequency = 'N', email = 'test@test.org',
            emailencodingkey = 'UTF-8', languagelocalekey='en_US', 
            localesidkey='en_US', profileid = p.Id, 
            timezonesidkey = 'America/Los_Angeles',
            username = 'futureuser@test.org',
            userpermissionsmarketinguser = false,
            userpermissionsofflineuser = false, userroleid = r.Id);
        insert(futureUser);
    }
}

And then, you could just change the original code to be the following:


@isTest
public class UserAndContactTest {
    public testmethod static void testUserAndContact() {
        InsertFutureUser.insertUser();

        Contact currentContact = new Contact(
            firstName = String.valueOf(System.currentTimeMillis()),
            lastName = 'Contact');
        insert(currentContact);
    }
}


And then there will be no more error. I think it is a better way of handling the issue than using runAs and should be considered when there is a need to run DML operations for setup and non-setup objects in the same unit test transaction.

Categories: Apex Code

Tags: , , ,

1 reply

  1. I don’t think your new code for inserting user is ever executed in your test, since @future methods only run in tests when you run Test.startTest() or Test.stopTest().

    Can you add an assertion to your test to make sure the user was actually inserted?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s