0

I'm a beginner APEX developer in a bit of a tricky spot.

As we know, leads get assigned to sellers. However, sellers get fired from time to time causing them to become inactive users. The issue then arises when we have inactive users that are owners of Open Leads. Ideally, I would like to automatically change any Lead Owner that is an inactive users back to a standard queue that we have created (we have different queue IDs depending on the country and industry) so that they can be manually re-assigned by a manager to active sellers.

In order to do this I wrote a trigger and a class which works perfectly but I am having difficulty making a test class for code coverage -

TRIGGER

trigger ResetInactiveUser on User (after update) {

       for(User u: trigger.new){
    
           if(u.IsActive == False){
    
           InActiveUserToQueue.changeOwnerToQueue(u.Id);
    
      }


    }

      }

CLASS

public class InActiveUserToQueue{

@future
public static void changeOwnerToQueue(String DeactivatedUserId){
    
    List<Lead> leadList = new List<Lead>();
    List<lead> updatedQueue = new List<Lead>();
    
    leadList = [SELECT Id, OwnerId, Countryy__c, Industry__c, Lead_Type__c, Status from lead where OwnerId =:DeactivatedUserId];
    
    for(Lead l : leadList){
        
        if(l.Countryy__c == 'France' && l.Industry__c == 'Accounting' && l.Lead_Type__c == 'Outbound' && l.Status == 'Open'){
            
            l.OwnerId = '00G5J000000pX41'; #Queue ID for Outbound France Accounting Leads
            updatedQueue.add(l);         
                            
        }
        
        update updatedQueue;
        
    }
           
}
  }

The only issue I'm having now is to write an appropriate test class to get the necessary code coverage! So far this is what I have but it results in Mixed DML Errors.

@isTest
public class fml {
public static testMethod void insertUser(){
    test.startTest();       
    User testUser = new User(FirstName = 'TEST',      
    LastName = 'GUY',
    Alias = 'TEST',
    Username = '[email protected]',
    Email = '[email protected]',
    CommunityNickname = 'sdfjklfdzpzp',
    ProfileId = '00e5J000000gqvQQAQ',
    
    EmailEncodingKey='UTF-8',
    LanguageLocaleKey='en_US',
    LocaleSidKey='en_US',
    TimeZoneSidKey='America/Los_Angeles',
    IsActive = True);
    
    insert testUser;
    
    test.startTest();
    testUser.IsActive = False;
    update testUser;
    
    
Lead testLead = new Lead();
testLead.OwnerId = '0055J000001t4JpQAI';
testLead.Company = 'TESTCOMP';
testLead.FirstName = 'TEST';
testLead.LastName = 'LEAD';
testLead.Countryy__c = 'France';
testLead.INDUSTRY__c = 'Accounting';
testLead.CurrencyIsoCode = 'EUR';
testLead.Lead_Type__c = 'Outbound';
testLead.Status = 'Open';
    
    insert testLead;
           
    testUser.IsActive = False;
    update testUser;
    
 List<Lead> testLeadList = new List<Lead>();
 List<lead> testupdatedQueue = new List<Lead>();
 List<Lead> testUserId = new List<Lead>();        
 testLeadList = [SELECT Id, OwnerId, Countryy__c, Industry__c, Lead_Type__c, Status from lead where OwnerId =:testLead.OwnerId];                     
    
        for(Lead l: testLeadList){
            
           if(l.Countryy__c == 'France' && l.Industry__c == 'Accounting' && l.Lead_Type__c == 'Outbound' && l.Status == 'Open'){
            
             l.OwnerId = '00G5J000000pX41';
             testupdatedQueue.add(l);         
                            
         }
           
        }
    
    update testUpdatedQueue;
    test.stopTest(); 
 }


  }

Your support is vital and I greatly appreciate any response! Thank you!

1

1 Answer 1

2

You run into the Mixed DML error when you try to update setup objects and non-setup objects in the same context. Most of the SObjects we can work with fall into the "non-setup" category, and the User SObject is the most common one you'll find people using in the "setup" category.

In most cases, we need to explicitly create our test data. Users are an exception to that. The actual User records in your org are always available in unit tests, though it's still a good idea to create your own test User for this.

The way we get around this is by using System.runAs(). You'll typically create and insert "setup" object data within a System.runAs() block, and keep all of the normal non-setup object code outside of it.

With that in mind, your test method would become something like this

@isTest
public class fml {
    public static testMethod void insertUser(){
        // We can only call Test.startTest() once inside of each test method
        // It has a few purposes, one of which is to give you a fresh set of governor
        //   limits to work with, so you don't want to call this method yet.
        //test.startTest();
    
        // You can create a User instance in-memory like this without an issue
        // It's the DML that we need to be wary of
        User testUser = new User(
            FirstName = 'TEST',      
            LastName = 'GUY',
            Alias = 'TEST',
            Username = '[email protected]',
            Email = '[email protected]',
            CommunityNickname = 'sdfjklfdzpzp',
            ProfileId = '00e5J000000gqvQQAQ',
            EmailEncodingKey='UTF-8',
            LanguageLocaleKey='en_US',
            LocaleSidKey='en_US',
            TimeZoneSidKey='America/Los_Angeles',
            IsActive = True
        );
    
        insert testUser;
    
        // This is deactivating the user too early
        // You want to assign a lead to your test user first
        /*test.startTest();
        testUser.IsActive = False;
        update testUser;*/
    
        // Using the SObject constructor to set name-value pairs for fields saves a bit
        //   of typing (and more typing = more chance to make a mistake)
        Lead testLead = new Lead(
            // Hard-coding Ids is bad practice, and you also want to be assigning your
            //   test user as the owner
            OwnerId = testUser.Id, //'0055J000001t4JpQAI',
            Company = 'TESTCOMP',
            FirstName = 'TEST',
            LastName = 'LEAD',
            Countryy__c = 'France',
            INDUSTRY__c = 'Accounting',
            CurrencyIsoCode = 'EUR',
            Lead_Type__c = 'Outbound',
            Status = 'Open'
        );
    
        // Everything inside the runAs block is run as if it were being executed by the
        //   specified user
        // Typical usage is to just run as yourself (or rather, run as whoever is running 
        //   the test)
        // The crucial part is that doing this gives us enough of a separate execution
        //   context to keep Salesforce from complaining
        // Normally, you'd wrap the DML for a test User in here, but seeing as how
        //   we're mainly working on the User object here, it makes more sense
        //   to wrap the DML for the Lead.
        // The important part is that your test method does not mix DML
        //   for Users and other SObjects without using System.runAs()
        System.runAs(new User(Id = UserInfo.getUserId())){
            insert testLead;
        }
    
        // Here is where you want to use Test.startTest()
        // Everything up to this point was setting up your test
        Test.startTest();
        testUser.IsActive = False;
        update testUser;
    
        // Once you're done executing the code you are trying to test, it's time to call
        //   stopTest
        Test.stopTest();
    
        // Your test should not copy/paste code from the class (or trigger) that you're trying
        //   to test
        // The purpose of a test is to run and stress the code you've already written
        //   so that you can verify that the code you've written is free of bugs
        // All of this multi-line commented code should be deleted
        /*List<Lead> testLeadList = new List<Lead>();
        List<lead> testupdatedQueue = new List<Lead>();
        List<Lead> testUserId = new List<Lead>();        
        testLeadList = [SELECT Id, OwnerId, Countryy__c, Industry__c, Lead_Type__c, Status from lead where OwnerId =:testLead.OwnerId];                     
    
        for(Lead l: testLeadList){    
            if(l.Countryy__c == 'France' && l.Industry__c == 'Accounting' && l.Lead_Type__c == 'Outbound' && l.Status == 'Open'){
                l.OwnerId = '00G5J000000pX41';
                testupdatedQueue.add(l);                
            }
        }
    
        update testUpdatedQueue;
        test.stopTest();*/
    
        // Once you're done executing the code to be tested, the final part of writing
        //   a unit test is to gather your results and make assertions to verify
        //   that your code behaves the way you think it should

        // In this case, you would want to query your testLead again (storing it in
        //   a separate variable), and make at least the following assertions
        // - The testLead and afterTestLead have different owners
        // - afterTestLead's owner is your specified queue (a sub-type of the Group SObject)
    }
}

A few extra notes:

  • Whenever you see yourself doing something like if(someValue == true), you can always shorten that to just if(someValue)
  • Likewise, if(someValue == false) can be shortened to if(!someValue)
  • You generally want to write methods to work on collections of things (lists, sets, or maps) rather than single instances. You should consider gathering a list of inactive user ids in your trigger, and then passing the whole list to changeOwnerToQueue()
    • This is a best practice to avoid having queries and DML appear inside of loops. Salesforce only gives us so many queries and DML to use in a single transaction, so having these things inside of loops (directly or indirectly) is generally going to send you careening head-first into those limits
    • This does mean that you'll want to re-write your changeOwnerToQueue() method. Luckily, there's not much you'll need to change (instead of WHERE OwnerId = :someId in your query, you'd do WHERE OwnerId IN :collectionOfIds)
  • Pull that update updatedQueue; outside of your loop! If you had 10 leads to update, this code would update 9 of those leads multiple times (a total of 55 dml rows and 10 DML operations instead of 10 dml rows and 1 DML operation)
5
  • Hi Derek, thanks for taking the time to give such an elaborate and helpful response! Plenty of useful suggestions here. I tried implementing your code with system.RunAs as you have indicated and I still encountered the Mixed DML issue. What gives? Commented Sep 15, 2021 at 17:15
  • @SexyGrandpa I was unsure if that would happen or not, but not entirely surprised that it did. Perhaps wrapping the insert testLead; inside of a system.runAs() instead of wrapping the insert testUser; would do the trick. I'll try to find some time to test that further.
    – Derek F
    Commented Sep 15, 2021 at 17:29
  • You're a LEGEND! The code executes perfectly now. Next, I need to perform assertions as you have indicated. Could you give me a few more pointers on how I perform the assertions? To begin with you mentioned that I shouldn't use the copy/pasted code from my class yet won't I have to write something similar to simulate the OwnerId being updated to the Queue ID? Thank you so much for your help !!!! Commented Sep 15, 2021 at 18:37
  • @SexyGrandpa (1/2) Assertions are made by calling System.assert(), System.assertEquals() or System.assertNotEquals() (assertEquals() and assertNotEquals() are generally the ones you will be using). If you haven't gone through it already, the Apex Testing module on trailhead does a decent job of explaining things.
    – Derek F
    Commented Sep 15, 2021 at 18:53
  • @SexyGrandpa (2/2) Unit tests are meant to cause other code to be run (and every line of code that is executed as a result of a unit test gains "coverage"). The testUser.IsActive = False; and update testUser; in your unit test (between the calls to startTest()/stopTest() ) causes your actual trigger to run. That, in turn, causes InActiveUserToQueue.changeOwnerToQueue() to be called. No simulation required. The job of the unit test is to set up specific data to allow your code to be run (and we do this because unit tests run isolated from your "real" data)
    – Derek F
    Commented Sep 15, 2021 at 19:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .