33

Sometimes in Salesforce tests you need to create User objects to run part of the test as a speciifc type of user.

However since the Salesforce Summer 08 update, attempts to create both User objects and normal objects (such as Accounts) in the same test lead to the following error:

MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): User, original object: Account

Note that the error doesn't happen when you run the tests from Eclipse/Force.com IDE, but it does happen when you deploy to Salesforce and then run the tests from within Salesforce.

How do I re-write my tests to avoid this error?

Here's a simple example of a test that causes the error:

static testMethod void test_mixed_dmlbug() {        
    Profile p = [select id from profile where name='(some profile)'];
    UserRole r = [Select id from userrole where name='(some role)'];
    User u = new User(alias = 'standt', email='standarduser@testorg.com', 
            emailencodingkey='UTF-8', lastname='Testing', 
            languagelocalekey='en_US', 
            localesidkey='en_US', profileid = p.Id, userroleid = r.Id,
            timezonesidkey='America/Los_Angeles', 
            username='standarduser@testorg.com');
    Account a = new Account(Firstname='Terry', Lastname='Testperson');
    insert a;

    System.runAs(u) {
        a.PersonEmail = 'test@madeupaddress.com';
        update a;
    }

}
skaffman
  • 398,947
  • 96
  • 818
  • 769
codeulike
  • 22,514
  • 29
  • 120
  • 167

5 Answers5

42

Not many Salesforce people on here yet, I guess.

I found a solution, I don't know why it works, but it works.

All parts of the test that access normal objects need to be wrapped in a System.runAs that explicitly uses the current user, like this:

User thisUser = [ select Id from User where Id = :UserInfo.getUserId() ];
System.runAs ( thisUser ) {
    // put test setup code in here
}

So, the example text_mixed_dmlbug method given in the question, would become:

static testMethod void test_mixed_dmlbug() {  
    User u;
    Account a;      
    User thisUser = [ select Id from User where Id = :UserInfo.getUserId() ];
    System.runAs ( thisUser ) {
        Profile p = [select id from profile where name='(some profile)'];
        UserRole r = [Select id from userrole where name='(some role)'];
        u = new User(alias = 'standt', email='standarduser@testorg.com', 
            emailencodingkey='UTF-8', lastname='Testing', 
            languagelocalekey='en_US', 
            localesidkey='en_US', profileid = p.Id, userroleid = r.Id,
            timezonesidkey='America/Los_Angeles', 
            username='standarduser@testorg.com');
        a = new Account(Firstname='Terry', Lastname='Testperson');
        insert a;
    }
    System.runAs(u) {
        a.PersonEmail = 'test@madeupaddress.com';
        update a;
    }

}

Then the MIXED_DML_OPERATION errors stop happening.

codeulike
  • 22,514
  • 29
  • 120
  • 167
  • Wait, do we need to insert a user record at all? I think you could simply init the new User instance and directly use in system.runAs() – LVS Sep 05 '11 at 12:42
  • But what do I do if I need more than one /new/ user? I'm going to play around with multiple system.runAs() to see if I can finesse user ids for my new users. – tggagne Mar 20 '12 at 03:08
  • It'll still work - you don't need to insert users. Also, what's the use of system.runAs() if you're going to pick the current user? – LVS May 04 '12 at 10:36
  • 3
    Because the contents of the runAs block will get their own context, at least for the sake of MIXED_DML_EXCEPTION. I have no idea why but you can just wrap it in System.RunAs(new User(id = UserInfo.getUserId()){ blah } to get around this. – ca_peterson May 23 '12 at 23:04
  • 6
    Six years later I just hit this problem again and googled myself to here and found that I'd done all this before. Is there a badge for that? – codeulike Apr 28 '16 at 16:29
  • Try to run insert userRole after System.runAs(u) {} and then update role for user. – Nguyễn Thắng May 17 '19 at 02:22
13

It seems like you've found a workaround. I just wanted to try and clear up why you where getting this error.

I think you are running into this issue (per http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dml_non_mix_sobjects.htm):

sObjects That Cannot Be Used Together in DML Operations

Some sObjects require that you perform DML operations on only one type per transaction. For example, you cannot insert an account, then insert a user or a group member in a single transaction. The following sObjects cannot be used together in a transaction:

* Group1
* GroupMember
* QueueSObject
* User2
* UserRole
* UserTerritory
* Territory

Important The primary exception to this is when you are using the runAs method in a test.

In addition, the Summer 08 Release notes (that link is a PDF) say:

In previous releases, in a single transaction that involved triggers, you could perform DML operations on more than one type of sObject, for example, you could insert an account, then insert a user. As of Summer '08, you can only perform DML operations on a single type of sObject from the following list of sObjects.

For example, you cannot insert an account, then insert a user, or update a group, then insert a group member.

  • Group
  • GroupMember
  • QueueSObject
  • User
  • UserRole
  • UserTerritory
  • Territory

In addition, User and Territory now support the insert and update DML operations, and UserRole now supports the insert, update delete and upsert DML operations.

Apex DML operations are not supported on the following sObjects:

  • AccountTerritoryAssignmentRule
  • AccountTerritoryAssignmentRuleItem
  • UserAccountTeamMember
Community
  • 1
  • 1
Paddyslacker
  • 1,880
  • 1
  • 14
  • 20
  • 1
    Yes thats the reason for the error, but it isn't very clear from the SF documentation whether the restriction applies to test methods or not. Some people seem to get this error with tests and some don't. There's a fair bit of discussion about it on the developerforce forums, but none of it is very clear so I thought I'd ask here... Thanks. – codeulike Mar 10 '10 at 09:37
  • 1
    And although the text "Important The primary exception to this is when you are using the runAs method in a test." is not on the referenced page anymore, this workaround still works. – Legolas Jul 30 '13 at 08:38
7

This behavior is actually documented in the salesforce documentation: http://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#StartTopic=Content/apex_dml_non_mix_sobjects.htm?SearchType. Read where it say "Important The primary exception to this is when you are using the runAs method in a test"

Jorge
  • 71
  • 1
  • 1
  • But not when using the runAs method properly inside of test methods. – ca_peterson May 23 '12 at 23:06
  • The text in help has changed in the meantime but still this answer saved my bacon today! Apparently you're OK if you don't specify UserRoleId. And the tests that set UserRoleId pass happily in Eclipse but fail in web test runner... thedailywtf.com, here I come... – eyescream May 21 '13 at 12:12
  • And although the text "Important The primary exception to this is when you are using the runAs method in a test." is not on the referenced page anymore, this workaround still works. – Legolas Jul 30 '13 at 8:38 – Moti Korets Dec 19 '14 at 09:35
1

Just found this in the documentation:

Other Uses of runAs

You can also use the runAs method to perform mixed DML operations in your test by enclosing the DML operations within the runAs block. In this way, you bypass the mixed DML error that is otherwise returned when inserting or updating setup objects together with other sObjects. See sObjects That Cannot Be Used Together in DML Operations.

So it looks like the RunAs workaround is not a workaround but is assumed by Salesforce as the only way of going by the mixed DML issue.

Hope this helps

Reference

Milo
  • 3,365
  • 9
  • 30
  • 44
fredbe
  • 11
  • 2
0

This error is so common when attempting to create user and other objects records in a single transaction in apex.

Workaround in apex class/trigger : use future method for creating user when encountered the error

Workaround in test class : don't try creating a new user data, instead use ))>

code-snippet at - https://thesalesforcedev.blogspot.com/2019/07/mixeddmloperation-dml-operation-on.html