[SalesForce] How to test @future methods with setup objects and avoid MIXED_DML_OPERATION

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.

Best Answer

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.

Related Topic