单元测试组合
我在测试组合时遇到困难。我正在关注:
哪些测试:
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
,因此测试没有完成。 我怎样才能通过这个测试?
I'm having difficulties testing Combine. I'm following:
Which tests:
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()
}
}
with the following:
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"])
}
And the following helper function:
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()
}
}
Here receiveValue
is never called so the test doesn't complete.
How can I get this test to pass?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我遇到了同样的问题,最终意识到为了通过测试,我们需要在设置订阅和等待期望之间更新视图模型。由于这两种情况当前都发生在
awaitPublisher
帮助程序内,因此我向该函数添加了一个闭包参数:注意闭包的确切位置 - 如果调用得太早或太晚,它将不起作用。
然后,您可以在测试中调用助手,如下所示:
I ran into the same issue and eventually realized that for the test to pass, we need to update the view-model between setting up the subscription and waiting for the expectation. Since both currently happen inside the
awaitPublisher
helper, I added a closure parameter to that function:Note the exact position of the closure – it won’t work if it’s called too early or too late.
You can then call the helper in your test like so:
在您订阅之前,您的
tokenPublisher
不会执行任何操作。在此代码中,您创建发布者,执行一些操作将通过发布者推送值如果有人订阅了它,然后您调用awaitPublisher
(进行订阅的事情)。你需要扭转这些:Your
tokenPublisher
won't do anything until you subscribe to it. In this code you create the publisher, do some actions that would have pushed values through the Publisher if anyone was subscribed to it, then you callawaitPublisher
(the thing that does the subscription). You need to reverse those:不幸的是,我没有发现上述回复中的答案有帮助。后来,我发现了这个Swift Package,它优雅地解决了我们的痛点。
这位作者救了我一天。
这是我的示例测试函数。
I unfortunately didn't find the answers from the responses above helpful. Later, I discovered this Swift Package, and it elegantly addressed our pain points.
This author save me a day.
Here are my example test function.