nalexn / ViewInspector

Runtime introspection and unit testing of SwiftUI views
MIT License
2.18k stars 150 forks source link

Is it possible to test .navigationDestination(for:) when using NavigationStack with path? #341

Open litvakle opened 2 weeks ago

litvakle commented 2 weeks ago

I'm trying to reach the navigation destination view in my tests, but it's not accessible.

import SwiftUI

@main
struct NavigationStackTestingApp: App {
    @StateObject var router = Router()
    var body: some Scene {
        WindowGroup {
            ContentView(router: router)
        }
    }
}
final class Router: ObservableObject {
    @Published var path = [RouterView]()

    enum RouterView: Hashable {
        case step1
        case step2
        case step3
    }
}
struct ContentView: View {
    @ObservedObject var router: Router

    var body: some View {
        NavigationStack(path: $router.path) {
            VStack {
                Text("Intro")
            }
            .accessibilityIdentifier("NAVIGATION_STACK_CONTENT")
            .navigationDestination(for: Router.RouterView.self) { view in
                switch view {
                case .step1: Text("Step1").accessibilityIdentifier("NAVIGATION_DESTINATION_VIEW")
                case .step2: Text("Step2")
                case .step3: Text("Step3")
                }
            }
        }
        .accessibilityIdentifier("NAVIGATION_STACK")
    }
}
@testable import NavigationStackTesting
import ViewInspector
import XCTest

final class ContentViewTests: XCTestCase {
    func test_navigationStack_isAccessible() throws {
        let sut = ContentView(router: Router())

        XCTAssertNoThrow(try sut.inspect().find(viewWithAccessibilityIdentifier: "NAVIGATION_STACK"))
    }

    func test_navigationStackContent_isAccessible() throws {
        let sut = ContentView(router: Router())

        XCTAssertNoThrow(try sut.inspect().find(viewWithAccessibilityIdentifier: "NAVIGATION_STACK_CONTENT"))
    }

    func test_navigationDestinationView_isAccessible() throws {
        let router = Router()
        let sut = ContentView(router: router)

        router.path.append(.step1)

        XCTAssertNoThrow(try sut.inspect().find(viewWithAccessibilityIdentifier: "NAVIGATION_DESTINATION_VIEW"))
    }
}

The first two tests are passing. The last one (test_navigationDestinationView_isAccessible) - is not. I get this error: "test_navigationDestinationView_isAccessible(): XCTAssertNoThrow failed: threw error "Search did not find a match".

Please let me know if I'm missing something.

greenerchen commented 5 days ago

Hi @litvakle, Pushing a view into a NavigationStack is asynchronous. Give async inspection a try. I used the approach #2 to test navigationDestination(isPresented:destination:) with NavigationStack, and it worked.