Skip to content

Instantly share code, notes, and snippets.

@SamirTalwar
Last active August 29, 2015 14:20
Show Gist options
  • Select an option

  • Save SamirTalwar/7cbece56643ce5d5c5f4 to your computer and use it in GitHub Desktop.

Select an option

Save SamirTalwar/7cbece56643ce5d5c5f4 to your computer and use it in GitHub Desktop.
Bank Account Kata with Transactions in F#
module BankAccount
module Prod =
type Result<'T> =
| Success of 'T
| Failure of string
let (|>>) (result: Result<'T>) (f: 'T -> Result<'U>) =
match result with
| Success(value) -> f value
| Failure(error) -> Failure(error)
type Currency =
| USD
override self.ToString() =
match self with
USD -> "USD"
type Account = { Amount: (int * Currency) }
let newAccount = { Amount = (0, USD) }
let deposit depositAmount currency { Amount = (oldAmount, _) } = { Amount = (oldAmount + depositAmount, USD)}
let withdraw withdrawalAmount currency { Amount = (oldAmount, _) } : Result<Account> =
match oldAmount - withdrawalAmount with
| newAmount when newAmount < 0 -> Failure("This bank account cannot be overdrawn.")
| newAmount -> Success { Amount = (newAmount, USD) }
let transfer amount currency bankAccountA bankAccountB : Result<Account * Account> =
withdraw amount currency bankAccountA |>> (fun newBankAccountA ->
let newBankAccountB = deposit amount currency bankAccountB
Success (newBankAccountA, newBankAccountB))
module Tests =
open Prod
open FsCheck
open NUnit.Framework
[<Test>]
let ``a new account has a balance of 0``() =
let bankAccount = newAccount
Assert.AreEqual ((0, USD), bankAccount.Amount)
[<Test>]
let ``money can be deposited into a bank account``() =
let bankAccount = newAccount |> deposit 10 USD
Assert.AreEqual ((10, USD), bankAccount.Amount)
[<Test>]
let ``money deposited into a bank account should be added to current amount``() =
let bankAccount = newAccount |> deposit 10 USD |> deposit 5 USD
Assert.AreEqual((15, USD), bankAccount.Amount)
[<Test>]
let ``money can be withdrawn from a bank account``() =
let (Success(bankAccount)) = newAccount |> deposit 50 USD |> withdraw 20 USD
Assert.AreEqual({ Amount = (30, USD) }, bankAccount)
[<Test>]
let ``a bank account cannot be overdrawn``() =
let (Failure(error)) = newAccount |> deposit 50 USD |> withdraw 100 USD
Assert.AreEqual("This bank account cannot be overdrawn.", error)
[<Test>]
let ``multiple withdrawals can be chained``() =
let (Success(bankAccount)) = newAccount |> deposit 1000 USD |> withdraw 500 USD |>> withdraw 200 USD
Assert.AreEqual((300, USD), bankAccount.Amount)
[<Test>]
let ``money can be transferred from one account to another``() =
let previousBankAccountA = newAccount |> deposit 500 USD
let previousBankAccountB = newAccount |> deposit 200 USD
let (Success((bankAccountA, bankAccountB))) = transfer 100 USD previousBankAccountA previousBankAccountB
Assert.AreEqual((400, USD), bankAccountA.Amount)
Assert.AreEqual((300, USD), bankAccountB.Amount)
[<Test>]
let ``money can't be transferred from one account to another if there aren't enough funds``() =
let previousBankAccountA = newAccount |> deposit 500 USD
let previousBankAccountB = newAccount |> deposit 200 USD
let (Failure(error)) = transfer 600 USD previousBankAccountA previousBankAccountB
Assert.AreEqual("This bank account cannot be overdrawn.", error)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment