As part of The Joy of Kotlin book (page 9). it comes up with the below idea to remove side effects from a function, but I couldn't figure out where we should do the charge()
functionality after that. Now let me bring the code:
Suppose we have a buyDonut
function:
fun buyDonut(creditCard: CreditCard): Donut {
val donut = Donut()
creditCard.charge(donut.price) // Charges the credit card as a side effect
return donut
}
To remove the side effect we update it as follows:
fun buyDonut(creditCard: CreditCard): Purchase {
val donut = Donut()
val payment = Payment(creditCard, Donut.price)
return Purchase(donut, payment)
}
Here is the Payment
and Purchase
classes definition:
class Payment(val creditCard: CreditCard, val amount: Int)
data class Purchase(val donut: Donut, val payment: Payment)
The benefit of doing this is we can test the buyDonut
function without the need to call CreditCard.charge()
or use mock. It could be done like this:
@Test
fun testBuyDonuts() {
val creditCard = CreditCard()
val purchase = buyDonuts(creditCard)
assertEquals(Donut.price, purchase.payment.amount)
assertEquals(creditCard, purchase.payment.creditCard)
}
So far so good, but where should charge()
method get called?
It seems just moving things around but at last, I have to use mocking somewhere else where the charge()
method is going to be called, or there is an approach that I can't figure out.
You're right; removing the side effect from buyDonut()
doesn't eliminate the need for a charge()
operation. Instead, it defers that operation to a later point where you can handle it more systematically.
Typically, you'd collect multiple Payment
objects and process them in a batch, maybe in a function like processPayments()
:
fun processPayments(payments: List<Payment>) {
payments.forEach { payment ->
payment.creditCard.charge(payment.amount)
}
}
This function can be tested independently and will be the place where you'd use mocks if necessary. This separation allows for easier testing and more flexible code, e.g., adding additional steps like logging or validation before actually charging.