Advanced 30 min read

Testing Triggers: Bulk and Edge Cases

By the end of this lesson, you'll be able to:

  • Write a bulk test inserting 200 records to verify a trigger stays governor-limit-safe
  • Write edge-case tests for a trigger's boundary conditions
  • Connect this back to Module 27's manual "test with multiple records" lesson

Prerequisites: "Testing Exceptions"

A bulk test, automated

@isTest
private class OpportunityTriggerHandlerTest {
    @isTest
    static void bulkClosedWonUpdateStaysWithinLimits() {
        Account acc = new Account(Name = 'Test Account');
        insert acc;

        List<Opportunity> opportunities = new List<Opportunity>();
        for (Integer i = 0; i < 200; i++) {
            opportunities.add(new Opportunity(
                Name = 'Deal ' + i, AccountId = acc.Id, StageName = 'Prospecting',
                CloseDate = Date.today(), Amount = 1000
            ));
        }
        insert opportunities;

        Test.startTest();
        for (Opportunity opp : opportunities) {
            opp.StageName = 'Closed Won';
        }
        update opportunities;
        Test.stopTest();

        Account updated = [SELECT Total_Won_Revenue__c FROM Account WHERE Id = :acc.Id];
        System.assertEquals(200000, updated.Total_Won_Revenue__c);
    }
}
  1. @isTest — marks the class.
  2. private class OpportunityTriggerHandlerTest { — the class.
  3. @isTest — marks the test method.
  4. static void bulkClosedWonUpdateStaysWithinLimits() { — this is Module 26's AccountRollupService feature, finally given the automated bulk test Module 26 itself only verified manually.
  5. Account acc = new Account(Name = 'Test Account'); — the account that will receive the rollup.
  6. insert acc; — saves it.
  7. (blank line)
  8. List<Opportunity> opportunities = new List<Opportunity>(); — 200 opportunities to build.
  9. for (Integer i = 0; i < 200; i++) { — loops 200 times.
  10. opportunities.add(new Opportunity( — builds one opportunity.
  11. Name = 'Deal ' + i, AccountId = acc.Id, StageName = 'Prospecting', — linked to the account, starting unwon.
  12. CloseDate = Date.today(), Amount = 1000 — a fixed amount each.
  13. )); — closes the constructor call and add().
  14. } — closes the loop.
  15. insert opportunities; — saves all 200 at once.
  16. (blank line)
  17. Test.startTest(); — marks the boundary before the action being measured.
  18. for (Opportunity opp : opportunities) { — loops over all 200.
  19. opp.StageName = 'Closed Won'; — the transition being tested.
  20. } — closes the loop.
  21. update opportunities; — one bulk update, firing the trigger with all 200 records at once.
  22. Test.stopTest(); — marks the end.
  23. (blank line)
  24. Account updated = [SELECT Total_Won_Revenue__c FROM Account WHERE Id = :acc.Id]; — re-queries the account.
  25. System.assertEquals(200000, updated.Total_Won_Revenue__c); — confirms the rollup handled every single one correctly (200 × 1000 = 200000).
  26. } — closes the test method.
  27. } — closes the class.

The new-vs-new edge case, automated

@isTest
static void overlappingRequestsInSameBatchAreBlocked() {
    List<Leave_Request__c> batch = new List<Leave_Request__c>{
        new Leave_Request__c(Employee_Name__c = 'Ben', Start_Date__c = Date.newInstance(2026, 5, 1), End_Date__c = Date.newInstance(2026, 5, 5), Status__c = 'Pending'),
        new Leave_Request__c(Employee_Name__c = 'Ben', Start_Date__c = Date.newInstance(2026, 5, 3), End_Date__c = Date.newInstance(2026, 5, 8), Status__c = 'Pending')
    };

    try {
        insert batch;
        System.assert(false, 'Expected a DmlException from the overlap validation');
    } catch (DmlException e) {
        System.assert(e.getMessage().contains('overlaps with another request'));
    }
}
  1. @isTest — marks the test method.
  2. static void overlappingRequestsInSameBatchAreBlocked() { — this is Module 27's exact new-vs-new manual verification, now automated and permanent.
  3. List<Leave_Request__c> batch = new List<Leave_Request__c>{ — two requests submitted together.
  4. new Leave_Request__c(Employee_Name__c = 'Ben', Start_Date__c = Date.newInstance(2026, 5, 1), End_Date__c = Date.newInstance(2026, 5, 5), Status__c = 'Pending'), — the first.
  5. new Leave_Request__c(Employee_Name__c = 'Ben', Start_Date__c = Date.newInstance(2026, 5, 3), End_Date__c = Date.newInstance(2026, 5, 8), Status__c = 'Pending') — the second, overlapping the first.
  6. }; — closes the list literal.
  7. (blank line)
  8. try { — wraps the insert expected to fail.
  9. insert batch; — this specific edge case only exists when multiple records are inserted together.
  10. System.assert(false, 'Expected a DmlException from the overlap validation'); — reached only if the insert unexpectedly succeeded.
  11. } catch (DmlException e) { — catches the expected failure.
  12. System.assert(e.getMessage().contains('overlaps with another request')); — verifies it's the right error.
  13. } — closes the try/catch.
  14. } — closes the test method, giving this edge case its own dedicated, always-running test.

Why triggers specifically need this category of test

A trigger's most dangerous bugs — SOQL/DML-in-a-loop (Module 25), recursion (Module 24), a batch-only edge case (Module 27) — are exactly the ones a single-record manual check can never surface. Every trigger this course has built deserves at least one bulk test (200 records) and one test per genuinely batch-specific edge case, as a matter of course, not an afterthought.

Exercise

Write a bulk test inserting 200 Leave_Request__c records for 200 different employees (no overlaps) and asserting they all insert successfully.

Show hint

Vary Employee_Name__c per record so none of them actually overlap with each other.

APEX

Testing Triggers: Bulk and Edge Cases Quiz

1. Why does a trigger deserve a bulk test with 200 records, not just a single-record test?

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 formalizes Module 27's manual multi-record verification into automated bulk and edge-case tests — the single most important category of test for any trigger.