I am writing a basic player wallet
management class where Integers
are added (credited) or subtracted (debited) to a player's wallet where errors are caught by a simple try-catch
logic.
Note: It only is meant to handle
Integers
.
I am attempting to abstract away the try-catch
logic into this Wallet class; it should handle anything with credit
, debit
actions, capture errors and return errors if any found.
Is there a way to move try-catch stuff into a class and yet returns errors?
Lets go through this example:
My current class is as such:
enum WalletError : Error {
case mustBePositive
case notEnoughFunds
}
class Wallet {
public private(set) var balance: Int = 0
init(amount: Int = 0) {
self.balance = amount
}
func credit(amount: Int) throws {
guard amount > 0 else {
throw WalletError.mustBePositive
}
handleCredit(amount: amount)
}
func debit(amount: Int) throws {
guard amount > 0 else {
throw WalletError.mustBePositive
}
guard balance >= amount else {
throw WalletError.notEnoughFunds
}
guard (self.balance - amount >= 0) else {
throw WalletError.notEnoughFunds
}
handleDebit(amount: amount)
}
// MARK: (Private)
private func handleCredit(amount: Int) {
self.balance += amount
}
private func handleDebit(amount: Int) {
self.balance -= amount
}
}
Testing the debit actions, I have this XCTest
function
func testDebit() {
let w = Wallet()
XCTAssertTrue(w.balance == 0)
// Can't debit negative numbers
XCTAssertThrowsError(try w.debit(amount: -100)) { error in
XCTAssertEqual(error as? WalletError, WalletError.mustBePositive)
}
// can't debit money you don't have
XCTAssertThrowsError(try w.debit(amount: 100)) { error in
XCTAssertEqual(error as? WalletError, WalletError.notEnoughFunds)
}
// credit $100
XCTAssertNoThrow(try w.credit(amount: 100))
// debit $0 should throw error
XCTAssertThrowsError(try w.debit(amount: 0)) { error in
XCTAssertEqual(error as? WalletError, WalletError.mustBePositive)
}
// debit > balance should throw error
XCTAssertThrowsError(try w.debit(amount: 101)) { error in
XCTAssertEqual(error as? WalletError, WalletError.notEnoughFunds)
}
// debit $1 should be successful
XCTAssertNoThrow(try w.debit(amount: 1))
// balance should be 100-1 = $99
XCTAssertTrue(w.balance == 99)
}
The problem I'm having here is:
When I write the application, I need to write the try-catch block every time.
I would like the Wallet class to handle all try-catch stuff, and only report errors
I tried this:
// changes to wallet class
func canCredit(amount: Int) throws -> Bool {
guard amount > 0 else {
throw WalletError.mustBePositive
}
return true
}
func credit(amount: Int) -> Error? {
do {
if ( try canCredit(amount: amount) ) {
handleCredit(amount: amount)
}
return nil
} catch let error {
return error
}
}
But now the XCTest
doesn't know about the throwing, and I would need to test the canCredit which now only returns true/false.
Further, the credit attempts to return the error, but I can't use XCTAssertThrowsError()
Is there a way where I can:
The Wallet class should only be responsible for credit, debit and validation
Throw all my try-catch stuff logic into this class so I don't have to repeat the try-catch stuff all the time?
I hope I've clarified the issue.
With thanks