MockK没有找到答案:
Posted
技术标签:
【中文标题】MockK没有找到答案:【英文标题】:MockK no answer found for: 【发布时间】:2022-01-17 20:47:43 【问题描述】:我正在尝试使用 JUnit 和 MockK 库在我的服务实现中测试一个方法。
PlanServiceFeatureImplTest.kt
@Test
fun `storeInstallmentPlan`(@MockK user: User)
val debtId = 123L
val amount = BigDecimal.valueOf(1000)
val interval = Repeat.monthly
val firstPaymentDate = LocalDate.of(
2021, 11, 4
).atStartOfDay(Time.DEFAULT_TIME_ZONE).toOffsetDateTime()
val planDTO = InstallmentPlanDTO(
interval = interval,
firstPaymentDate = firstPaymentDate,
amount = amount,
installments = listOf()
)
val debt = Debt(
userId = 32L,
title = "debt1",
amount = amount,
category = DebtCategory.credit
)
val plan = InstallmentPlan(
id = 122L,
debtId = debtId,
interval = interval,
firstPaymentDate = firstPaymentDate,
amount = amount
)
val installment1 = Installment(
id = 34,
debtId = debtId,
recordId = 13
)
val installment2 = Installment(
id = 35,
debtId = debtId,
recordId = 14
)
val newStartDate = ZonedDateTime.parse("2021-10-05T00:00+02:00[Europe/Berlin]")
val newEndDate = ZonedDateTime.parse("2021-11-04T00:00+01:00[Europe/Berlin]")
val installments = listOf(
WalletRecord(
userId = debt.userId,
type = RecordType.debt_rate,
amount = plan.amount,
title = debt.title,
category = RecordCategory.debt_rate,
repeat = plan.interval,
startDate = newStartDate.toOffsetDateTime(),
endDate = newEndDate.toOffsetDateTime(),
)
)
val records = flowOf(
WalletRecord(
id = 43,
userId = debt.userId,
type = RecordType.debt_rate,
amount = plan.amount,
title = debt.title,
category = RecordCategory.debt_rate,
repeat = plan.interval,
startDate = newStartDate.toOffsetDateTime(),
endDate = newEndDate.toOffsetDateTime(),
)
)
every user.tz returns "Europe/Berlin"
coEvery debtRepository.findById(debtId) returns debt
coEvery installmentPlanRepository.findByDebtId(debtId) returns plan
coEvery
installmentPlanRepository.save(
planDTO.copy(
amount = amount
).toEntity(
id = plan.id,
debtId = debtId
)
)
returns InstallmentPlan(plan.id, debtId, interval, firstPaymentDate, amount )
coEvery installmentRepository.findByDebtId(debtId) returns flowOf(installment1, installment2)
coEvery
installmentRepository.deleteAllById(listOf(installment1.id!!, installment2.id!!).asIterable())
just Runs
coEvery recordService.deleteAll(listOf(installment1.recordId, installment2.recordId)) just Runs
coEvery userService.findById(debt.userId) returns user
coEvery recordService.saveAll(installments) returns records
coEvery
installmentRepository.saveAll(
records.map
Installment(
debtId = debtId,
recordId = it.id!!
)
).map it.recordId
returns flowOf(43)
runBlocking planService.storeInstallmentPlan(debtId, planDTO)
PlanServiceFeatureImpl.kt
@Transactional
override suspend fun storeInstallmentPlan(debtId: Long, installmentPlanDTO: InstallmentPlanDTO): Flow<Long>
val debt = debtRepository.findById(debtId) ?: throw NotFoundException("Could not find debt with id $debtId")
val installmentPlanId = installmentPlanRepository.findByDebtId(debtId)?.id
val minimumAmount =
BigDecimal.ONE.max(debt.amount.multiply(MINIMUM_INSTALLMENT_PERCENTAGE_OF_TOTAL_AMOUNT, MathContext.DECIMAL32))
.setScale(2, RoundingMode.HALF_UP)
.stripTrailingZeros()
val maximumAmount = debt.amount
val clampedAmount = maximumAmount.min(minimumAmount.max(installmentPlanDTO.amount))
val installmentPlan = installmentPlanDTO.copy(
amount = clampedAmount,
).toEntity(
id = installmentPlanId,
debtId = debtId,
)
installmentPlanRepository.save(installmentPlan)
// delete existing records associated with the installment plan
val existingInstallments = installmentRepository.findByDebtId(debtId).toList()
installmentRepository.deleteAllById(existingInstallments.map it.id!! )
recordService.deleteAll(existingInstallments.map it.recordId )
// calculate installments / records
/*
This calculation follows this invariant:
debt.amount = countOfFullInstallments * amount + lastInstallment
*/
val user = userService.findById(debt.userId)
?: throw NotFoundException("Could not find user that owns debt $debtId")
val zoneId = ZoneId.of(user.tz)
val firstPaymentDate = installmentPlan.firstPaymentDate.atZoneSameInstant(zoneId)
val countOfFullInstallments =
debt.amount.divide(
(if (installmentPlan.amount <= BigDecimal.ONE) BigDecimal.ONE else installmentPlan.amount),
MathContext.DECIMAL32
)
.setScale(0, RoundingMode.DOWN)
.intValueExact()
val lastInstallmentAmount = debt.amount - installmentPlan.amount * countOfFullInstallments.toBigDecimal()
val countOfInstallments = countOfFullInstallments + if (lastInstallmentAmount > BigDecimal.ZERO) 1 else 0
val installments = List(countOfInstallments) i ->
val endDate =
addInterval(firstPaymentDate, installmentPlan.interval, i)
val startDate = addInterval(firstPaymentDate, installmentPlan.interval, i - 1)
.plusDays(1)
val recordAmount = if (i < countOfFullInstallments)
installmentPlan.amount
else lastInstallmentAmount
WalletRecord(
userId = debt.userId,
type = RecordType.debt_rate,
amount = recordAmount,
title = debt.title,
category = RecordCategory.debt_rate,
repeat = installmentPlan.interval,
startDate = startDate.toOffsetDateTime(),
endDate = endDate.toOffsetDateTime(),
)
val recordsFlow = recordService.saveAll(installments)
return installmentRepository.saveAll(recordsFlow.map
Installment(
debtId = debtId,
recordId = it.id!!,
)
).map it.recordId
我收到此错误:
no answer found for: InstallmentRepository(installmentRepository#4).saveAll(app.backend.plan.PlanServiceFeatureImpl$storeInstallmentPlan$suspendImpl$$inlined$map$1@31e1ec3)
io.mockk.MockKException: no answer found for: InstallmentRepository(installmentRepository#4).saveAll(app.backend.plan.PlanServiceFeatureImpl$storeInstallmentPlan$suspendImpl$$inlined$map$1@31e1ec3)
这是很多代码,但由于我不知道错误来自哪里,所以我提供了完整的代码。在其他出现错误“找不到答案”的情况下,它为我提供了类似于“[...]InstallmentRepository(...).saveAll([parameters here])”的信息,而不是被测单元的路径。 希望有人可以帮助我。
【问题讨论】:
【参考方案1】:您正在尝试模拟以下调用:
installmentRepository.saveAll(recordsFlow.map
Installment(
debtId = debtId,
recordId = it.id!!,
)
).map it.recordId
但你真正需要模拟的只是saveAll()
,而不是它后面的map()
,如下:
coEvery installmentRepository.saveAll(
records.map
Installment(
debtId = debtId,
recordId = it.id!!
)
)
returns flowOf(Installment(debtId, 43))
如果这不起作用,请尝试以下方法(匹配不太严格):
coEvery installmentRepository.saveAll(ArgumentMatchers.any()) returns flowOf(Installment(debtId, 43))
【讨论】:
感谢您的回答。由于 storeInstallmentPlan 返回 FlowinstallmentRepository.saveAll()
的调用,而不是对您尝试测试的方法的调用。映射已经由storeInstallmentPlan()
:.map it.recordId
中的代码完成。您只需要专注于模拟对存储库的调用。
感谢您的意见和解释。解决了 coEvery installmentRepository.saveAll(any以上是关于MockK没有找到答案:的主要内容,如果未能解决你的问题,请参考以下文章
使用 Kotlin 数据类的 Json 解析器正确返回 json 数据,但是为啥解析器(MockK)的单元测试会失败?
使用 MockK 模拟 Spring Repository 时,类 java.lang.Object 不能强制转换为类 Task