I just can't help myself... I had to flesh out the Account Transfer behavior from my previous post. I was interested in two things:
Existing User Story:
As an account holder,I want to transfer money between accountsSo that I can avoid overdraft feesAcceptance Criteria:
This was the fun part, really - seeing if I could implement the API that I wanted to see. It turned out to be significantly easier than I had thought it would be.
I started with the Account object, and added the "Transfer" method. I knew that this method needed to return an object so that I could have the "To" method accept an Account object. A few moments of thinking this through and I decided to go with an object called TransferCriteria. This would let me collect all of the information that I need, about the transfer.
public TransferCriteria Transfer(double ammount)
{
TransferCriteria criteria = new TransferCriteria(this, ammount);
return criteria;
}
The end-goal of this syntax model is to return an AccountTransfer object, directly. So, the TransferCriteria.To method then creates an AccountTransfer object, using the TransferCriteria.
public AccountTransfer To(Account receivingAccount)
ReceivingAccount = receivingAccount;
return new AccountTransfer(this);
In the end, I have an AccountTransfer object that contains all of the information I need - the originating account, the receiving account, and the transfer amount. With that in place, the rest of the code is simple.
AccountTransferSpecifications.cs
using AccountSample.Domain;
using NUnit.Framework;
using SpecUnit;
namespace AccountSample.Specifications
[TestFixture]
[Concern("Account Transfers")]
public class When_The_Requested_Transfer_Amount_Is_Greater_Than_The_Originating_Account_Balance : ContextSpecification
#region Context
private const double originatingAccountBalance = 100;
private const double receivingAccountBalance = 0;
private const double requestedTransferAmmount = 125.95;
private Account originatingAccount;
private Account receivingAccount;
private AccountTransfer accountTransfer;
protected override void Context()
originatingAccount = new Account(originatingAccountBalance);
receivingAccount = new Account(receivingAccountBalance);
accountTransfer = originatingAccount.Transfer(requestedTransferAmmount).To(receivingAccount);
#endregion
#region Observations
[Test]
[Observation]
public void The_Transfer_Should_Fail()
accountTransfer.Status.ShouldEqual(TransferStatus.Failed);
public void The_Originating_Account_Balance_Should_Not_Be_Changed()
originatingAccount.Balance.ShouldEqual(originatingAccountBalance);
public void The_Receiving_Account_Balance_Should_Not_Be_Changed()
receivingAccount.Balance.ShouldEqual(receivingAccountBalance);
public class When_The_Requested_Transfer_Amount_is_Less_Than_The_Originating_Account_Balance: ContextSpecification
private const double requestedTransferAmmount = 25;
public void The_Transfer_Is_Successful()
accountTransfer.Status.ShouldEqual(TransferStatus.Success);
public void The_Originating_Account_Is_Debited_The_Transfer_Amount()
const double newBalance = originatingAccountBalance - requestedTransferAmmount;
originatingAccount.Balance.ShouldEqual(newBalance);
public void The_Receiving_Account_Is_Credited_The_Transfer_Amount()
const double newBalance = receivingAccountBalance + requestedTransferAmmount;
receivingAccount.Balance.ShouldEqual(newBalance);
Account.cs
namespace AccountSample.Domain
public class Account
#region Properties
public double Balance { get; private set; }
#region Constructor
public Account(double currentAccountBalance)
Balance = currentAccountBalance;
#region Methods
public void Debit(double ammount)
Balance -= ammount;
public void Credit(double ammount)
Balance += ammount;
AccountTransfer.cs
public class AccountTransfer
#region Vars
private TransferStatus _status = TransferStatus.None;
public TransferStatus Status
get { return _status; }
private set { _status = value; }
public double TransferAmmount { get; private set; }
public Account OriginatingAccount { get; private set; }
public Account ReceivingAccount { get; private set; }
public AccountTransfer(TransferCriteria criteria)
OriginatingAccount = criteria.OriginatingAccount;
ReceivingAccount = criteria.ReceivingAccount;
TransferAmmount = criteria.TransferAmount;
Execute();
private void Execute()
if (TransferAmmount > OriginatingAccount.Balance)
Status = TransferStatus.Failed;
else
OriginatingAccount.Debit(TransferAmmount);
ReceivingAccount.Credit(TransferAmmount);
Status = TransferStatus.Success;
TransferCriteria.cs
public class TransferCriteria
public double TransferAmount { get; private set; }
public TransferCriteria(Account originatingAccount, double transferAmount)
OriginatingAccount = originatingAccount;
TransferAmount = transferAmount;
And finally, TransferStatus.cs
public enum TransferStatus
None = 0,
Failed = -1,
Success = 1
I am madly in love with Behavior Driven Development.
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.