Advanced
30 min read
Build a Bulk-Safe Trigger
By the end of this lesson, you'll be able to:
- Write a before insert trigger applying validation, calculation, and status assignment together
- Keep the trigger bulk-safe, following Module 25's discipline
- Write a bulk test confirming 200 applications process correctly in one transaction
Prerequisites: "Enforce Validation Rules"
The trigger handler, combining everything so far
public class LoanApplicationTriggerHandler {
private static final Integer LOAN_TERM_MONTHS = 60;
public void beforeInsert(List<Loan_Application__c> newApplications) {
LoanValidationService validator = new LoanValidationService();
LoanCalculationService calculator = new LoanCalculationService();
for (Loan_Application__c app : newApplications) {
validator.validate(app.Requested_Amount__c, app.Annual_Income__c, app.Credit_Score__c);
Decimal rate = calculator.calculateInterestRate(app.Credit_Score__c);
app.Interest_Rate__c = rate;
app.Monthly_Payment__c = calculator.calculateMonthlyPayment(app.Requested_Amount__c, rate, LOAN_TERM_MONTHS);
app.Status__c = determineStatus(app.Credit_Score__c, app.Requested_Amount__c, app.Annual_Income__c);
}
}
private String determineStatus(Decimal creditScore, Decimal amount, Decimal income) {
if (creditScore >= 700 && amount <= income * 0.4) {
return 'Pre-Approved';
} else if (creditScore >= 600) {
return 'Manual Review';
} else {
return 'Declined';
}
}
}
public class LoanApplicationTriggerHandler {— a genuinely realistic trigger handler.private static final Integer LOAN_TERM_MONTHS = 60;— a shared constant.- (blank line)
public void beforeInsert(List<Loan_Application__c> newApplications) {— the entry point.LoanValidationService validator = new LoanValidationService();— validation (Lesson 3).LoanCalculationService calculator = new LoanCalculationService();— calculation (Lesson 2).- (blank line)
for (Loan_Application__c app : newApplications) {— loops over the new applications, entirely in memory.validator.validate(app.Requested_Amount__c, app.Annual_Income__c, app.Credit_Score__c);— validates first — throws if invalid.- (blank line)
Decimal rate = calculator.calculateInterestRate(app.Credit_Score__c);— computes the rate.app.Interest_Rate__c = rate;— stores it.app.Monthly_Payment__c = calculator.calculateMonthlyPayment(app.Requested_Amount__c, rate, LOAN_TERM_MONTHS);— computes and stores the payment.app.Status__c = determineStatus(app.Credit_Score__c, app.Requested_Amount__c, app.Annual_Income__c);— the approval decision (Lesson 1's policy, finally implemented).}— closes the loop — no SOQL or DML inside it at all.}— closes the method.- (blank line)
private String determineStatus(Decimal creditScore, Decimal amount, Decimal income) {— the policy, as code.if (creditScore >= 700 && amount <= income * 0.4) {— the pre-approval rule.return 'Pre-Approved';— its outcome.} else if (creditScore >= 600) {— the manual-review rule.return 'Manual Review';— its outcome.} else {— everything else.return 'Declined';— the decline outcome.}— closes theif/else if/else.}— closes the method.}— closes the class.
The bulk test, written immediately
@isTest
private class LoanApplicationTriggerHandlerTest {
@isTest
static void bulkApplicationsAllProcessCorrectly() {
List<Loan_Application__c> applications = new List<Loan_Application__c>();
for (Integer i = 0; i < 200; i++) {
applications.add(new Loan_Application__c(
Requested_Amount__c = 10000, Annual_Income__c = 50000, Credit_Score__c = 720
));
}
Test.startTest();
insert applications;
Test.stopTest();
List<Loan_Application__c> saved = [SELECT Status__c, Interest_Rate__c FROM Loan_Application__c];
System.assertEquals(200, saved.size());
for (Loan_Application__c app : saved) {
System.assertEquals('Pre-Approved', app.Status__c);
System.assertEquals(6.5, app.Interest_Rate__c);
}
}
}
@isTest— marks the class.private class LoanApplicationTriggerHandlerTest {— this is Module 31's "Testing Triggers: Bulk and Edge Cases" lesson, applied the moment the trigger exists.@isTest— marks the test method.static void bulkApplicationsAllProcessCorrectly() {— the test.List<Loan_Application__c> applications = new List<Loan_Application__c>();— 200 applications to build.for (Integer i = 0; i < 200; i++) {— loops 200 times.applications.add(new Loan_Application__c(— builds one application.Requested_Amount__c = 10000, Annual_Income__c = 50000, Credit_Score__c = 720— values that should all qualify for pre-approval.));— closes the constructor call andadd().}— closes the loop.- (blank line)
Test.startTest();— marks the boundary.insert applications;— oneinsert, 200 records.Test.stopTest();— marks the end.- (blank line)
List<Loan_Application__c> saved = [SELECT Status__c, Interest_Rate__c FROM Loan_Application__c];— re-queries all 200.System.assertEquals(200, saved.size());— confirms all were saved.for (Loan_Application__c app : saved) {— loops over the results.System.assertEquals('Pre-Approved', app.Status__c);— confirms every single one processed correctly.System.assertEquals(6.5, app.Interest_Rate__c);— and got the right rate.}— closes the loop.}— closes the test method.}— closes the class.
Exercise
Write a test inserting 3 applications with different credit scores (750, 650, 550) and asserting each gets the correct Status__c.
Show hint
Insert all 3 in one List, then query and check each one's Status__c individually.
APEX
Build a Bulk-Safe Trigger Quiz
My Notes
Log in to keep private notes on this lesson.
Questions about this lesson
No questions yet — be the first to ask.
Log in to ask a question about this lesson.
Summary
This lesson wires the calculation and validation services into a real trigger, and — immediately — writes the bulk test that confirms it stays governor-limit-safe.