I am super new to Swift and SwiftUI and I have started a new project using SwiftUI. I have some experience in other component based libraries for the web and I wanted a way to use the same pattern for iOS development.
Is there a way to ui test individual components in SwiftUI? For example, I have created a Map component that accepts coordinates and renders a map and I want to test this map individually by making the app immediately render the component. Here is my code and test code at the moment:
// App.swift (main)
// Map is not rendered yet
@main
struct PicksApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// MyMap.swift
struct MyMap: View {
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(
latitude: 25.7617,
longitude: 80.1918
),
span: MKCoordinateSpan(
latitudeDelta: 10,
longitudeDelta: 10
)
)
var body: some View {
Map(coordinateRegion: $region)
}
}
struct MyMap_Previews: PreviewProvider {
static var previews: some View {
MyMap()
}
}
// MyMapUITests.swift
class MyMapUITests: XCTestCase {
func testMapExists() throws {
let app = XCUIApplication()
app.launch()
let map = app.maps.element
XCTAssert(map.exists, "Map does not exist")
}
}
Is it possible to tell UI Test framework to only test one component instead of launching the entire app and making me navigate between each view before I am able to get to my view?
For example, in my case, there is going to be a login view when the app opens for the first time (which is every time from perspective of ui testing) and the map view can be located inside the app somewhere. I want to be able to test only the map view without testing end-to-end user experience.
One approach you could take is to have a list view view builders, and use it to set the app entry point if some environment variable is found. You can then inject the environment variable from your UI tests:
@main
struct MyApp: App {
#if DEBUG
// allowing this only in Debug builds
static let viewBuilders: [String: () -> AnyView] = [
"MainView": { AnyView(ContentView()) },
"MyMap": { AnyView(MyMap()) }]
#endif
var body: some Scene {
WindowGroup {
#if DEBUG
if let viewName = ProcessInfo().customUITestedView,
let viewBuilder = Self.viewBuilders[viewName] {
viewBuilder()
} else {
AnyView(ContentView())
}
#else
ContentView()
#endif
}
}
}
#if DEBUG
extension ProcessInfo {
var customUITestedView: String? {
guard environment["MyUITestsCustomView"] == "true" else { return nil }
return environment["MyCustomViewName"]
}
}
#endif
With the above changes, the UI test needs only two more lines of code - the environment preparation:
func testMapExists() throws {
let app = XCUIApplication()
app.launchEnvironment["MyUITestsCustomView"] = "true"
app.launchEnvironment["MyCustomViewName"] = "MyMap"
app.launch()
let map = app.maps.element
XCTAssert(map.exists, "Map does not exist")
}
#endif