Swift Testingで値を監視したい
と書いたが、別にXCTestでも、テストでなくてもなんでも良い。
値を非同期で監視し続ける場合に値が更新されたのを待って、テストを走らせたいということがある。
例えば以下のようなViewModelがあったとして、personに値が入ってからテストケースを実行したいとする。
SwiftTesting内にこの値を監視するメソッドがないわけではないが使いにくかったので自作した。
@MainActor
@Observable
final class ViewModel {
var person: Person?
init() {
observe()
}
func observe() {
Task { [weak self] in
let stream: AsyncStream<Person> = ...
for await value in stream {
guard let self else { return }
person = value
}
}
}
}
ちなみにTaskを用いる場合weak selfは必要ないとよく言われるが、AsyncStreamの場合は必須である(別にweak selfでなくても良いがこのTaskを抜ける処理がないと永遠とメモリ上に存在してしまう)。
自作したメソッドは以下
func observe(content: @Sendable @escaping () async -> Bool, maxAttemtpts: Int = 10) async throws {
let timer = AsyncStream { try? await Task.sleep(for: .seconds(0.5)) }
var count: Int = 0
for await value in timer {
if await content() { return }
count += 1
if count >= maxAttemtpts { throw TestObserverError() }
}
}
struct TestObserverError: Error {}
これをテスト内で以下のように用いる
import Testing
@Test("Test")
func test() async throws {
let state = ViewModel()
try await observe() { await state.person != nil }
... いろいろなテスト
}
以上