Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[![Open in Visual Studio Code](https://classroom.github.com/assets/open-in-vscode-718a45dd9cf7e7f842a935f5ebbe5719a5e09af4491e668f4dbf3b35d5cca122.svg)](https://classroom.github.com/online_ide?assignment_repo_id=15052810&assignment_repo_type=AssignmentRepo)
# Developer Kickstart: Testing and Debugging
This repository is an essential segment of the Developer Kickstart curriculum at Cloud Code Academy. Tailored specifically for up-and-coming Salesforce developers, this module plunges into the crucial aspects of testing and debugging, underscoring the necessity of robust test classes, effective debugging strategies, and the maintenance of high code coverage.

Expand Down
108 changes: 68 additions & 40 deletions force-app/main/default/classes/LeadTriggerHandler.cls
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,32 @@ public with sharing class LeadTriggerHandler {
*/
public static void handleTitleNormalization(List<Lead> leadsToNormalize) {
for (Lead ld : leadsToNormalize) {
if (ld.title == 'vp' || ld.title.contains('v.p.') || ld.title.contains('vice president')) {
if (String.isBlank(ld.Title)) {
continue;
}
if (
ld.title.containsIgnoreCase('vp') ||
ld.title.containsIgnoreCase('v.p.') ||
ld.title.containsIgnoreCase('vice president')
) {
ld.Title = 'Vice President';
} else if (
ld.title.contains('mgr') ||
ld.title.contains('manage') ||
ld.title.contains('head of department')
ld.title.containsIgnoreCase('mgr') ||
ld.title.containsIgnoreCase('manage') ||
ld.title.containsIgnoreCase('head of department')
) {
ld.Title = 'Manager';
} else if (ld.title.contains('exec') || ld.title == 'chief' || ld.title.contains('head')) {
} else if (
ld.title.containsIgnoreCase('exec') ||
ld.title.containsIgnoreCase('chief') ||
ld.title.containsIgnoreCase('head')
) {
ld.Title = 'Executive';
} else if (ld.title.contains('assist') || ld.title.contains('deputy') || ld.title == 'jr') {
} else if (
ld.title.containsIgnoreCase('assist') ||
ld.title.containsIgnoreCase('deputy') ||
ld.title.containsIgnoreCase('jr')
Comment on lines +38 to +59

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all of these examples try create a contains the values that you are looking for then do contain on the list.

) {
ld.Title = 'Assistant';
}
}
Expand All @@ -61,21 +76,18 @@ public with sharing class LeadTriggerHandler {
*/
public static void handleAutoLeadScoring(List<Lead> leadsToScore) {
for (Lead ld : leadsToScore) {
Integer score = 10;
Integer score = 0;

// Check and add points based on the specified conditions
if (ld.LeadSource == 'Website' && ld.Email != null) {
score = 3;
if (ld.LeadSource == 'Web' && ld.Email != null) {
score += 3;
}
Comment on lines +82 to 84

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put the condition values into a variable so it's easier to change in the future.


if (ld.Phone != null) {
score = 5;
score += 5;
}

if (ld.Industry == 'Technology') {
score = 10;
score += 10;
}

ld.Lead_Score__c = score; // Set the computed score back to the lead
}
}
Expand Down Expand Up @@ -104,42 +116,58 @@ public with sharing class LeadTriggerHandler {
* - One of the errors is map related. Make sure you are using the correct contact map key
*/
public static void handleLeadAutoConvert(List<Lead> leads) {
// Get convert status
LeadStatus convertStatus = [SELECT Id, MasterLabel FROM LeadStatus WHERE IsConverted = TRUE LIMIT 1];
// Step 1: Gather all lead emails
Map<Id,String> leadToEmailMap = new Map<Id,String>();
for (Lead lead : leads) {
leadToEmailMap.put(lead.Id, lead.Email);
if (!lead.IsConverted && lead.Status != convertStatus.MasterLabel && lead.Email != null) {
leadToEmailMap.put(lead.Id, lead.Email);
}
}

if (leadToEmailMap.size() == 0) {
return;
}

// Step 2: Find matching contacts based on email
Map<String, Contact> emailToContactMap = new Map<String, Contact>();
for (Contact c : [SELECT Id, Email, AccountId FROM Contact WHERE Email IN :leadToEmailMap.values()]) {
if (!emailToContactMap.containsKey(c.Email)) {
emailToContactMap.put(c.Email, c);
} else {
// If we found another contact with the same email, we don't auto-convert.
// So we remove the email from the map.
emailToContactMap.remove(c.Email);
}
List<AggregateResult> ars = [
SELECT Email
FROM Contact
GROUP BY Email
HAVING COUNT(Id) = 1
AND Email IN :leadToEmailMap.values()
];
List<String> singleMatchEmail = new List<String>();
for (AggregateResult ar : ars) {
singleMatchEmail.add((String) ar.get('Email'));
}

// Step 3: Auto-convert leads
List<Database.LeadConvert> leadConverts = new List<Database.LeadConvert>();
LeadStatus convertStatus = [SELECT Id, MasterLabel FROM LeadStatus WHERE IsConverted = TRUE LIMIT 1];
for (Id leadId : leadToEmailMap.keySet()) {
String leadEmail = leadToEmailMap.get(leadId);
if (emailToContactMap.containsKey(leadEmail)) {
Database.LeadConvert lc = new Database.LeadConvert();
lc.setLeadId(leadId);
lc.setContactId(emailToContactMap.get(leadEmail).Id); // Use existing Contact Id
lc.setAccountId(emailToContactMap.get(leadEmail).AccountId); // Use existing Account Id
lc.setDoNotCreateOpportunity(true); // Assuming we don't want to create an opportunity
lc.setConvertedStatus(convertStatus.MasterLabel); // Set the converted status
leadConverts.add(lc);
// If we have matching contacts
if (singleMatchEmail.size() > 0) {
Map<String, Contact> emailToContactMap = new Map<String, Contact>();
for (Contact contact : [SELECT Id, Email, AccountId FROM Contact WHERE Email IN :singleMatchEmail]) {
emailToContactMap.put(contact.Email, contact);
}

// Step 3: Auto-convert leads
List<Database.LeadConvert> leadConverts = new List<Database.LeadConvert>();
for (Id leadId : leadToEmailMap.keySet()) {
if (emailToContactMap.containsKey(leadToEmailMap.get(leadId))) {
Contact cont = emailToContactMap.get(leadToEmailMap.get(leadId));
Database.LeadConvert lc = new Database.LeadConvert();
lc.setLeadId(leadId);
lc.setContactId(cont.Id); // Use existing Contact Id
lc.setAccountId(cont.AccountId); // Use existing Account Id
lc.setDoNotCreateOpportunity(true); // Assuming we don't want to create an opportunity
lc.setConvertedStatus(convertStatus.MasterLabel); // Set the converted status
leadConverts.add(lc);
}
}
}

if (!leadConverts.isEmpty()) {
List<Database.LeadConvertResult> lcrs = Database.convertLead(leadConverts);
if (!leadConverts.isEmpty()) {
List<Database.LeadConvertResult> lcrs = Database.convertLead(leadConverts);
}
}
}
}
218 changes: 218 additions & 0 deletions force-app/main/default/classes/LeadTriggerHandlerTest.cls

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add additional test directly testing method like LeadTriggerHandler.handleTitleNormalization
Passing in the leads you create in memory.

Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/**
* This class contains unit tests for validating the behavior of
* Apex class LeadTriggerHandler
* and trigger LeadTrigger.
*
* Implemented by Oxana Suvorova
*/
@isTest
private class LeadTriggerHandlerTest {

/*
* LeadTriggerHandler.handleTitleNormalization insert test
*/
@isTest
static void testHandleTitleNormalization_insert() {
// Prepare the test data
List<String> titles = new List<String>{
'vp', 'v.p.', 'vice president', 'VPOTUS', 'V.P.', 'vice person', // 5 'Vice President'
'mgr', 'manage', 'head of department', '', 'Manager', // 4 'Manager'
'exec', 'chief', 'head', 'Chief Executive', null, // 4 'Executive'
'assist', 'deputy', 'jr', 'DEPUTY', 'junior'}; // 4 'Assistant'

List<Lead> leadsToInsert = TestDataFactory.createLeadsByTitle(titles, false);

// Perform the test
Test.startTest();
Database.insert(leadsToInsert);
Test.stopTest();

// Retrieve the count of Leads grouped by Title
List<AggregateResult> groupedLeads = [
SELECT
Title,
COUNT(Name) aggCount
FROM Lead
GROUP BY Title
];

Map<String, Integer> countsByTitle = new Map<String, Integer>();
Integer totalRecords = 0;
for (AggregateResult ar : groupedLeads) {
totalRecords += (Integer) ar.get('aggCount');
countsByTitle.put((String) ar.get('Title'), (Integer) ar.get('aggCount'));
}

// Assert that the Title have been correctly changed
Assert.isTrue(countsByTitle.get('Vice President') == 5, 'Expected 5 Leads with Title \'Vice President\'');
Assert.isTrue(countsByTitle.get('Manager') == 4, 'Expected 4 Leads with Title \'Manager\'');
Assert.isTrue(countsByTitle.get('Executive') == 4, 'Expected 4 Leads with Title \'Executive\'');
Assert.isTrue(countsByTitle.get('Assistant') == 4, 'Expected 4 Leads with Title \'Assistant\'');
Assert.areEqual(21, totalRecords, 'Expected 21 Lead records');
}

/*
* LeadTriggerHandler.handleTitleNormalization update test
*/
@isTest
static void testHandleTitleNormalization_update() {
// Prepare the test data
List<String> titles = new List<String>{'asist', 'depty', 'junior', null, ''};
List<Lead> leadsToUpdate = TestDataFactory.createLeadsByTitle(titles, true);

for (Lead lead : leadsToUpdate) {
lead.Title = 'jr';
}
// Perform the test
Test.startTest();
Database.update(leadsToUpdate);
Test.stopTest();

// Retrieve updated Leads
List<Lead> leadsAfterUpdate = [
SELECT
Title
FROM Lead
WHERE Id IN :leadsToUpdate
];

// Assert that the Title have been correctly updated
Assert.isTrue(leadsAfterUpdate.size() == 5, 'Expected 5 Lead records');
for (Lead lead : leadsAfterUpdate) {
Assert.areEqual('Assistant', lead.Title, 'Expected Title \'Assistant\'');
}
}

/*
* LeadTriggerHandler.handleTitleNormalization update test
*/
@isTest
static void testHandleAutoLeadScoring() {
// Prepare the test data
List<Map<String, Object>> params = new List<Map<String, Object>>();
params.add(new Map<String, Object>{'Lead_Score__c' => 20, 'LeadSource' => null}); // 0
params.add(new Map<String, Object>{'LeadSource' => 'Other', 'Email' => 'test@mail.com'}); // 0
params.add(new Map<String, Object>{'LeadSource' => 'Web', 'Email' => 'test@mail.com'}); // 3
params.add(new Map<String, Object>{'Phone' => '(908)345-1234', 'Industry' => 'Government'}); // 5
params.add(new Map<String, Object>{'LeadSource' => 'Web', 'Email' => 'test2@mail.com', 'Phone' => '(908)345-2345'}); // 8
params.add(new Map<String, Object>{'Lead_Score__c' => 10, 'Email' => 'test3@mail.com', 'Industry' => 'Technology'}); // 10
params.add(new Map<String, Object>{'LeadSource' => 'Web', 'Email' => 'test4@mail.com', 'Industry' => 'Technology'}); // 13
params.add(new Map<String, Object>{'LeadSource' => 'Web', 'Phone' => '(908)346-1234', 'Industry' => 'Technology'}); // 15
params.add(new Map<String, Object>{'LeadSource' => 'Web', 'Email' => 'test5@mail.com',
'Phone' => '(908)346-1234', 'Industry' => 'Technology'}); // 18

List<Lead> leadsToScore = TestDataFactory.createLeadsByParams(params, false);

// Perform the test
Test.startTest();
LeadTriggerHandler.handleAutoLeadScoring(leadsToScore);
Test.stopTest();

// Assert that the Score calculates correctly
List<Integer> scoreVariants = new List<Integer>{0, 0, 3, 5, 8, 10, 13, 15, 18};
for (Integer i = 0; i < leadsToScore.size(); i++) {
Assert.isTrue(leadsToScore[i].Lead_Score__c <= 18, 'Lead score shouldn\'t be more than 18');
Assert.areEqual(scoreVariants[i], leadsToScore[i].Lead_Score__c, 'Score has not correctly calculated');
}

}

@isTest
static void testHandleLeadAutoConvert_insertPositive() {
// Prepare the test data
TestDataFactory.generateAccountWithContacts(50, 7); // 7 contacts with a unique email
Map<String, Object> params = new Map<String, Object>{'LeadSource' => 'Web', 'Email' => 'test'};
List<Lead> leads = TestDataFactory.createLeadsWithParams(50, params, false);

// Perform the test
Test.startTest();
Database.insert(leads);
Test.stopTest();

// Request resulted Leads
List<Lead> insertedLeads = [
SELECT Id, Email, IsConverted
FROM Lead
];
// Assertions
Assert.isTrue(insertedLeads.size() == 50, 'Expected 50 Leads inserted');
Integer converted = 0;
for (Integer i = 0; i < 50; i++) {
if (i > 0 && Math.mod(i, 7) == 0) {
Assert.isTrue(insertedLeads[i].IsConverted, 'Lead with email ' + insertedLeads[i].Email + ' expected to be converted');
converted++;
} else {
Assert.isFalse(insertedLeads[i].IsConverted, 'Lead with email ' + insertedLeads[i].Email + ' shouldn\t be converted');
}
}
Assert.isTrue(converted == 7, 'Expected 7 Leads converted');
}

@isTest
static void testHandleLeadAutoConvert_insertNegative() {
// Prepare the test data
TestDataFactory.generateAccountWithContacts(5, 2); // 2 contacts with a unique email
Map<String, Object> params = new Map<String, Object>{'LeadSource' => 'Web',
'Status' => 'Closed - Converted',
'Email' => 'test'};
List<Lead> leads = TestDataFactory.createLeadsWithParams(5, params, false);

// Perform the test
Test.startTest();
Database.insert(leads);
Test.stopTest();

// Request resulted Leads
List<Lead> insertedLeads = [
SELECT
Id,
Email,
IsConverted
FROM Lead
];
// Assertions
Assert.isTrue(insertedLeads.size() == 5, 'Expected 5 Leads inserted');
for (Lead lead : insertedLeads) {
Assert.isFalse(lead.IsConverted, 'Lead should\'t be converted');
}
}

@isTest
static void testHandleLeadAutoConvert_update() {
// Prepare the test data
TestDataFactory.generateAccountWithContacts(5, 2); // 2 contacts with a unique email
Map<String, Object> params = new Map<String, Object>{'LeadSource' => 'Web'};
List<Lead> leads = TestDataFactory.createLeadsWithParams(2, params, true);

// Change email
List<Lead> leadsToUpdate = [
SELECT
Id,
Email
FROM Lead
WHERE Id IN :leads
AND IsConverted = false
];
Assert.areEqual(2, leadsToUpdate.size(), 'Expected to get 2 unconverted Leads');
leadsToUpdate[0].Email = 'test0@mail.com'; // not going to be converted
leadsToUpdate[1].Email = 'test1@mail.com'; // going to be converted

// Perform the test
Test.startTest();
Database.update(leadsToUpdate);
Test.stopTest();

// Request resulted Leads
List<Lead> updatedLeads = [
SELECT
Id,
Email,
IsConverted
FROM Lead
WHERE Id IN :leadsToUpdate
];
// Assertions
Assert.isFalse(updatedLeads[0].IsConverted, 'Lead with email ' + updatedLeads[0].Email + ' shouldn\'t be converted');
Assert.isTrue(updatedLeads[1].IsConverted, 'Lead with email ' + updatedLeads[1].Email + ' expected to be converted');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading