using FluentAssertions.Execution;
public static void Main()
var loanInterestServiceTests = new LoanInterestServiceTests();
loanInterestServiceTests.GetLoanInterest_ReturnsDefaultInterest(1990, 30, "1.8");
loanInterestServiceTests.GetLoanInterest_ReturnsDefaultInterest(1988, 20, "1.6");
loanInterestServiceTests.GetLoanInterest_ReturnsDefaultInterest(1986, 10, "1.4");
loanInterestServiceTests.GetLoanInterest_ReturnsDefaultInterest(2003, 10, "1.2");
Console.WriteLine($"{nameof(LoanInterestServiceTests)} tests passed");
var calculationServiceTests = new CalculationServiceTests();
calculationServiceTests.GetCalculation_ValidPerson_Confirms(1983, 42000, 30);
calculationServiceTests.GetCalculation_ValidPerson_Confirms(1965, 55000, 25);
calculationServiceTests.GetCalculation_ValidPerson_Confirms(2001, 22000, 15);
calculationServiceTests.GetCalculation_ValidPerson_Confirms(1996, 18000, 8);
Console.WriteLine($"{nameof(CalculationServiceTests)} tests passed");
public class MortgageCalculation
public MortgageCalculation(bool isPersonAvailable, decimal? amount, decimal? monthlyPayment)
IsPersonAvailable = isPersonAvailable;
MonthlyPayment = monthlyPayment;
public bool IsPersonAvailable { get; }
public decimal? Amount { get; }
public decimal? MonthlyPayment { get; }
public static MortgageCalculation GetRejectedCalculation()
return new(false, null, null);
public static MortgageCalculation GetConfirmedCalculation(decimal amount, decimal monthlyPayment)
return new(true, amount, monthlyPayment);
public interface ILoanInterestService
decimal GetLoanInterest(DateTime birthDate, int yearsToPay);
public class LoanInterestService : ILoanInterestService
public decimal GetLoanInterest(DateTime birthDate, int yearsToPay)
if (yearsToPay <= 1) throw new ArgumentException($"Payment period is too short: {yearsToPay} years.");
var ageInDays = (DateTime.Now.Date - birthDate.Date).TotalDays;
var ageInYears = Math.Truncate(ageInDays / 365);
throw new InvalidOperationException($"Loan interest can't be count due to age: {ageInYears} years old.");
public interface IPersonValidatorService
public class PersonValidatorService : IPersonValidatorService
public bool IsValidPerson()
throw new NotImplementedException();
public interface IMortgageService
(decimal, decimal) GetAmountAndMonthlyPayment(decimal loanInterest, decimal annualIncome, int yearsToPay);
public class MortgageService : IMortgageService
public (decimal, decimal) GetAmountAndMonthlyPayment(decimal loanInterest, decimal annualIncome, int yearsToPay)
throw new NotImplementedException();
public interface ICalculationService
MortgageCalculation GetCalculation(DateTime birthDate, decimal annualIncome, int yearsToPay);
public class CalculationService : ICalculationService
private readonly ILoanInterestService _loanInterestService;
private readonly IMortgageService _mortgageService;
private readonly IPersonValidatorService _personValidatorService;
public CalculationService(ILoanInterestService loanInterestService, IPersonValidatorService personValidatorService,
IMortgageService mortgageService)
_loanInterestService = loanInterestService;
_personValidatorService = personValidatorService;
_mortgageService = mortgageService;
public MortgageCalculation GetCalculation(DateTime birthDate, decimal annualIncome, int yearsToPay)
if (!_personValidatorService.IsValidPerson()) return MortgageCalculation.GetRejectedCalculation();
var loanInterest = _loanInterestService.GetLoanInterest(birthDate, yearsToPay);
var (amount, monthlyPayment) =
_mortgageService.GetAmountAndMonthlyPayment(loanInterest, annualIncome, yearsToPay);
return MortgageCalculation.GetConfirmedCalculation(amount, monthlyPayment);
public class LoanInterestServiceTests
private readonly ILoanInterestService _loanInterest = new LoanInterestService();
[InlineData(1990, 30, "1.8")]
[InlineData(1988, 20, "1.6")]
[InlineData(1986, 10, "1.4")]
[InlineData(2003, 10, "1.2")]
public void GetLoanInterest_ReturnsDefaultInterest(int birthYear, int yearsToPay, string expectedInterestString)
var birthDate = new DateTime(birthYear, 1, 1);
var expectedInterest = Convert.ToDecimal(expectedInterestString);
var loanInterest = _loanInterest.GetLoanInterest(birthDate, yearsToPay);
Assert.True(loanInterest == expectedInterest);
public class CalculationServiceTests
private readonly ICalculationService _calculationService;
private readonly ILoanInterestService _loanInterestService = Substitute.For<ILoanInterestService>();
private readonly IMortgageService _mortgageService = Substitute.For<IMortgageService>();
private readonly IPersonValidatorService _personValidatorService = Substitute.For<IPersonValidatorService>();
public CalculationServiceTests()
_calculationService = new CalculationService(_loanInterestService, _personValidatorService, _mortgageService);
[InlineData(1983, 42000, 30)]
[InlineData(1965, 55000, 25)]
[InlineData(2001, 22000, 15)]
[InlineData(1996, 18000, 8)]
public void GetCalculation_ValidPerson_Confirms(int birthYear, int annualIncome, int yearsToPay)
const decimal monthlyPayment = 1200;
const decimal loanInterest = 1.2m;
var birthDate = new DateTime(birthYear, 1, 1);
var amount = monthlyPayment * yearsToPay * 12;
.GetLoanInterest(birthDate, yearsToPay)
.GetAmountAndMonthlyPayment(1.2m, annualIncome, yearsToPay)
.Returns((amount, monthlyPayment));
var calculations = _calculationService.GetCalculation(birthDate, annualIncome, yearsToPay);
.GetLoanInterest(Arg.Any<DateTime>(), yearsToPay);
.GetAmountAndMonthlyPayment(default, default, default);
using (new AssertionScope())
calculations.Should().NotBeNull();
calculations.IsPersonAvailable.Should().BeTrue();
calculations.Amount.Should().Be(amount);
calculations.MonthlyPayment.Should().Be(monthlyPayment);