Advanced 40 min read

Write a Full Test Suite

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

  • Write bulk-safe test coverage for both triggers and the batch job
  • Test the overdraft-rejection and frozen-account scenarios explicitly
  • Apply Module 31's testing principles to a system with real financial stakes

Prerequisites: "Secure Account Data"

Testing the happy path in bulk

@isTest
private class TransactionTriggerTest {
    @isTest
    static void updatesBalanceForBulkDeposits() {
        Bank_Account__c acc = new Bank_Account__c(Name = 'Test', Balance__c = 0, Status__c = 'Active');
        insert acc;

        List<Transaction__c> deposits = new List<Transaction__c>();
        for (Integer i = 0; i < 200; i++) {
            deposits.add(new Transaction__c(Bank_Account__c = acc.Id, Type__c = 'Deposit', Amount__c = 10));
        }

        Test.startTest();
        insert deposits;
        Test.stopTest();

        Assert.areEqual(2000, [SELECT Balance__c FROM Bank_Account__c WHERE Id = :acc.Id].Balance__c);
    }
}

200 deposits of 10 each should net to exactly 2000 — this is Module 31's "always test at bulk scale" rule, catching any hidden per-record assumption Lesson 2's trigger might have.

Testing the rejection paths explicitly

@isTest
static void rejectsOverdraftWithdrawal() {
    Bank_Account__c acc = new Bank_Account__c(Name = 'Test', Balance__c = 100, Status__c = 'Active');
    insert acc;

    Transaction__c overdraft = new Transaction__c(Bank_Account__c = acc.Id, Type__c = 'Withdrawal', Amount__c = 500);

    Test.startTest();
    try {
        insert overdraft;
        Assert.fail('Expected an overdraft DmlException');
    } catch (DmlException e) {
        Assert.isTrue(e.getMessage().contains('Insufficient funds'));
    }
    Test.stopTest();
}

This is Module 31's negative-path testing principle — confirming the rejection itself works correctly is just as important as confirming a valid deposit updates the balance, since an untested rejection path could silently let an overdraft through.

Testing the async ledger callout with a mock

@isTest
static void reportsToLedgerAsynchronously() {
    Test.setMock(HttpCalloutMock.class, new LedgerMockSuccess());
    Bank_Account__c acc = new Bank_Account__c(Name = 'Test', Balance__c = 0, Status__c = 'Active');
    insert acc;

    Test.startTest();
    insert new Transaction__c(Bank_Account__c = acc.Id, Type__c = 'Deposit', Amount__c = 50);
    Test.stopTest(); // forces the enqueued Queueable job to run synchronously here

    // assert on a field the mock's callout would have set, or that no exception occurred
}

Test.setMock and Test.stopTest() forcing the enqueued job to run are Module 31's and Module 36's exact async-callout testing patterns, combined here for the ledger-integration job from Lesson 4.

Exercise

As a comment, list the three categories of test this lesson covers, and why all three are needed rather than just the happy-path bulk test.

Show hint

Think about what each test type would catch that the others wouldn't.

APEX

Write a Full Test Suite Quiz

1. Why does rejectsOverdraftWithdrawal wrap the insert in a try/catch and assert inside the catch block?

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 applies Module 31's full testing toolkit — bulk data, negative-path testing, and Test.startTest()/stopTest() for the async ledger callout — to a system where an untested edge case has real financial consequences.