I want to test following method:
@Transactional
override suspend fun updateDebtWithoutDocumentsByUserId(userId: Long, dtoIn: DebtDTO): DebtDTO {
if (dtoIn.id == null) {
throw NotFoundException("Can only update existing debt")
}
val d = debtRepository.findById(dtoIn.id) ?: throw NotFoundException("Could not find debt ${dtoIn.id}")
val updated = debtRepository.save(
d.copy(
title = dtoIn.title,
amount = dtoIn.amount,
category = dtoIn.category,
)
)
if (dtoIn.amount != d.amount) {
// update potentially existing installment plan.
// Note that this guarantees that the installment amount is kept in bounds and all installment records are updated
// accordingly.
val installmentPlan = installmentPlanRepository.findByDebtId(dtoIn.id)
if (installmentPlan != null) {
val installmentRecordIds: List<Long> = installmentRepository.findByDebtId(dtoIn.id)
.toList()
.map { it.recordId }
storeInstallmentPlan(dtoIn.id, InstallmentPlanDTO.from(installmentPlan, installmentRecordIds))
.collect() // ensure that the flow is executed.
}
}
val documents = debtDocumentRepository.findByDebtId(dtoIn.id)
.toSet()
.map { DocumentDTO.from(it) }
.associateBy { it.id!! }
return DebtDTO.from(updated, documents)
}
If there is an installment plan available the method storeInstallmentPlan(dtoIn.id, InstallmentPlanDTO.from(installmentPlan, installmentRecordIds))
will be called. The original method storeInstallmentPlan
is already tested.
How can I mock storeInstallmentPlan
inside my unit I want to test without repeat myself by testing storeInstallmentPlan
again? I think I have to somehow use SpyK
for doing this, but I don't know exactly how to do it. Maybe someone has a solution for me.
Thanks :)
Here my full test class:
@ExtendWith(MockKExtension::class)
internal class PlanServiceFeatureImplTest {
@MockK
lateinit var debtRepository: DebtRepository
@MockK
lateinit var debtDocumentRepository: DebtDocumentRepository
@MockK
lateinit var installmentPlanRepository: InstallmentPlanRepository
@MockK
lateinit var installmentRepository: InstallmentRepository
@MockK
lateinit var recordService: RecordService
@MockK
lateinit var userService: UserService
@InjectMockKs
lateinit var planService: PlanServiceFeatureImpl
@Test
fun `updateDebtWithoutDocumentsByUserId ok - no installment plan available`() {
val userId = 123L
val dtoIn = DebtDTO(
id = 1L,
title = "debt1New",
amount = BigDecimal.valueOf(2000),
category = DebtCategory.dangerous,
documents = mapOf(),
)
val oldDebt = Debt(
id = 1L,
userId = userId,
title = "debt1",
amount = BigDecimal.valueOf(1000),
category = DebtCategory.credit,
)
assertThat(dtoIn.title).isNotEqualTo(oldDebt.title)
assertThat(dtoIn.amount).isNotEqualTo(oldDebt.amount)
assertThat(dtoIn.category).isNotEqualTo(oldDebt.category)
coEvery { debtRepository.findById(dtoIn.id!!) } returns oldDebt
val doc1 = DebtDocument(
id = 11L,
debtId = dtoIn.id!!,
title = "doc1",
bytes = byteArrayOf(0x01),
)
coEvery { debtRepository.save(any()) } answers { arg(0) }
coEvery { debtDocumentRepository.findByDebtId(dtoIn.id!!) } returns flowOf(doc1)
//no installment plan available
coEvery { installmentPlanRepository.findByDebtId(dtoIn.id!!) } returns null
runBlocking {
val actual = planService.updateDebtWithoutDocumentsByUserId(userId, dtoIn)
assertThat(actual).isEqualTo(
dtoIn.copy(
documents = mapOf(doc1.id!! to DocumentDTO.from(doc1)),
)
)
}
coVerify {
debtRepository.save(withArg {
assertThat(it.id).isEqualTo(dtoIn.id)
assertThat(it.userId).isEqualTo(userId)
assertThat(it.title).isEqualTo(dtoIn.title)
assertThat(it.amount).isEqualTo(dtoIn.amount)
assertThat(it.category).isEqualTo(dtoIn.category)
})
}
}
@Test
fun `updateDebtWithoutDocumentsByUserId ok - installment plan available`() {
val userId = 123L
val dtoIn = DebtDTO(
id = 1L,
title = "debt1New",
amount = BigDecimal.valueOf(2000),
category = DebtCategory.dangerous,
documents = mapOf(),
)
val oldDebt = Debt(
id = 1L,
userId = userId,
title = "debt1",
amount = BigDecimal.valueOf(1000),
category = DebtCategory.credit,
)
assertThat(dtoIn.title).isNotEqualTo(oldDebt.title)
assertThat(dtoIn.amount).isNotEqualTo(oldDebt.amount)
assertThat(dtoIn.category).isNotEqualTo(oldDebt.category)
coEvery { debtRepository.findById(dtoIn.id!!) } returns oldDebt
val doc1 = DebtDocument(
id = 11L,
debtId = dtoIn.id!!,
title = "doc1",
bytes = byteArrayOf(0x01),
)
coEvery { debtRepository.save(any()) } answers { arg(0) }
coEvery { debtDocumentRepository.findByDebtId(dtoIn.id!!) } returns flowOf(doc1)
//installment plan available
val plan = mockk<InstallmentPlan>()
coEvery { installmentPlanRepository.findByDebtId(dtoIn.id!!) } returns plan
coEvery { installmentRepository.findByDebtId(dtoIn.id!!) } returns flowOf()
/*
Here I need to test storeInstallmentPlan / mock of it
*/
runBlocking {
val actual = planService.updateDebtWithoutDocumentsByUserId(userId, dtoIn)
assertThat(actual).isEqualTo(
dtoIn.copy(
documents = mapOf(doc1.id!! to DocumentDTO.from(doc1)),
)
)
}
coVerify {
debtRepository.save(withArg {
assertThat(it.id).isEqualTo(dtoIn.id)
assertThat(it.userId).isEqualTo(userId)
assertThat(it.title).isEqualTo(dtoIn.title)
assertThat(it.amount).isEqualTo(dtoIn.amount)
assertThat(it.category).isEqualTo(dtoIn.category)
})
}
}
@Test
fun `storeInstallmentPlan ok`(@MockK user: User) {
val debtId = 123L
val debtAmount = BigDecimal.valueOf(1000)
val installmentAmount = BigDecimal.valueOf(500)
val interval = Repeat.monthly
val firstPaymentDate = LocalDate.of(2021, 12, 27)
.atStartOfDay(Time.DEFAULT_TIME_ZONE).toOffsetDateTime()
val planDTO = InstallmentPlanDTO(
interval = interval,
firstPaymentDate = firstPaymentDate,
amount = installmentAmount,
installments = listOf()
)
val debt = Debt(
userId = 32L,
title = "debt1",
amount = debtAmount,
category = DebtCategory.credit
)
val plan = InstallmentPlan(
id = 122L,
debtId = debtId,
interval = interval,
firstPaymentDate = firstPaymentDate,
amount = installmentAmount
)
val installment1 = Installment(
id = 12L,
debtId = debtId,
recordId = 15L
)
val installment2 = Installment(
id = 13L,
debtId = debtId,
recordId = 16L
)
val installments = listOf(
WalletRecord(
userId = debt.userId,
type = RecordType.debt_rate,
amount = installmentAmount,
title = debt.title,
category = RecordCategory.debt_rate,
repeat = plan.interval,
startDate = ZonedDateTime.parse("2021-11-28T00:00+01:00[Europe/Berlin]").toOffsetDateTime(),
endDate = ZonedDateTime.parse("2021-12-27T00:00+01:00[Europe/Berlin]").toOffsetDateTime()
),
WalletRecord(
userId = debt.userId,
type = RecordType.debt_rate,
amount = installmentAmount,
title = debt.title,
category = RecordCategory.debt_rate,
repeat = plan.interval,
startDate = ZonedDateTime.parse("2021-12-28T00:00+01:00[Europe/Berlin]").toOffsetDateTime(),
endDate = ZonedDateTime.parse("2022-01-27T00:00+01:00[Europe/Berlin]").toOffsetDateTime()
)
)
every { user.tz } returns "Europe/Berlin"
coEvery { debtRepository.findById(debtId) } returns debt
//installment plan for debt with debtId exists
coEvery { installmentPlanRepository.findByDebtId(debtId) } returns plan
//update installment plan
coEvery {
installmentPlanRepository.save(planDTO.copy(amount = installmentAmount).toEntity(id = plan.id, debtId = debtId))
} returns InstallmentPlan(plan.id, debtId, interval, firstPaymentDate, installmentAmount)
//find and delete all previous installments/records for debt with debtId
coEvery { installmentRepository.findByDebtId(debtId) } returns flowOf(installment1, installment2)
coEvery { installmentRepository.deleteAllById(listOf(installment1.id!!, installment2.id!!)) } just Runs
coEvery { recordService.deleteAll(listOf(installment1.recordId, installment2.recordId)) } just Runs
//replace all new installments/records
coEvery { userService.findById(debt.userId) } returns user
coEvery { recordService.saveAll(installments) } returns flowOf()
coEvery { installmentRepository.saveAll(any<Flow<Installment>>()) } returns flowOf()
runBlocking { planService.storeInstallmentPlan(debtId, planDTO) }
coVerify { installmentPlanRepository.save(
planDTO.copy(amount = installmentAmount).toEntity(id = plan.id, debtId = debtId)
) }
coVerify { recordService.saveAll(installments) }
coVerify { installmentRepository.saveAll(any<Flow<Installment>>()) }
}
}
Assuming that storeInstallmentPlan()
is public, then you could annotate with @SpyK
the class you are testing and then you would need to mock the storeInstallmentPlan()
call in the updateDebtWithoutDocumentsByUserId ok - no installment plan available
test.
If you could add your complete test class, then we could provide a full example.