Advanced 30 min read

Enforce Validation Rules

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

  • Validate that a loan amount and income are both positive
  • Validate that a credit score falls within a realistic range
  • Write tests confirming both valid and invalid input are handled correctly

Prerequisites: "Build the Calculation Engine"

Validating the inputs

public class LoanValidationException extends Exception {}

public class LoanValidationService {
    public void validate(Decimal requestedAmount, Decimal annualIncome, Decimal creditScore) {
        if (requestedAmount == null || requestedAmount <= 0) {
            throw new LoanValidationException('Requested amount must be positive.');
        }
        if (annualIncome == null || annualIncome <= 0) {
            throw new LoanValidationException('Annual income must be positive.');
        }
        if (creditScore == null || creditScore < 300 || creditScore > 850) {
            throw new LoanValidationException('Credit score must be between 300 and 850.');
        }
    }
}
  1. public class LoanValidationException extends Exception {} — exactly Module 22's LibraryException and Module 23's InventoryException pattern — one dedicated exception type.
  2. (blank line)
  3. public class LoanValidationService { — the validation service.
  4. public void validate(Decimal requestedAmount, Decimal annualIncome, Decimal creditScore) { — the method.
  5. if (requestedAmount == null || requestedAmount <= 0) { — the first rule, stated as a clear, individually-testable condition.
  6. throw new LoanValidationException('Requested amount must be positive.'); — rejects with a specific message.
  7. } — closes the if.
  8. if (annualIncome == null || annualIncome <= 0) { — the second rule.
  9. throw new LoanValidationException('Annual income must be positive.'); — rejects with its own message.
  10. } — closes the if.
  11. if (creditScore == null || creditScore < 300 || creditScore > 850) { — the third rule.
  12. throw new LoanValidationException('Credit score must be between 300 and 850.'); — rejects with its own message.
  13. } — closes the if.
  14. } — closes the method.
  15. } — closes the class.

Testing the rejection paths

@isTest
private class LoanValidationServiceTest {
    @isTest
    static void negativeAmountThrows() {
        LoanValidationService validator = new LoanValidationService();
        try {
            validator.validate(-1000, 50000, 700);
            System.assert(false, 'Expected a LoanValidationException');
        } catch (LoanValidationException e) {
            System.assert(e.getMessage().contains('amount'));
        }
    }

    @isTest
    static void creditScoreAbove850Throws() {
        LoanValidationService validator = new LoanValidationService();
        try {
            validator.validate(10000, 50000, 900);
            System.assert(false, 'Expected a LoanValidationException');
        } catch (LoanValidationException e) {
            System.assert(e.getMessage().contains('Credit score'));
        }
    }
}
  1. @isTest — marks the class.
  2. private class LoanValidationServiceTest { — the class.
  3. @isTest — marks the first test.
  4. static void negativeAmountThrows() { — this is Module 31's "Testing Exceptions" lesson, applied to real project code the moment it's written.
  5. LoanValidationService validator = new LoanValidationService(); — creates the validator.
  6. try { — wraps the call expected to throw.
  7. validator.validate(-1000, 50000, 700); — an invalid, negative amount.
  8. System.assert(false, 'Expected a LoanValidationException'); — reached only if it unexpectedly didn't throw.
  9. } catch (LoanValidationException e) { — catches the expected failure.
  10. System.assert(e.getMessage().contains('amount')); — verifies it's the right error.
  11. } — closes the try/catch.
  12. } — closes this test.
  13. (blank line)
  14. @isTest — marks the second test.
  15. static void creditScoreAbove850Throws() { — a different invalid-input scenario.
  16. LoanValidationService validator = new LoanValidationService(); — creates the validator.
  17. try { — wraps the call.
  18. validator.validate(10000, 50000, 900); — an out-of-range credit score.
  19. System.assert(false, 'Expected a LoanValidationException'); — reached only if it didn't throw.
  20. } catch (LoanValidationException e) { — catches it.
  21. System.assert(e.getMessage().contains('Credit score')); — verifies the right error.
  22. } — closes the try/catch.
  23. } — closes this test.
  24. } — closes the class.

Testing the acceptance path too

@isTest
static void validInputsDoNotThrow() {
    LoanValidationService validator = new LoanValidationService();
    validator.validate(10000, 50000, 700); // should complete without throwing
    System.assert(true); // reaching this line confirms no exception was thrown
}
  1. @isTest — marks the test method.
  2. static void validInputsDoNotThrow() { — exactly Module 31's "confirm it does NOT throw" habit — a valid set of inputs deserves its own explicit test.
  3. LoanValidationService validator = new LoanValidationService(); — creates the validator.
  4. validator.validate(10000, 50000, 700); — should complete without throwing.
  5. System.assert(true); — reaching this line confirms no exception was thrown, rather than just assuming "the rejection tests passing" implies the acceptance path works too.
  6. } — closes the test method.

Exercise

Write a test confirming that a null annualIncome throws a LoanValidationException.

Show hint

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

Salesforce: ○ Not connected Connect
APEX

Execution Results

Run your code to see the result.

Enforce Validation Rules Quiz

1. Why does this lesson test validInputsDoNotThrow as its own separate 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

Before the approval decision itself, this lesson validates the raw inputs — and, following this module's pattern, tests both the rejection and acceptance paths immediately.