【发布时间】:2021-09-22 03:50:40
【问题描述】:
我正在构建一个抵押贷款计算器作为学习Combine 的练习。一切都在顺利进行,直到我遇到一种情况,当我对它进行单元测试时,我没有从我的Publishers 之一获得确定性的已发布输出。我没有进行任何异步调用。这是有问题的AnyPublisher:
public lazy var monthlyPayment: AnyPublisher<Double, Never> = {
Publishers.CombineLatest3(financedAmount, monthlyRate, numberOfPayments)
.print("montlyPayment", to: nil)
.map { financedAmount, monthlyRate, numberOfPayments in
let numerator = monthlyRate * pow((1 + monthlyRate), Double(numberOfPayments))
let denominator = pow((1 + monthlyRate), Double(numberOfPayments)) - 1
return financedAmount * (numerator / denominator)
}
.eraseToAnyPublisher()
}()
假设我将抵押贷款类型从 30 年更改为 15 年,会发生一些事情:
-
numberOfPayments因抵押期限(长度)的变化而变化 -
monthlyRate由于抵押期限(长度)的变化
最终目标
我的最终目标是等待financedAmount、monthlyRate 和numberOfPayments 出版商完成他们的工作,当他们都完成后,然后计算每月付款。 Merge 3 似乎会在每个发布者中获取更改,并且对于每个更改,它都会计算并吐出我不想要的输出。
Repo with problematic class and associated unit tests
我的尝试
我尝试过使用MergeMany、Merge3、.collect(),但我无法正确使用语法。我已经用谷歌搜索了这个鼻涕,并在公共 GitHub 存储库中寻找示例,但我没有想出与我的情况密切相关的任何内容。我正试图弄清楚我在搞砸什么以及如何解决它。
支持声明
这些是我对 monthlyPayment 所依赖的其他发布者的声明:
@Published var principalAmount: Double
@Published var mortgageTerm: MortgageTerm = .thirtyYear
@Published var downPaymentAmount: Double = 0.0
// monthlyRate replies upon annualRate, so I'm including annualRate above
internal lazy var monthlyRate: AnyPublisher<Double, Never> = {
annualRate
.print("monthlyRate", to: nil)
.map { rate in
rate / 12
}
.eraseToAnyPublisher()
}()
public lazy var annualRate: AnyPublisher<Double, Never> = {
$mortgageTerm
.print("annualRate", to: nil)
.map { value -> Double in
switch value {
case .tenYear:
return self.rates.tenYearFix
case .fifteenYear:
return self.rates.fifteenYearFix
case .twentyYear:
return self.rates.twentyYearFix
case .thirtyYear:
return self.rates.thirtyYearFix
}
}
.map { $0 * 0.01 }
.eraseToAnyPublisher()
}()
public lazy var financedAmount: AnyPublisher<Double, Never> = {
Publishers.CombineLatest($principalAmount, $downPaymentAmount)
.map { principal, downPayment in
principal - downPayment
}
.eraseToAnyPublisher()
}()
public lazy var numberOfPayments: AnyPublisher<Double, Never> = {
$mortgageTerm
.print("numberOfPayments: ", to: nil)
.map {
Double($0.rawValue * 12)
}
.eraseToAnyPublisher()
}()
更新
我尝试将Merge3 与.collect() 一起使用,但我的单元测试超时了。这是更新后的monthlyPayment 声明:
public lazy var monthlyPayment: AnyPublisher<Double, Never> = {
Publishers.Merge3(financedAmount, monthlyRate, numberOfPayments)
.collect()
.map { mergedArgs in
let numerator = mergedArgs[1] * pow((1 + mergedArgs[1]), mergedArgs[2])
let denominator = pow((1 + mergedArgs[1]), mergedArgs[2]) - 1
return mergedArgs[0] * (numerator / denominator)
}
.eraseToAnyPublisher()
}()
测试现在因超时而失败,并且永远不会调用 .sink 代码:
func testMonthlyPayment() {
// sut is initialized w/ principalAmount of $100,000 & downPaymentAmount of $20,000
let sut = calculator
let expectation = expectation(description: #function)
let expectedPayments = [339.62, 433.97, 542.46]
sut.monthlyPayment
.collect(3)
.sink { actualMonthlyPayment in
XCTAssertEqual(actualMonthlyPayment.map { $0.roundTo(places: 2) }, expectedPayments)
expectation.fulfill()
}
.store(in: &subscriptions)
// Initialized with 30 year fix with 20% down
// Change term to 20 years
sut.mortgageType = .twentyYear
// Change the financedAmount
sut.downPaymentAmount.value = 0.0
waitForExpectations(timeout: 5, handler: nil)
}
【问题讨论】:
-
我没有看到单元测试。让我们确保您知道如何对 Combine 进行单元测试;这不是微不足道的。 “我没有进行任何异步调用”错误。关于 Combine 的一切都是异步的,只有异步测试才能测试它。因此我的怀疑。您可能会发现我的 stackoverflow.com/questions/66036397/… 教育...
-
你也可以试试
zip()。 -
@matt 谢谢。我会检查你的 cmets。
-
@Cristik 谢谢。你会把
zip()放在哪里? -
问题是你正在组合的三个信号都在它们自己的“时间轴”上。您需要的是一个表明“该值已准备好计算”的信号。所有源值是否每次都更改?然后你需要一个发布者来计算有多少值发生了变化。但是,如果只有一个或两个值可能发生变化......那么您将需要一些其他信号来表示“好的......这些值已经稳定,现在可以计算每月付款。