Advanced 20 min read

Testing Exceptions

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

  • Verify that a method throws the expected exception under the right conditions
  • Verify that a method does NOT throw under valid conditions
  • Explain why an untested exception path is a common source of production bugs

Prerequisites: "Assertions That Actually Tell You Something"

Confirming an exception is thrown

@isTest
static void checkOutAlreadyBorrowedBookThrows() {
    Book__c book = new Book__c(Title__c = 'Test Book', ISBN__c = '123', Is_Checked_Out__c = true);
    insert book;

    LibraryService library = new LibraryService();

    try {
        library.checkOutBook(book.Id, 'Amara Nkosi');
        System.assert(false, 'Expected a LibraryException to be thrown');
    } catch (LibraryException e) {
        System.assert(e.getMessage().contains('already checked out'));
    }
}

System.assert(false, ...) right after the call being tested is a deliberate trick: if checkOutBook doesn't throw, execution reaches that line and the test fails with a clear message — if it does throw (as expected), that line is never reached, and the catch block verifies the exception's details instead.

Confirming a valid case does NOT throw

@isTest
static void checkOutAvailableBookSucceedsWithoutException() {
    Book__c book = new Book__c(Title__c = 'Test Book', ISBN__c = '123', Is_Checked_Out__c = false);
    insert book;

    LibraryService library = new LibraryService();

    Checkout__c checkout = library.checkOutBook(book.Id, 'Amara Nkosi');

    System.assertNotEquals(null, checkout.Id);
}

This test doesn't wrap the call in try/catch at all — if checkOutBook unexpectedly threw an exception here, the test would simply fail with that uncaught exception, which is exactly the correct behavior: an available book checking out successfully should never throw.

Why untested exception paths are a real risk

It's easy to write a test only for the "happy path" (Module 12's term) and never verify the error-handling branches at all — meaning a bug in the exception logic itself (a wrong message, a missed edge case, or worse, an exception that should fire but silently doesn't) can ship straight to production undetected. Explicitly testing both "throws when it should" and "doesn't throw when it shouldn't" closes this exact gap.

Exercise

Write a test confirming that InventoryService.shipOrder throws an InventoryException when shipping more than the available stock.

Show hint

Use the try/System.assert(false)/catch pattern from this lesson.

APEX

Testing Exceptions Quiz

1. In the try/System.assert(false)/catch testing pattern, when is System.assert(false) actually reached?

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

A method's exception-throwing behavior (Module 13, Module 22's custom exceptions) deserves its own explicit test — confirming both that it throws when it should, and that it doesn't throw when it shouldn't.