Advanced 35 min read

Build the Calculation Engine

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

  • Write a LoanCalculationService with a monthly payment formula
  • Apply a credit-score-based interest rate
  • Write the first formal test class for this service, immediately

Prerequisites: "Design the Approval Logic"

The calculation service

public class LoanCalculationService {
    public Decimal calculateInterestRate(Decimal creditScore) {
        if (creditScore >= 700) {
            return 6.5;
        } else if (creditScore >= 600) {
            return 9.0;
        } else {
            return 14.0;
        }
    }

    public Decimal calculateMonthlyPayment(Decimal amount, Decimal annualRatePercent, Integer months) {
        Decimal monthlyRate = (annualRatePercent / 100) / 12;
        return (amount * monthlyRate) / (1 - Math.pow(1 + monthlyRate, -months));
    }
}
  1. public class LoanCalculationService { — the class.
  2. public Decimal calculateInterestRate(Decimal creditScore) { — Module 6's if/else if/else chain.
  3. if (creditScore >= 700) { — the best tier.
  4. return 6.5; — its rate.
  5. } else if (creditScore >= 600) { — the middle tier.
  6. return 9.0; — its rate.
  7. } else { — the lowest tier.
  8. return 14.0; — its rate.
  9. } — closes the if/else if/else.
  10. } — closes the method.
  11. (blank line)
  12. public Decimal calculateMonthlyPayment(Decimal amount, Decimal annualRatePercent, Integer months) { — implements the standard amortized-loan-payment formula.
  13. Decimal monthlyRate = (annualRatePercent / 100) / 12; — converts the annual percentage to a monthly decimal rate.
  14. return (amount * monthlyRate) / (1 - Math.pow(1 + monthlyRate, -months)); — the principal, annual rate, and loan term in months, combined into the standard formula.
  15. } — closes the method.
  16. } — closes the class.

Testing it immediately

@isTest
private class LoanCalculationServiceTest {
    @isTest
    static void highCreditScoreGetsLowestRate() {
        LoanCalculationService service = new LoanCalculationService();
        System.assertEquals(6.5, service.calculateInterestRate(750));
    }

    @isTest
    static void midCreditScoreGetsMiddleRate() {
        LoanCalculationService service = new LoanCalculationService();
        System.assertEquals(9.0, service.calculateInterestRate(650));
    }

    @isTest
    static void lowCreditScoreGetsHighestRate() {
        LoanCalculationService service = new LoanCalculationService();
        System.assertEquals(14.0, service.calculateInterestRate(550));
    }
}
  1. @isTest — marks the class.
  2. private class LoanCalculationServiceTest { — three test methods, one per branch of the if/else if/else chain.
  3. @isTest — marks the first test.
  4. static void highCreditScoreGetsLowestRate() { — this is Module 31's Lesson 2 habit ("one test method, one scenario") applied the moment the logic is written.
  5. LoanCalculationService service = new LoanCalculationService(); — creates the service.
  6. System.assertEquals(6.5, service.calculateInterestRate(750)); — verifies the top tier.
  7. } — closes this test.
  8. (blank line)
  9. @isTest — marks the second test.
  10. static void midCreditScoreGetsMiddleRate() { — a different scenario.
  11. LoanCalculationService service = new LoanCalculationService(); — creates the service.
  12. System.assertEquals(9.0, service.calculateInterestRate(650)); — verifies the middle tier.
  13. } — closes this test.
  14. (blank line)
  15. @isTest — marks the third test.
  16. static void lowCreditScoreGetsHighestRate() { — another scenario.
  17. LoanCalculationService service = new LoanCalculationService(); — creates the service.
  18. System.assertEquals(14.0, service.calculateInterestRate(550)); — verifies the lowest tier.
  19. } — closes this test.
  20. } — closes the class.

Testing the boundary values specifically

@isTest
static void creditScoreOf700IsTheLowestQualifyingForBestRate() {
    LoanCalculationService service = new LoanCalculationService();
    System.assertEquals(6.5, service.calculateInterestRate(700));
}

@isTest
static void creditScoreOf699JustMissesTheBestRate() {
    LoanCalculationService service = new LoanCalculationService();
    System.assertEquals(9.0, service.calculateInterestRate(699));
}
  1. @isTest — marks the first test.
  2. static void creditScoreOf700IsTheLowestQualifyingForBestRate() { — testing exactly 700 — the boundary between two branches.
  3. LoanCalculationService service = new LoanCalculationService(); — creates the service.
  4. System.assertEquals(6.5, service.calculateInterestRate(700)); — 700 should qualify for the best rate.
  5. } — closes this test.
  6. (blank line)
  7. @isTest — marks the second test.
  8. static void creditScoreOf699JustMissesTheBestRate() { — the value one below the boundary.
  9. LoanCalculationService service = new LoanCalculationService(); — creates the service.
  10. System.assertEquals(9.0, service.calculateInterestRate(699)); — 699 should just miss it — this is where off-by-one bugs (> instead of >=, from Module 4) actually hide.
  11. } — closes this test.

This is Module 31's "assertions that actually tell you something" lesson, applied specifically to boundary conditions.

Exercise

Write a test confirming calculateInterestRate(600) returns 9.0 (the boundary between the middle and lowest tiers).

Show hint

Test the exact boundary value, not just a value comfortably inside the range.

Salesforce: ○ Not connected Connect
APEX

Execution Results

Run your code to see the result.

Build the Calculation Engine Quiz

1. Why does this lesson specifically test the boundary values 699 and 700?

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 builds the calculation logic and its test class together — the standard shape of every lesson going forward in this module.