0

Working on what I think should be a simple trigger. At this point, I am assuming there is something basic flawed in my DML or understanding of the order of Execution.

If a lead has a lead source of Website then I want to check their email against existing leads and contacts. IF there is a match on the email address then assign the lead to a queue to be checked.

I have tried using a before insert trigger and nothing was updated in my test classes. In an after trigger, my code fails because the fields are not writeable. I thought maybe I had to get the records out of Trigger.new. So I have tried cloning and adding them to a new list. In each case, my test fails.

Thank you for taking the time to help.

public with sharing class DuplicateLeadHandler {
    public static void execute(List<Lead> leads, Map<Id, Lead> leadMap) {

        List<Lead> leadsToProcess = new List<Lead>();
        List<String > emails = new List<String>();
        Map<String, Lead> leadEmailMap = new Map<String, Lead>();
        Map<String, Contact> contactEmailMap = new Map<String, Contact>();
        Group duplicateGroup = [
                SELECT Id
                FROM Group
                WHERE DeveloperName = 'Website_Duplicates'
                LIMIT 1];
        //check to see if they are a website lead. If true then check for matching emails.
        for (Lead l : leads) {
            if (l.LeadSource == 'Website') {
                leadsToProcess.add(l);
                emails.add(l.Email);
            }
        }
        if (!leadsToProcess.isEmpty()) {
            leadEmailMap = DuplicateLeadHandler.getLeads(emails);
            System.debug('Matching Lead Emails: ' + leadEmailMap.values());
            contactEmailMap = DuplicateLeadHandler.getContacts(emails);
            System.debug('Matching Contact emails: ' + contactEmailMap.values());
        } else {
            return;
        }
        //check to see if emails were found
        if (!leadEmailMap.isEmpty() || !contactEmailMap.isEmpty()) {
            for (Lead l : leadsToProcess){
                if(leadEmailMap.containsKey(l.Email)){
                    l.OwnerId = duplicateGroup.Id;
                }
            }
        }
    }

    public static Map<String, Lead> getLeads(List<String> emails) {
        Map<String, Lead> leadEmailMap = new Map<String, Lead>();
        List<Lead> leadList = ([
                SELECT Email
                FROM Lead
                WHERE Email = :emails
        ]);
        if (!leadList.isEmpty()) {
            for (Lead l : leadList) {
                leadEmailMap.put(l.Email, l);
            }
        }
        return leadEmailMap;
    }
    public static Map<String, Contact> getContacts(List<String> emails) {
        Map<String, Contact> contactMap = new Map<String, Contact>([
                SELECT Email
                FROM Contact
                WHERE Email = :emails
        ]);
        return contactMap;
    }
}


Here is the test



@IsTest
public class DuplicateLeadHandler_Test {
    @TestSetup
    public static void setUp() {
        Lead l = new Lead(LastName = 'Kirk', FirstName = 'James', Company = 'Enterprise', Email = 'jtkirk@enterprise.com');
        insert l;
        Account a = new Account(Name = 'Star Ship Enterprise');
        insert a;
        Contact c = new Contact(FirstName = 'Mr', LastName = 'Spock', AccountId = a.Id, Email = 'mspock@enterprise.com');
        insert c;

        //create a test user
        Profile p = [
                SELECT Id
                FROM Profile
                WHERE Name = 'Standard User'
        ];
        User u = new User(Alias = 'standt', Email = 'standardTestUser@fake.com', EmailEncodingKey = 'UTF-8', ProfileId = p.Id,
                Username = 'test1972AGA@user.com', TimeZoneSidKey = 'America/New_York', LocaleSidKey = 'en_US', LanguageLocaleKey = 'en_US',
                LastName = 'Test');
        insert u;

    }
    @IsTest
    public static void noRecordsFound() {
        //given
        User u = [SELECT Id FROM User WHERE Alias = :'standt'];
        Lead lead = new lead(LastName = 'test', Company = 'Test Co', Email = 'fake@fake1.com', FirstName = 'Test', OwnerId = u.Id);
        insert lead;
        Account a = new Account(Name = 'Test Acc');
        Lead updateLead = [SELECT OwnerId FROM Lead WHERE Email = :'fake@fake1.com' LIMIT 1];
        System.assertEquals(u.Id, updateLead.OwnerId, 'Owner Id was changed');
    }
    @IsTest
    public static void recordsFound() {
        Group dupQueue = [SELECT Id FROM Group WHERE DeveloperName = 'Website_Duplicates' LIMIT 1];
        User u = [SELECT Id FROM User WHERE Alias = :'standt'];
        Lead lead = new lead(LastName = 'test', Company = 'Test Co', Email = 'jtkirk@enterprise.com', FirstName = 'Test', OwnerId = u.Id, LeadSource = 'Website');
        insert lead;
        System.debug('queue id ' + dupQueue.Id);
        Lead updateLead = [SELECT OwnerId FROM Lead WHERE Email = 'jtkirk@enterprise.com' LIMIT 1];
        System.debug('ownerId in test = ' + updateLead.OwnerId);
        System.assertEquals(dupQueue.Id, updateLead.OwnerId, 'Owner Id was not changed');


    }
}


2

Your test fails because of this line:

    Lead updateLead = [SELECT OwnerId FROM Lead WHERE Email = 'jtkirk@enterprise.com' LIMIT 1];

This is querying the lead record created in @testSetup. Instead, do this:

    Lead updateLead = [SELECT OwnerId FROM Lead WHERE Id = :lead.Id];

As an aside, I'd recommend you don't name your variable after an sobject (e.g. Lead lead), as this can cause confusion or unexpected compilation errors.

  • Thanks as always. It never occurred to me that I would be running the query on TestSetup. Thanks as always -just learned something new! – Brooks Johnson Jun 10 '19 at 20:44
  • 1
    @BrooksJohnson Yes, filtering by email without an ORDER means the database would "randomly" choose one to return. It so happens that the default order for most queries are by ID ascending (at least for now), which means the record created earlier would be returned first. However, that could change in the future, in which case the test might have passed/failed randomly, etc. When you know which record you're trying to look at, you should always prefer to query the record by ID. – sfdcfox Jun 10 '19 at 20:48
1

Things to check:

  • is your class actually being called in the before insert trigger? Sounds basic but I've made this mistake a few times. Or maybe the class is called, but the trigger itself is not handling the before insert context (check the trigger definition)

    • If you are calling this on beforeInsert, you don't need the following DML call

update leadsToUpdate;

  • Do you have any assignment rules or workflows on leads that could be changing the owner back to a user? These would run AFTER the beforeInsert triggers (read on the order of execution)

  • Have you tried testing this via the UI? Sometimes apex behaves somewhat different to what you would expect from the UI. If it works from the UI, then you know your code works, and something is wrong with the way you are testing it.

  • Also this query is wrong, you should not use the colon sign here, because you are filtering by a text literal, not a variable

    Lead updateLead = [SELECT OwnerId FROM Lead WHERE Email = :'jtkirk@enterprise.com' LIMIT 1];

  • Code is being called and I have 100% code coverage. I tried doing it on before insert and nothing was updated. If I try on After I get an error that the record is ready only. Testing in the UI also throws an error that the record is read only. – Brooks Johnson Jun 10 '19 at 20:10
  • 1
    Did you try on before insert without the DML call I mentioned? The read only error would happen if you try do the DML call on before insert. Also, you didn't mention anything about workflows or assignment rules..did you check on those? – CommonCoreTawan Jun 10 '19 at 20:16
  • No work flows etc that would touch the owner. I will refactor and try again with a before insert and no DML call. But in my previous attempt, the records were not being updated. That is why gave after insert a try. – Brooks Johnson Jun 10 '19 at 20:18
  • You should only use after insert to update OTHER records, not the records in context. And if you use before insert, you simply need to set the field values, there's no need to use a DML call because it's already implied. – CommonCoreTawan Jun 10 '19 at 20:24
  • I know, but my tests were not passing that is why I was trying different approaches. I did a quick refactor. It works on the UI but still fails the test. I will update the code above and include the test. – Brooks Johnson Jun 10 '19 at 20:36

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.