function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Bob BaconBob Bacon 

Creating a test class for trigger that references a lead to copy a field to a task

I have a relatively simple working trigger but I can't seem to get a test class to successfully run.  I have spent a few hours researching for a solution but I'm a novice and still can't figure it out.  Can anyone please help?  TIA

The trigger is:
trigger UpdateAccountType on Task (before insert , before update) { 
    Set<Id> leadId = new Set<Id>(); 
        for(Task t : Trigger.new){ 
            if(t.WhoId != null){ 
                String s1 = t.WhoId; 
            if(s1.left(3) == '00Q'){ 
                leadId.add(t.whoId); 
            } 
        } 
    } 
Lead lead = [Select Id, Type__c From Lead Where Id In : leadId limit 1]; 
    for(Task tk: Trigger.New){ 
    tk.Account_Type__c = lead.Type__c; 
    } 
 }

The test class I'm trying to run is:
@IsTest
Public Class TestAcountType {
static testmethod void insertTask() {
    Task t = new Task();
        t.WhoId = '00QJ000000B1nPQ';
        t.Type = 'Other';
    insert t
    }
}
Best Answer chosen by Bob Bacon
Martijn SchwarzerMartijn Schwarzer
Hi Bob,

First of all:
I'm not sure what your trigger is doing. First you collect all leads from the tasks created. Then you query only the first Lead and assing that type to all the Tasks. Is that what you meant to do? If not, please let me know what the trigger should do exactly, so I can point you in the right direction.

Regarding your test class:
There are a few things you will need to know about unit tests on the Salesforce platform. My advise would be to study the following chapter of the apex developer guide:

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing.htm

Your apex unit test code does not have access to the Salesforce database (by default, you can give that access but that is not a best practice). This means that you will first need to create data for your test class before you run your test. I guess that's where your test is failing. You are providing a hardcoded ID that is not available during test run. Unfortunately, without the error I cannot say this for sure.

This is how your test class could look like, when you create your own test data:
@IsTest
private Class TestAcountType {

  static testmethod void insertTask() {
    //create test data

    Lead l = new Lead(LastName = 'LastName1', Company = 'Company1', Type__c = 'Some Type');
    insert l;

    //Query the just created lead again to get Id
    List<Lead> leads = [Select Id, Name, Company, Type__c From Lead WHERE Company = 'Company1'];
    Lead lead1 = leads.get(0);

    Test.startTest();

    Task t = new Task();
    t.WhoId = lead1.Id;
    t.Type = 'Other';
    insert t;

    Test.stopTest();

  }

}

I hope this points you in the right direction. If this doesn't work, please also post the debug log from your test run. That will help in offering a solution.

Happy coding!

Best regards,
Martijn Schwärzer
 

All Answers

Martijn SchwarzerMartijn Schwarzer
Hi Bob,

First of all:
I'm not sure what your trigger is doing. First you collect all leads from the tasks created. Then you query only the first Lead and assing that type to all the Tasks. Is that what you meant to do? If not, please let me know what the trigger should do exactly, so I can point you in the right direction.

Regarding your test class:
There are a few things you will need to know about unit tests on the Salesforce platform. My advise would be to study the following chapter of the apex developer guide:

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing.htm

Your apex unit test code does not have access to the Salesforce database (by default, you can give that access but that is not a best practice). This means that you will first need to create data for your test class before you run your test. I guess that's where your test is failing. You are providing a hardcoded ID that is not available during test run. Unfortunately, without the error I cannot say this for sure.

This is how your test class could look like, when you create your own test data:
@IsTest
private Class TestAcountType {

  static testmethod void insertTask() {
    //create test data

    Lead l = new Lead(LastName = 'LastName1', Company = 'Company1', Type__c = 'Some Type');
    insert l;

    //Query the just created lead again to get Id
    List<Lead> leads = [Select Id, Name, Company, Type__c From Lead WHERE Company = 'Company1'];
    Lead lead1 = leads.get(0);

    Test.startTest();

    Task t = new Task();
    t.WhoId = lead1.Id;
    t.Type = 'Other';
    insert t;

    Test.stopTest();

  }

}

I hope this points you in the right direction. If this doesn't work, please also post the debug log from your test run. That will help in offering a solution.

Happy coding!

Best regards,
Martijn Schwärzer
 
This was selected as the best answer
Bob BaconBob Bacon
Hi Martijn,

Yes, I was unaware that the default setting for test classes is that they can't pull live sandbox data.  With your additional lines of code to add those variables the test class now works!  Thanks!

FYI: The original trigger is intended to write the contents of the lead.type__c field into the task.account_type__c field.  In the testing I have performed it seems to work.  Is there a flaw in my logic or testing?  TIA
Martijn SchwarzerMartijn Schwarzer
Hi Bob,

I assume you want to have the task.account_type__c to be filled with the type__c field of the corresponding Lead. Is that correct?

The way your trigger works NOW is as follows:

Lets say we have 2 leads:

Lead 1: Type__c = 'Type1'
Lead 2: Type__c = 'Type2'

If you insert 2 tasks (one for each lead), the following would happen:

They would both get 'Type1' as account_type__c.

if you want to have task1 to be filled with 'Type1' and task2 with 'Type2', you will need to adjust your trigger like this:
 
trigger UpdateAccountType on Task (before insert , before update) { 
    Set<Id> leadId = new Set<Id>(); 
    for(Task t : Trigger.new){ 
        if(t.WhoId != null && t.WhoId.left(3) == '00Q'){ 
            leadId.add(t.whoId); 
        } 
    } 

    //Query all leads from trigger.new (I removed the Limit 1 --> this will only retrieve the first lead)
    List<Lead> leads = [Select Id, Type__c From Lead Where Id In : leadId];

    //Create map for easy retrieval of records
    Map<Id, Lead> leadMap = new Map<Id, Lead>(leads);
 
    for(Task tk: Trigger.New){ 
        //Check if lead is available in Map
        if(leadMap.containsKey(t.WhoId){
            Lead lead = leadMap.get(t.WhoId);  //Get lead from Map
            tk.Account_Type__c = lead.Type__c; 
        }
    }
 }

Hope this helps!

Happy coding and thanks for marking best answer!

Regards,
Martijn Schwärzer

 
Martijn SchwarzerMartijn Schwarzer
In the example code above, I made a typo. In the 2nd for-loop, the variable should be tk and not t. (tk.WhoId in stead of t.WhoId)

Sorry.
Bob BaconBob Bacon
Hi Martijn,

Not being a coder I'm in no position to challenge your proposed chnages except that when I test the original code in the sandbox it works as intended.  Task.Account_Type_c always equals lead.Type__c.

Test Methodology:

I created three leads, each with a different Type__c and when I created tasks from each of the leads, the Account_Type__c contents were correct. 

I revised the Type__c value on each of the three leads and created a new task on each lead.  The Account_Type__c contents were correct.

I edited and saved each of the original tasks and their Account_Type__c field was updaetd to refled the new Type__c value on the revised leads. 

Is there something flawed about my testing methodology or is there something about the original trigger that will work in the sandbox but not in production?

Thanks again for your help.

Bob
Martijn SchwarzerMartijn Schwarzer
Hi Bob,

Your testing methods are definitely correct (if you use the Salesforce UI). 

You will see that the code fails if you do the following:

In your test class:
1: create 3 leads with 3 different Type__c values
2: add those 3 leads to a list
3: insert the list

4: create 3 tasks, 1 for each lead (don't insert them yet!)
5: Add the 3 tasks to a list
6: Insert the list.

You will now see that all 3 tasks have the same Account_Type__c value.

Here's the code to do so (I adjusted the test class from previous post):
 
@IsTest
private Class TestAcountType {

  static testmethod void insertTask() {
    //create test data

    Lead l = new Lead(LastName = 'LastName1', Company = 'Company1', Type__c = 'Some Type');
    insert l;

    //Query the just created lead again to get Id
    List<Lead> leads = [Select Id, Name, Company, Type__c From Lead WHERE Company = 'Company1'];
    Lead lead1 = leads.get(0);

    Test.startTest();

    Task t = new Task();
    t.WhoId = lead1.Id;
    t.Type = 'Other';
    insert t;

    Test.stopTest();

  }

  static testmethod void testInsertMultipleTasks(){
    //create test data

    List<Lead> leadsToInsert = new List<Lead>();
    Lead lead1 = new Lead(LastName = 'LastName1', Company = 'Company1', Type__c = 'Type1');
    Lead lead2 = new Lead(LastName = 'LastName2', Company = 'Company2', Type__c = 'Type2');
    Lead lead3 = new Lead(LastName = 'LastName3', Company = 'Company3', Type__c = 'Type3');
    leadsToInsert.add(lead1);
    leadsToInsert.add(lead2);
    leadsToInsert.add(lead3);

    insert leadsToInsert;

    //Query the just created leads again to get Id
    List<Lead> leads = [Select Id, Name, Company, Type__c From Lead];

    //Make sure all 3 leads are created
    system.assertEquals(3, leads.size(), 'There should be three leads in the database');

    Lead lead1fromDb = leads.get(0);
    Lead lead2fromDb = leads.get(1);
    Lead lead3fromDb = leads.get(2);

    Test.startTest();

    List<Task> tasksToInsert = new List<Task>();

    Task task1 = new Task(WhoId = lead1fromDb.Id, Type = 'Other');
    Task task2 = new Task(WhoId = lead2fromDb.Id, Type = 'Other');
    Task task3 = new Task(WhoId = lead3fromDb.Id, Type = 'Other');

    tasksToInsert.add(task1);
    tasksToInsert.add(task2);
    tasksToInsert.add(task3);

    insert tasksToInsert;

    Test.stopTest();

    system.debug('Task1 Account_Type__c: ' + Task1.Account_Type__c);
    system.debug('Task2 Account_Type__c: ' + Task1.Account_Type__c);
    system.debug('Task3 Account_Type__c: ' + Task1.Account_Type__c);

    system.assertEquals(lead1fromDb.Type__c, task.Account_Type__c, 'Task1 Account Type must match Lead1 Type__c');
    system.assertEquals(lead2fromDb.Type__c, task.Account_Type__c, 'Task2 Account Type must match Lead2 Type__c');
    system.assertEquals(lead3fromDb.Type__c, task.Account_Type__c, 'Task3 Account Type must match Lead3 Type__c');

  }

}

You will find that the code fails at the assertions at the end. If you check the debug logs, you will see that all 3 tasks have the same Account_Type__c value.

This has to do with bulkifying your code (best practice: Always bulkify everything). As long as people use the Salesforce UI, it will keep on working. But try loading a bunch of tasks via the data loader. There will be 200 records at the same time inserted in Salesforce (and hitting your trigger. Those records will be in Trigger.new). That's when the code will fail.

But hey, since you're not a coder, I don't expect you to know that. You did a great job! It's just a tip from my side to prevent corrupt records when someone loads a buch of tasks using the API's (e.g. via the Data loader).

Keep up the good work! And if you run into issues, you know where to find the community :-)

Happy coding!

Regards,
Martijn Schwärzer
Bob BaconBob Bacon
Hi Martijn,

I get it now.  Thanks for taking the time to explain.  In fact, we use TalkDesk which does create tasks via an API so I'm going back to your proposed code and start over!

I copied the exact code you proposed and it wouldn't save.  I got "Compile Error: unexpected token: '{' at line 17 column 40"  I counted brackets and they even out.  Any thoughts?

Once we get past this issue, will the Apex class have to be changed to accomodate your code design or will it work as is?

Thanks again.  Cheers!

Bob