MIXED_DML_OPERATION error and Apex unit tests (how to fix it)

This is a common error when we start to work with testing or manipulating some particular objects: setup and non-setup objects


Andres Canavesi
Feb 09, 2022
One option is to use @future method but since that's for running in background, we cannot query that data in the next line due to it could be created or not.
featured image
This is a common error when we start to work with testing or manipulating some particular objects: setup and non-setup objects. During test setup, we have to create some data to be used by the test because we don't have access to the real org's data unless we use the flag SeeAllData=True which is not recommended.

Table of contents

The error

Let's say you want to create an Account and later a User. If you try to do it in the same transaction you will get this error (or similar): 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): Account, original object: PermissionSetAssignment: []

Why?

The Salesforce documentation is pretty clear and basically, it says you cannot do a DML (insert or update) operation at the same time with objects that involve access-lever permissions

DML operations on certain sObjects, sometimes referred to as setup objects, can't be mixed with DML on other sObjects in the same transaction. This restriction exists because some sObjects affect the user’s access to records in the org. You must insert or update these types of sObjects in a different transaction to prevent operations from happening with incorrect access-level permissions. For example, you can’t update an account and a user role in a single transaction.

The solution

Let's see how to fix the MIXED_DML_OPERATION error

One option is to use @future method but since that's for running in background, we cannot query that data in the next line due to it could be created or not.

Another solution is to insert setup objects such as User using the current user during the execution. We choose this one since we can access the user later in the test

      
System.runAs(new User(Id = UserInfo.getUserId())){ createUser(); }
   
    

The full example

      

  @isTest
  public class TestTest {

  @isTest static void itDoesNotWork() {
  createUser();
  createAccount();
  }

  @isTest static void itWorks() {
  System.runAs(new User(Id = UserInfo.getUserId())){ createUser(); }
  User user = [SELECT Id FROM User WHERE UserName LIKE 'admin%' LIMIT 1];
  createAccount();

  }

  private static void createAccount(){
  String random = String.valueof(DateTime.now().getTime());

  Account acc = new Account(Name = random);
  acc.CurrencyIsoCode = 'USD';
  acc.BillingCity = 'New York';
  acc.BillingCountry = 'United States';
  acc.BillingState = 'New York';
  acc.BillingStreet = 'abc 1234';
  acc.Website = 'www.google.com';

  insert ACC;

  }

  private static void createUser(){
  String random = String.valueof(DateTime.now().getTime());

  Profile profile = [SELECT Id FROM Profile WHERE Name='System Administrator'];

  User user = new User();
  user.Email = 'random@random.com'+random;
  user.UserName = 'admin@random.com'+random;
  user.LastName = 'random'+random;
  user.Alias = 'random';
  user.ProfileId = profile.Id;
  user.EmailEncodingKey='UTF-8';
  user.LanguageLocaleKey='en_US';
  user.LocaleSidKey='en_US';
  user.TimeZoneSidKey = 'America/Los_Angeles';

  insert user;
  }
  }

  
  
As you can see in the image below, the first test failed and the second one worked (the one running as current user)
Output of the test execution in the Salesforce Developer Console

Resources

Featured bhoto by Nik Shuliahin on Unsplash