1

I have a complex Apex trigger that calls two separate queueable jobs - one job inserts Permission Set Assignments, and another job creates and deletes ContactShare records. I am positive that neither job can invoke the other via trigger actions.

When I run the unit test, I get a mixed DML error from the second job because of the Permission Set Assignment creation in the first job.

Question: If queued jobs are running within the same startTest/stopTest block, can they cause Mixed DML issues with each other? Is there a workaround for it?

The two jobs are initiated as part of a single Apex transaction (inserting a user). I have the user insertion wrapped in a System.runAs() in the test, and it's in a startTest/stopTest block.

I can see from the debug log that when the first job is executed:

14:39:20.596 (6305771684)|CODE_UNIT_STARTED|[EXTERNAL]|01pK0000000HEBm|DESK_UserDomain
14:39:20.596 (6305842037)|SYSTEM_METHOD_ENTRY|[14]|QueueableContextImpl.QueueableContextImpl()
14:39:20.596 (6305854657)|SYSTEM_METHOD_EXIT|[14]|QueueableContextImpl
14:39:20.596 (6308757099)|DML_BEGIN|[396]|Op:Insert|Type:PermissionSetAssignment|Rows:1

I see the second job starting as well, but there's no reference to QueueableContextImpl.QueueableContextImpl. Does that occur only once and apply to all queuable jobs spawned within the startTest/stopTest block?

14:39:20.596 (6494302250)|CODE_UNIT_STARTED|[EXTERNAL]|01pK0000000HAZp|HL_ClientDataSharing
14:39:20.596 (6496669468)|USER_DEBUG|[464]|### Starting Queued Execution for HL_ClientDataSharing.queueClientDataSharing()
  • 1
    Yes they can IIRC. Test each separately. Test the first that it assigns the perm set. Then in another method use runAs to manually assign the permset to simulate what the first queueable does and run the second queueable. The combination of the two test methods will test the full cycle – Eric Oct 13 '17 at 23:36
1

When you call Test.stopTest(), all enqueued asynchronous code runs synchronously. I recommend @TestVisible static Boolean flags to toggle off the Queueable jobs as needed in your test context.

If you are already using a trigger handler framework, it should be dead simple to drop them in. For example, my typical framework would look like:

public with sharing class MyObjectTriggerHandler
{
    // I always include the bypassTrigger flag.

    // It can speed your test suite up tremendously
    // if you implement across the board and use it when you don't need
    // the trigger logic for a particular object to run.

    // It also simplifies testing the service layer
    // when you need the objects to be inserted.
    @TestVisible static Boolean bypassTrigger = false;

    // I would then add these two supplemental flags
    @TestVisible static Boolean assignPermissionSets = true;
    @TestVisible static Boolean createContactShares = true;

    final List<MyObject__c> newRecords;
    final Map<Id, MyObject__c> oldMap;
    public MyObjectTriggerHandler(List<MyObject> newRecords, Map<Id, MyObject__c> oldMap)
    {
        this.newRecords = newRecords;
        this.oldMap = oldMap;
    }

    public void afterInsert()
    {
        if (bypassTrigger) return;

        if (assignPermissionSets) { /*execute permissions job*/}
        if (createContactShares) { /*execute sharing job*/}
    }
}

Then in your test class, you can test each job independently and avoid mixed DML.

static testmethod testInsert_AssignPermissionSets()
{
    MyObjectTriggerHandler.createContactShares = false;
    // set up data

    Test.startTest();
        // execute DML
    Test.stopTest();

    // assert on permissions
}
static testmethod testInsert_CreateContactShares()
{
    MyObjectTriggerHandler.assignPermissionSets = false;
    // set up data

    Test.startTest();
        // execute DML
    Test.stopTest();

    // assert on shares
}

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.