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 返回 Flow 我认为我需要 .map it.recordId 还是我错了? 您错了,因为您在模拟对installmentRepository.saveAll() 的调用,而不是对您尝试测试的方法的调用。映射已经由storeInstallmentPlan():.map it.recordId 中的代码完成。您只需要专注于模拟对存储库的调用。 感谢您的意见和解释。解决了 coEvery installmentRepository.saveAll(any>()) 返回 flowOf() 的问题 太棒了!很高兴我能帮上忙!

以上是关于MockK没有找到答案:的主要内容,如果未能解决你的问题,请参考以下文章

kotlin:模拟注入的单元测试(mockK)

使用 Kotlin 数据类的 Json 解析器正确返回 json 数据,但是为啥解析器(MockK)的单元测试会失败?

单元测试--mockk

使用 MockK 模拟 Spring Repository 时,类 java.lang.Object 不能强制转换为类 Task

安卓Kotlin单元测试/ Collection, ArrayList依赖的解耦/ MockK

使用多平台模拟 kotlin 中的常见测试