ViewInspector
ViewInspector copied to clipboard
Help testing update by async func
I wrote a View and test that is updated by an asynchronous function in SwiftUI. But the test failed. I would like to know how I should write tests in this case.
View
import SwiftUI
struct ContentView: View {
internal var didAppear: ((Self) -> Void)?
@State var count = 0
var body: some View {
NavigationView {
VStack {
Text(count.description)
.tag("count")
}
.onAppear { self.didAppear?(self) }
.navigationTitle("TestApp")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing){
Button {
Task {
await self.update()
}
} label: {
Label("update", systemImage: "arrow.clockwise")
}
.tag("updateButton")
}
}
}
}
func update() async {
try? await Task.sleep(until: .now + .seconds(1), clock: .continuous)
self.count += 3
}
}
Test
import XCTest
import ViewInspector
@testable import TestApp
extension ContentView: Inspectable {}
final class TestAppTests: XCTestCase {
func testSample() throws {
let startCount = "0"
let incCount = "3"
var sut = ContentView()
let exp = sut.on(\.didAppear) { view in
var count = try view.find(viewWithTag: "count").text().string()
XCTAssertEqual(count, startCount)
try view.find(viewWithTag: "updateButton").button().tap()
count = try view.find(viewWithTag: "count").text().string()
XCTAssertEqual(count, incCount) // testSample(): XCTAssertEqual failed: ("0") is not equal to ("3")
}
ViewHosting.host(view: sut)
wait(for: [exp], timeout: 2.0)
}
}
@atsu0127 you just have to wait until it's set.
final class TestAppTests: XCTestCase {
@MainActor
func testSample() throws {
let startCount = "0"
let incCount = "3"
var sut = ContentView()
let e = expectation(description: "for Task")
let exp = sut.on(\.didAppear) { view in
var count = try view.find(viewWithTag: "count").text().string()
XCTAssertEqual(count, startCount)
Task {
try view.find(viewWithTag: "updateButton").button().tap()
try await Task.sleep(until: .now + .seconds(1), clock: .continuous)
count = try view.find(viewWithTag: "count").text().string()
XCTAssertEqual(count, incCount) // 🆗
e.fulfill()
}
}
ViewHosting.host(view: sut)
wait(for: [e, exp], timeout: 2.0)
}
}
@nh7a Waiting for a fixed amount of time for async to finish is not optimal.
@atsu0127 I suggest you to try my approach where I track each body evaluation by index, and it allows me to write async/await tests: https://github.com/sisoje/testable-view-swiftui
I use a different production code setup and view hosting though.