[SalesForce] System.DmlException: Insert failed: MIXED_DML_OPERATION, DML

So I have some code below that causes the error:

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): GroupMember, original object: Submit_CI__c: []

Here is the method in question:

 @future
public static void addToPublicGroup(List<Id> uid, List<Id> gid)
{
//if(!Test.isRunningTest())
//{
    if(!uid.isEmpty())
    {
        List<GroupMember>  lstGm = new List<GroupMember>();
        for(integer j = 0; j<uid.size() ; j++)
        {
            lstGm.add(new GroupMember(UserOrGroupId = uid[j], GroupId = gid[j]));
        }        
        //if(!Test.isRunningTest())
            insert lstGm;
        utility.AddMemToPbGroup = true;
     }    
//}
 }

I have looked around and have seen you can use the following:

    System.RunAs(usr)
    {
        Test.startTest();
           myInsertMethod();
        Test.stopTest();
    }
}

However, within 'myInsertMethod()' I am not sure what to write. Can I just write 'insert 1stGm;'

@future
private static void myInsertMethod(){

//What shall I write here?
//Can I pass parameters in?

}

Best Answer

If the underlying code under test does DML on GroupMember and DML on "normal" (i.e. non-setup) SObjects like Account or Contact, then the DML on GroupMember has to be done in a separate transaction such as @future or a queueable. Your OP indicates that GroupMember is done in a @future.

For the testmethod, if you need to pre-insert some GroupMember to test logic, you will need to do the following

System.runAs([select Id from User where Id = :UserInfo.getUserId()][0]) {
  insert GroupMember(...); // setup objects must be inserted in a separate context
}

Test.startTest();  
  new MyClass().doWork(); // code path eventually ends up calling addToPublicGroup
Test.stoptest(); // this will cause the future method to execute
System.assert(...whatever is needed...);