Swift Testing: XCTestExpectation相当のことができない

困っています。
何が困るかというと、waitができない点。
これを見ると、Confirmationを使えとあるんですが、出ているサンプルがcompletionHandlerを使う例で、asyncのサンプルがないんですね。それでもって以下のように書いてある。
however, they don’t block or suspend the caller while waiting for a condition to be fulfilled.
asyncの何かが変わるのを待てないんで、どうしたものかなっていう状態です。
テストなんで、スピンロック的なループをしてもいいのかも知れませんが、XCTestでできてることができないのが残念。
できるのかしら。

例えば2秒後に値が42になるというコードがあるとします。
@MainActor
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
@Published var isLoading: Bool = false
func loadCount() {
isLoading = true
Task {
try await Task.sleep(for: .seconds(2))
self.count = 42
self.isLoading = false
}
}
}
表示はこんな感じ。
struct CounterView: View {
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
Text(viewModel.isLoading ? "Loading..." : "Count: \(viewModel.count)")
Button("Load Count") {
viewModel.loadCount()
}
}
}
}
XCTestで、実際にisLoading
がfalse
になったときに値が42になっているかというテストをするなら、以下のような感じになります。
func testLoadCount() async throws {
let viewModel = await CounterViewModel()
var cancellables = Set<AnyCancellable>()
let expectation = XCTestExpectation(description: "Load count finishes")
await viewModel.$isLoading.dropFirst().sink { isLoading in
if !isLoading {
expectation.fulfill()
}
}.store(in: &cancellables)
await viewModel.loadCount()
wait(for: [expectation], timeout: 3.0)
let count = await viewModel.count
XCTAssertEqual(count, 42)
}
wait(for: [exepctation], timeout: 3.0)
の行でexpectation.fulfill()
が実行されるまで待ってくれるんですよね。最大3秒。実際は2秒でisLoading = false
になるので、3秒も待ちませんが。
でもこれが、Swift Testingだと書けません。本当に?
苦し紛れに以下のような感じです。
@Test
func testLoadCount() async throws {
let viewModel = await CounterViewModel()
await viewModel.loadCount()
while true {
let isLoading = await viewModel.isLoading
if !isLoading {
break
}
try! await Task.sleep(for: .milliseconds(10))
}
let count = await viewModel.count
#expect(count == 42)
}
withCheckedContinuation
だと、ハンドラ版のやつは書けてもasyncの奴が書けません。
confirmation()
だと、isLoading = false
になるまで待たずにすぐ返ってきてしまうので必ず失敗します。
(もしくはウェイトとかループを入れるか。だったら上みたいなスピンロック的なので充分ですし)
何かおかしいんじゃないかという気になっていますが、どうなんでしょうか。