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';
        }
    }
}
  1. public class LoanApplicationTriggerHandler { — a genuinely realistic trigger handler.
  2. private static final Integer LOAN_TERM_MONTHS = 60; — a shared constant.
  3. (blank line)
  4. public void beforeInsert(List<Loan_Application__c> newApplications) { — the entry point.
  5. LoanValidationService validator = new LoanValidationService(); — validation (Lesson 3).
  6. LoanCalculationService calculator = new LoanCalculationService(); — calculation (Lesson 2).
  7. (blank line)
  8. for (Loan_Application__c app : newApplications) { — loops over the new applications, entirely in memory.
  9. validator.validate(app.Requested_Amount__c, app.Annual_Income__c, app.Credit_Score__c); — validates first — throws if invalid.
  10. (blank line)
  11. Decimal rate = calculator.calculateInterestRate(app.Credit_Score__c); — computes the rate.
  12. app.Interest_Rate__c = rate; — stores it.
  13. app.Monthly_Payment__c = calculator.calculateMonthlyPayment(app.Requested_Amount__c, rate, LOAN_TERM_MONTHS); — computes and stores the payment.
  14. 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).
  15. } — closes the loop — no SOQL or DML inside it at all.
  16. } — closes the method.
  17. (blank line)
  18. private String determineStatus(Decimal creditScore, Decimal amount, Decimal income) { — the policy, as code.
  19. if (creditScore >= 700 && amount <= income * 0.4) { — the pre-approval rule.
  20. return 'Pre-Approved'; — its outcome.
  21. } else if (creditScore >= 600) { — the manual-review rule.
  22. return 'Manual Review'; — its outcome.
  23. } else { — everything else.
  24. return 'Declined'; — the decline outcome.
  25. } — closes the if/else if/else.
  26. } — closes the method.
  27. } — 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);
        }
    }
}
  1. @isTest — marks the class.
  2. private class LoanApplicationTriggerHandlerTest { — this is Module 31's "Testing Triggers: Bulk and Edge Cases" lesson, applied the moment the trigger exists.
  3. @isTest — marks the test method.
  4. static void bulkApplicationsAllProcessCorrectly() { — the test.
  5. List<Loan_Application__c> applications = new List<Loan_Application__c>(); — 200 applications to build.
  6. for (Integer i = 0; i < 200; i++) { — loops 200 times.
  7. applications.add(new Loan_Application__c( — builds one application.
  8. Requested_Amount__c = 10000, Annual_Income__c = 50000, Credit_Score__c = 720 — values that should all qualify for pre-approval.
  9. )); — closes the constructor call and add().
  10. } — closes the loop.
  11. (blank line)
  12. Test.startTest(); — marks the boundary.
  13. insert applications; — one insert, 200 records.
  14. Test.stopTest(); — marks the end.
  15. (blank line)
  16. List<Loan_Application__c> saved = [SELECT Status__c, Interest_Rate__c FROM Loan_Application__c]; — re-queries all 200.
  17. System.assertEquals(200, saved.size()); — confirms all were saved.
  18. for (Loan_Application__c app : saved) { — loops over the results.
  19. System.assertEquals('Pre-Approved', app.Status__c); — confirms every single one processed correctly.
  20. System.assertEquals(6.5, app.Interest_Rate__c); — and got the right rate.
  21. } — closes the loop.
  22. } — closes the test method.
  23. } — 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

1. How many DML statements does the trigger handler run for a batch of 200 applications?

Log in to submit the quiz and save your score.

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.