Basic unit-tests for SwiftUI view with ViewInspector

While using SwiftUI for building UIs, sooner or later we would like to write some unit-tests as well. Of course, we could always go for UI-tests, but these are much slower and therefore not so scalable if we would just want to have a basic verification for our view. It is easy to get going with writing unit-tests for UIKit code, but it is much more difficult for SwiftUI views. Currently, there seems to be two main ways: snapshot testing using the pointfreeco’s library or inspecting views with ViewInspector. Today, we are not going to compare these libraries and instead just take a look at how to get going with ViewInspector.

Although ViewInspector supports using bindings etc for updating the view while running the unit-test, I personally feel like these kinds of tests where we interact with the view is probably better for UI-tests. Therefore, in this blog post, we just take a look at a basic SwiftUI view and how to inspect it in a unit-tests.

Here we have a basic SwiftUI view which uses a view model to provide data for the view. The style property is driving the title of the view. A pretty simple example to demonstrate the usage of the ViewInspector library.

struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text(viewModel.title)
.font(.title)
}
.padding()
}
}
extension ContentView {
final class ViewModel: ObservableObject {
@Published var style: Style = .hello
var title: String {
switch style {
case .hello: return "Hello World!"
case .welcome: return "Welcome World!"
}
}
}
enum Style {
case hello, welcome
}
}

Time to add two unit-tests where we first configure the view model and then verify. It is also possible to write a test where we dynamically change the style property, but that requires some extra code for supporting it. The first step after adding the library and only adding to the unit-testing target is to opt-in the custom view for inspection. Without the ContentView extension the library is not able to inspect any views. In the example below, we are just using the find method for looking for a Text with specific string, which depends on the style property. I feel like this library is really nice for these kinds of unit-tests where we create a view and just verify what it is displaying.

@testable import SwiftUIExampleViewInspector
import SwiftUI
import ViewInspector
import XCTest
// Do not forget this!
extension ContentView: Inspectable {}
final class ContentViewTests: XCTestCase {
func testInitialTitle() throws {
let contentView = ContentView()
let text = try contentView.inspect()
.find(text: "Hello World!")
let font = try text.attributes().font()
XCTAssertEqual(font, Font.title)
}
func testWelcomeTitle() throws {
let viewModel = ContentView.ViewModel()
viewModel.style = .welcome
let contentView = ContentView(viewModel: viewModel)
_ = try contentView.inspect().find(text: "Welcome World!")
}
}

In summary, I think that ViewInspector is a really nice library for unit-testing SwiftUI views. Since it requires more work to support reacting to view updates dynamically I feel like, at least for now, I am going to use it for static views and use UI-tests instead for tests simulating user interaction.

If this was helpful, please let me know on Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.