6

I’m in a situation where I have to use a future method to avoid MIXED_DML_OPERATION error. I have a trigger that runs after INSERT on USER and creates a custom object X. They are in 1:1 relation. User is an owner of X and sharing model is private.

I’m not sure what is the best approach to write tests for this scenario. I want to keep my data isolated so in tests I need to create a User and X he owns. I can think of 2 scenarios:

1) Create a mock user and let the trigger create X for the test

Because in test context @future methods are executed when the Test.stopTest() is called I would have to enclose my set up section in Test.start/stopTest(). This means I can’t use these two later in the method where I want to test a particular logic on X.

2) Create a mock user, don't wait for future method and create X manually in mock section

I can write a single test for creation of X in the trigger. In all other tests checking the business logic on X I create a user, manually create X setting the user as the owner. The problem is that now if later in the method I call Test.start/stopTest() and do a select from X where ownerId = user.id I will get two X records.

To my surprise I discovered that I’m able to insert X objects from the trigger while running a test. I didn't use the @future method and I got no MIXED_DML_OPERATION error ( - a bug?).
for(User aUser: newUsers){ newXtoInsertFromFuture.put(aUser.ID, aUser.lastName ) objectX.add(new X(ownerID=aUser.id, property=aUser.lastName)); } if(Test.isRunningTest()){ insert objectX; }else{ matchUsersWithX(newXtoInsertFromFuture); }

With this code in trigger I can create a user in a test set up section and the corresponding X is created synchronously. I’m not sure why things work this way. Anyway this looks like a dirty hack that yields the same problems as 2) if I ever call Test.start/stopTest() in the test.

Could any of you good people out there tell me what is the correct way to write tests in my case? thanks in advance.

  • Any solution here? running into the same thing – NSjonas Jan 9 '15 at 20:44
9

A longstanding trick that I've seen used time and time again to work around MIXED_DML_OPERATION when doing test data setup is to wrap the setup DML operation in a

System.runas(new User(Id = UserInfo.getUserId())){
    insert myTestUser;
}

The System.runAs(User) method is only available inside of tests, but by setting up a new context albeit with the currently running user the MIXED_DML_EXCEPTION problem is avoided.

Edit: now that I'm the PM for Apex I'm officially declaring this supported. There's no way we could remove it with how much it's used.

Edit in response to Eric's comment on my answer: @future methods queued during test method runs wait until the end of the test and then all run synchronously, in the order they were invoked. The exception to this is that Test.stopTest() forced @future methods to run right then. It might be possible to leverage the two of these to get a working solution.

  • 1
    I was gonna say the same thing but the issue appears to be the fact that the trigger is on User creation. Thus even when wrapper in system.Runas you would get mixed DML as the non set up item is created in the trigger via future method....Unless I am misunderstand the OP description – Eric Oct 5 '14 at 0:34
  • Thank you folks for your input. Yes @Eric it's the asynchronous nature of the @future that is causing me a grief. I implemented the runAs() trick a while ago much like the way it's explained here (if anyone else in need bumps here) – user682217 Oct 5 '14 at 9:22
  • you could also - knowing that your trigger is handling creation of X record - call test.stopTest() and then query to get the results and assert that the X records were created and assert that the Owner field is set to your user record that was created. – Ronnie Dec 15 '17 at 4:36
0

After lot's of digging I found a work around in this blog post. Figured I'd post it in case someone else winds up here first like I did.

Taken from section #2 regarding test code.

 @future
  public static void createGroupMembers(Set<Id> groupIds) {
    List<GroupMember> newGroupMembers = new List<GroupMember>();

    for (Id grpId : groupIds) {
        newGroupMembers.add(new GroupMember(GroupId=grpId, UserOrGroupId=UserInfo.getUserId()));
    }

   if (Test.isRunningTest()) {
     // start new context via system.runAs() for the same user for test code only
      System.runAs(new User(Id = Userinfo.getUserId())) {
        insert newGroupMembers;
      }
    } else {
      // in non-test code insert normally
      insert newGroupMembers;
    }

  }

As you can see, the solution is to wrap the system object DML in a System.runAs().

The whole blog is very helpful and work reading in its entirety.

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.