Salesforce Trigger Scenario Based Questions
Salesforce Trigger Scenario Based Questions
Notification
Problem Statement:
When an Opportunity with Amount > $500,000 is inserted, create a Forecast__c record with the Opportunity's
expected revenue and quarter. Additionally, notify the Account Owner via Chatter using a Queueable. Ensure
recursive protection is in place.
Trigger:
trigger OpportunityTrigger on Opportunity (after insert) {
switch on [Link] {
when AFTER_INSERT {
if (![Link]()) {
[Link]();
[Link]([Link]);
}
}
}
}
Trigger Handler:
public class OpportunityTriggerHandler {
public static void handleAfterInsert(List<Opportunity> opportunities) {
List<Forecast__c> forecasts = new List<Forecast__c>();
Set<Id> accountIds = new Set<Id>();
if (![Link]()) {
[Link](new NotifyAccountOwnersQueueable(accountIds));
}
}
}
Test Class:
@isTest
private class OpportunityTriggerHandlerTest {
@isTest static void testHighValueForecastAndNotify() {
Account acc = new Account(Name='Test Account');
insert acc;
[Link]();
[Link](new NotifyAccountOwnersQueueable(new Set<Id>{[Link]}));
[Link]();
Problem Statement:
When an Order is inserted with a Status of 'Activated', automatically create a Delivery__c record. Link the
Delivery__c to the Order via a lookup and set a default Estimated_Delivery_Date__c. Ensure the logic is
bulk-safe.
Trigger:
trigger OrderTrigger on Order (after insert) {
switch on [Link] {
when AFTER_INSERT {
[Link]([Link]);
}
}
}
Trigger Handler:
public class OrderTriggerHandler {
public static void handleAfterInsert(List<Order> orders) {
List<Delivery__c> deliveries = new List<Delivery__c>();
if (![Link]()) {
insert deliveries;
}
}
}
Test Class:
@isTest
private class OrderTriggerHandlerTest {
@isTest static void testDeliveryCreationOnOrderInsert() {
Account acc = new Account(Name='Test Account');
insert acc;
List<Delivery__c> deliveries = [SELECT Id, Order__c FROM Delivery__c WHERE Order__c = :[Link]];
[Link](1, [Link]());
}
}
Trigger Scenario: Task Trigger: Escalate Unassigned High-Priority
Tasks
Problem Statement:
When a Task is inserted with Priority 'High' and no OwnerId, assign it to a default queue and log escalation in
Task_Log__c. Ensure the logic is bulk-safe and done in before insert.
Trigger:
trigger TaskTrigger on Task (before insert) {
switch on [Link] {
when BEFORE_INSERT {
[Link]([Link]);
}
}
}
Trigger Handler:
public class TaskTriggerHandler {
public static void handleBeforeInsert(List<Task> tasks) {
Id defaultQueueId = [SELECT Id FROM Group WHERE Name = 'Default Escalation Queue' AND Type = 'Que
List<Task_Log__c> logs = new List<Task_Log__c>();
if (![Link]()) {
insert logs;
}
}
}
Test Class:
@isTest
private class TaskTriggerHandlerTest {
@isTest static void testEscalationForUnassignedTask() {
Task t = new Task(Subject='Critical Follow-up', Priority='High');
insert t;
Problem Statement:
When an Asset is updated and linked to a Case via Related_Case__c, update the Case's
Asset_Summary__c field with the Asset Name + SerialNumber. Ensure bulk-safe and efficient lookup and
update.
Trigger:
trigger AssetTrigger on Asset (after update) {
switch on [Link] {
when AFTER_UPDATE {
[Link]([Link]);
}
}
}
Trigger Handler:
public class AssetTriggerHandler {
public static void handleAfterUpdate(List<Asset> assets) {
Map<Id, String> caseUpdates = new Map<Id, String>();
if (![Link]()) {
update casesToUpdate;
}
}
}
Test Class:
@isTest
private class AssetTriggerHandlerTest {
@isTest static void testCaseUpdateFromAsset() {
Case c = new Case(Subject='Test Case');
insert c;
[Link] = '67890';
update a;
Problem Statement:
When an Event is rescheduled (StartDateTime is changed), log the original and new date/time in a custom
object Calendar_Audit__c. This should only happen if the date is actually changed.
Trigger:
trigger EventTrigger on Event (after update) {
switch on [Link] {
when AFTER_UPDATE {
[Link]([Link], [Link]);
}
}
}
Trigger Handler:
public class EventTriggerHandler {
public static void handleAfterUpdate(List<Event> newList, Map<Id, Event> oldMap) {
List<Calendar_Audit__c> audits = new List<Calendar_Audit__c>();
Test Class:
@isTest
private class EventTriggerHandlerTest {
@isTest static void testEventRescheduleAudit() {
Event e = new Event(Subject='Team Meeting', StartDateTime=[Link]().addDays(1), EndDateTime=Sy
insert e;
[Link] = [Link]().addDays(2);
update e;
Problem Statement:
When an Invoice__c is inserted, automatically create a related Payment__c record with status 'Pending'.
Also, in before insert, throw an error if Amount__c is less than or equal to zero.
Trigger:
trigger InvoiceTrigger on Invoice__c (before insert, after insert) {
switch on [Link] {
when BEFORE_INSERT {
[Link]([Link]);
}
when AFTER_INSERT {
[Link]([Link]);
}
}
}
Trigger Handler:
public class InvoiceTriggerHandler {
public static void handleBeforeInsert(List<Invoice__c> invoices) {
for (Invoice__c inv : invoices) {
if (inv.Amount__c <= 0) {
[Link]('Invoice amount must be greater than zero.');
}
}
}
Test Class:
@isTest
private class InvoiceTriggerHandlerTest {
@isTest static void testValidInvoiceCreatesPayment() {
Invoice__c inv = new Invoice__c(Name='INV-001', Amount__c=500, Due_Date__c=[Link]().addDays
insert inv;
Problem Statement:
When a User's Role is changed, log the previous and new Role into a custom object
User_Change_History__c with a timestamp and who made the change. Bulk-safe and after update logic
required.
Trigger:
trigger UserTrigger on User (after update) {
switch on [Link] {
when AFTER_UPDATE {
[Link]([Link], [Link]);
}
}
}
Trigger Handler:
public class UserTriggerHandler {
public static void handleAfterUpdate(List<User> newList, Map<Id, User> oldMap) {
List<User_Change_History__c> changes = new List<User_Change_History__c>();
Test Class:
@isTest
private class UserTriggerHandlerTest {
@isTest static void testUserRoleChangeLogged() {
Profile p = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1];
UserRole role1 = new UserRole(Name='Role A');
UserRole role2 = new UserRole(Name='Role B');
insert new List<UserRole>{role1, role2};
[Link] = [Link];
update u;
Problem Statement:
When a Case is updated and its status changes to 'Escalated', the system should: 1. Create a high-priority
Task for follow-up 2. Insert a record into the Case_Audit__c custom object This logic must be bulk-safe and
include recursive protection.
Trigger:
trigger CaseTrigger on Case (after update) {
switch on [Link] {
when AFTER_UPDATE {
if (![Link]()) {
[Link]();
[Link]([Link], [Link]);
}
}
}
}
Trigger Handler:
public class CaseTriggerHandler {
public static void handleAfterUpdate(List<Case> newList, Map<Id, Case> oldMap) {
List<Task> tasksToInsert = new List<Task>();
List<Case_Audit__c> auditsToInsert = new List<Case_Audit__c>();
Test Class:
@isTest
private class CaseTriggerHandlerTest {
@isTest static void testEscalationLogic() {
Case c = new Case(Subject='Test Case', Status='New');
insert c;
[Link] = 'Escalated';
update c;
Problem Statement:
When an Account's Type is changed to 'Customer', count the number of related active Contacts and update a
custom field Total_Contacts__c on the Account. The logic must be bulk-safe and avoid unnecessary DML.
Trigger:
trigger AccountTrigger on Account (after update) {
switch on [Link] {
when AFTER_UPDATE {
[Link]([Link], [Link]);
}
}
}
Trigger Handler:
public class AccountTriggerHandler {
public static void handleAfterUpdate(List<Account> newList, Map<Id, Account> oldMap) {
Set<Id> changedAccounts = new Set<Id>();
if (![Link]()) {
Map<Id, Integer> contactCounts = new Map<Id, Integer>();
for (AggregateResult ar : [
SELECT AccountId, COUNT(Id) contactCount
FROM Contact
WHERE AccountId IN :changedAccounts AND IsDeleted = false
GROUP BY AccountId
]) {
[Link]((Id)[Link]('AccountId'), (Integer)[Link]('contactCount'));
}
Test Class:
@isTest
private class AccountTriggerHandlerTest {
@isTest static void testContactAggregation() {
Account acc = new Account(Name='Test Account', Type='Prospect');
insert acc;
[Link] = 'Customer';
update acc;
Account updated = [SELECT Total_Contacts__c FROM Account WHERE Id = :[Link]];
[Link](2, updated.Total_Contacts__c);
}
}
Trigger Scenario: Lead Trigger: Sync Converted Leads and Async
External Push
Problem Statement:
When a Lead is converted, a record should be created in a custom object Lead_Sync__c. Also, enqueue a
Queueable class to simulate an async sync with an external system. Ensure the trigger is bulk-safe and
ignores already synced records.
Trigger:
trigger LeadTrigger on Lead (after update) {
switch on [Link] {
when AFTER_UPDATE {
[Link]([Link], [Link]);
}
}
}
Trigger Handler:
public class LeadTriggerHandler {
public static void handleAfterUpdate(List<Lead> newList, Map<Id, Lead> oldMap) {
List<Lead_Sync__c> syncs = new List<Lead_Sync__c>();
Set<Id> toSync = new Set<Id>();
if (![Link]()) {
[Link](new PushLeadToExternalSystemQueueable(toSync));
}
}
}
Test Class:
@isTest
private class LeadTriggerHandlerTest {
@isTest static void testLeadSyncAndQueueable() {
Lead l = new Lead(LastName='Test Lead', Company='Test Co');
insert l;
[Link] = true;
[Link] = [Link](); // Dummy value for test
[Link] = [Link](); // Dummy value for test
update l;
[Link]();
[Link](new PushLeadToExternalSystemQueueable(new Set<Id>{[Link]}));
[Link]();
}
}
Trigger Scenario: Contract Trigger: Auto-Creation of Renewal
Opportunity
Problem Statement:
When a Contract is nearing its end date (within 30 days) and Renewal_Opportunity__c is not already set,
automatically create a Renewal Opportunity and link it to the Contract. Ensure this is done only once using a
status flag.
Trigger:
trigger ContractTrigger on Contract (after insert, after update) {
switch on [Link] {
when AFTER_INSERT, AFTER_UPDATE {
[Link]([Link]);
}
}
}
Trigger Handler:
public class ContractTriggerHandler {
public static void handleAfter(List<Contract> contracts) {
List<Opportunity> renewalOpps = new List<Opportunity>();
Map<Id, Contract> toUpdate = new Map<Id, Contract>();
if (![Link]()) {
insert renewalOpps;
Test Class:
@isTest
private class ContractTriggerHandlerTest {
@isTest static void testRenewalOpportunityCreation() {
Account acc = new Account(Name='Test Account');
insert acc;
Contract con = new Contract(
AccountId = [Link],
Status = 'Activated',
StartDate = [Link]().addDays(-30),
EndDate = [Link]().addDays(10)
);
insert con;
Problem Statement:
When a Quote is updated and the Status is changed to 'Approved', ensure it has at least one QuoteLineItem.
If not, prevent the update by adding an error. The logic must be handled in before update context.
Trigger:
trigger QuoteTrigger on Quote (before update) {
switch on [Link] {
when BEFORE_UPDATE {
[Link]([Link]);
}
}
}
Trigger Handler:
public class QuoteTriggerHandler {
public static void handleBeforeUpdate(List<Quote> quotes) {
Set<Id> quoteIdsToCheck = new Set<Id>();
if (![Link]()) {
Map<Id, Integer> lineItemCounts = new Map<Id, Integer>();
for (AggregateResult ar : [
SELECT QuoteId, COUNT(Id) cnt FROM QuoteLineItem
WHERE QuoteId IN :quoteIdsToCheck
GROUP BY QuoteId
]) {
[Link]((Id) [Link]('QuoteId'), (Integer) [Link]('cnt'));
}
Test Class:
@isTest
private class QuoteTriggerHandlerTest {
@isTest static void testApprovalBlockedWithoutLineItems() {
Quote q = new Quote(Name='Test Quote', Status='Draft');
insert q;
[Link] = 'Approved';
[Link]();
try {
update q;
[Link](false, 'Expected error due to no line items');
} catch (DmlException e) {
[Link]([Link]().contains('Cannot approve a Quote without at least one line item.
}
[Link]();
}
}
Trigger Scenario: Campaign Trigger: Budget Aggregation from
Campaign Members
Problem Statement:
When a Campaign is updated, aggregate the total Estimated_Cost__c from all related
CampaignMembers__c and update the Total_Estimated_Cost__c field on the Campaign. Use after update
and bulk-safe cross-object aggregation logic.
Trigger:
trigger CampaignTrigger on Campaign (after update) {
switch on [Link] {
when AFTER_UPDATE {
[Link]([Link]);
}
}
}
Trigger Handler:
public class CampaignTriggerHandler {
public static void handleAfterUpdate(List<Campaign> campaigns) {
Set<Id> campaignIds = new Set<Id>();
for (Campaign c : campaigns) {
[Link]([Link]);
}
Test Class:
@isTest
private class CampaignTriggerHandlerTest {
@isTest static void testCostAggregation() {
Campaign camp = new Campaign(Name='Test Campaign');
insert camp;
update camp;
Problem Statement:
When a Custom_Object__c record is inserted, auto-link it to an Account whose Region__c matches. The field
Linked_Account__c should be updated accordingly. Ensure this is done in before insert and is bulk-safe.
Trigger:
trigger CustomObjectTrigger on Custom_Object__c (before insert) {
switch on [Link] {
when BEFORE_INSERT {
[Link]([Link]);
}
}
}
Trigger Handler:
public class CustomObjectTriggerHandler {
public static void handleBeforeInsert(List<Custom_Object__c> records) {
Set<String> regions = new Set<String>();
for (Custom_Object__c rec : records) {
if (rec.Region__c != null) {
[Link](rec.Region__c);
}
}
Test Class:
@isTest
private class CustomObjectTriggerHandlerTest {
@isTest static void testAccountLinkingByRegion() {
Account acc = new Account(Name='East Account', Region__c='East');
insert acc;
Problem Statement:
When a Product2 record is updated and its IsDiscontinued field is set to true, post a Chatter message on the
Product2 record to notify internal users. This should only happen once.
Trigger:
trigger Product2Trigger on Product2 (after update) {
switch on [Link] {
when AFTER_UPDATE {
[Link]([Link], [Link]);
}
}
}
Trigger Handler:
public class Product2TriggerHandler {
public static void handleAfterUpdate(List<Product2> newList, Map<Id, Product2> oldMap) {
List<FeedItem> chatterPosts = new List<FeedItem>();
if (![Link]()) {
insert chatterPosts;
}
}
}
Test Class:
@isTest
private class Product2TriggerHandlerTest {
@isTest static void testChatterNotificationOnDiscontinue() {
Product2 prod = new Product2(Name='Test Product', IsActive=true);
insert prod;
[Link] = true;
update prod;
List<FeedItem> posts = [SELECT Id, ParentId FROM FeedItem WHERE ParentId = :[Link]];
[Link](1, [Link]());
}
}