我在测试组合时遇到困难。我正在关注:
哪些测试:
final class ViewModel {
@Published private(set) var tokens = [String]()
@Published var string = ""
private let tokenizer = Tokenizer()
init () {
$string
.flatMap(tokenizer.tokenize)
.replaceError(with: [])
.assign(to: &$tokens)
}
}
struct Tokenizer {
func tokenize(_ string: String) -> AnyPublisher<[String], Error> {
let strs = string.components(separatedBy: " ")
return Just(strs)
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
}
具有以下内容:
func testTokenizingMultipleStrings() throws {
let viewModel = ViewModel()
let tokenPublisher = viewModel.$tokens
.dropFirst()
.collect(2)
.first()
viewModel.string = "Hello @john"
viewModel.string = "Check out #swift"
let tokenArrays = try awaitPublisher(tokenPublisher)
XCTAssertEqual(tokenArrays.count, 2)
XCTAssertEqual(tokenArrays.first, ["Hello", "john"])
XCTAssertEqual(tokenArrays.last, ["Check out", "swift"])
}
以及以下辅助函数:
extension XCTestCase {
func awaitPublisher<T: Publisher>(
_ publisher: T,
timeout: TimeInterval = 10,
file: StaticString = #file,
line: UInt = #line
) throws -> T.Output {
var result: Result<T.Output, Error>?
let expectation = self.expectation(description: "Awaiting publisher")
let cancellable = publisher.sink(
receiveCompletion: { completion in
switch completion {
case .failure(let error):
result = .failure(error)
case .finished:
break
}
expectation.fulfill()
},
receiveValue: { value in
result = .success(value)
}
)
waitForExpectations(timeout: timeout)
cancellable.cancel()
let unwrappedResult = try XCTUnwrap(
result,
"Awaited publisher did not produce any output",
file: file,
line: line
)
return try unwrappedResult.get()
}
}
此处
receiveValue
从未被调用,因此测试未完成。
我怎样才能通过这个测试?
我遇到了同样的问题,最终意识到为了通过测试,我们需要在设置订阅和等待期望之间更新视图模型。由于这两种情况当前都发生在 awaitPublisher
帮助器内部,因此我向该函数添加了一个闭包参数:
func awaitPublisher<T: Publisher>(
_ publisher: T,
timeout: TimeInterval = 10,
file: StaticString = #file,
line: UInt = #line,
closure: () -> Void
) throws -> T.Output {
...
let expectation = ...
let cancellation = ...
closure()
waitForExpectations(timeout: timeout)
...
}
注意闭包的确切位置——调用太早或太晚都不会起作用。
然后您可以在测试中调用助手,如下所示:
let tokenArrays = try awaitPublisher(publisher) {
viewModel.string = "Hello @john"
viewModel.string = "Check out #swift"
}
您的
tokenPublisher
在您订阅之前不会执行任何操作。在此代码中,您创建发布者,执行一些操作,
would已通过发布者推送值if有人订阅了它,然后您调用
awaitPublisher
(执行订阅的事情)。你需要扭转这些: let viewModel = ViewModel()
let tokenPublisher = viewModel.$tokens
.dropFirst()
.collect(2)
.first()
let tokenArrays = try awaitPublisher(tokenPublisher)
viewModel.string = "Hello @john"
viewModel.string = "Check out #swift"
,它优雅地解决了我们的痛点。 这位作者救了我一天。 这是我的示例测试函数。
import Combine
import TestableCombinePublishers
import XCTest
func testCollect3Values() {
let values = [0, 1, 2]
let intValue = CurrentValueSubject<Int, Never>(-1)
intValue.send(values[0])
let test = intValue
.collect(values.count) // 3
.expect(values) // [0, 1, 2]
.expectNoCompletion()
intValue.send(values[1])
intValue.send(values[2])
test.waitForExpectations(timeout: 1)
}