In the last post, we wrote our first SwiftUI test in pseudocode. We knew what we wanted to test — that the initial displayed count is zero — but we didn’t yet have a way to make it real.
Before we can turn that pseudocode into real code, we need an essential tool: ViewInspector. This is what makes it possible to write unit tests for SwiftUI views.
Why ViewInspector?
We already know we want to test what the UI does, not how it’s laid out. It would be great if we could write tests for SwiftUI like we can already do with UIKit:
- Invoke actions, like tapping buttons
- Query state that determines changes in appearance
(Testing UIKit is the focus of my book, iOS Unit Testing by Example.)
But SwiftUI resists these kinds of tests. Views don’t expose their internal structure. We can’t reach in to tap a button or read a text label. UIKit is imperative, while SwiftUI is declarative. SwiftUI views are value types that get replaced as things change. So what do we do?
That’s where ViewInspector comes in. It gives us a way to inspect SwiftUI views from our tests — not just examine state, but also trigger actions.
ViewInspector is the missing link that enables TDD with SwiftUI.
What ViewInspector Can Do
When I teach UIKit testing, we start by simulating a button tap, then verify its side effect. ViewInspector gives us similar tools for SwiftUI. It lets us:
- Find specific subviews inside larger views (for example, a particular button or text).
- Read view state, such as text values, fonts, or colors.
- Tap buttons to trigger actions.
- Even pull out the underlying SwiftUI view to access its view model.
You can see a quick summary of these features under "Use cases" on ViewInspector’s GitHub page.
A Mental Model of Inspection
To interact with any system or tool, it’s helpful to build a mental model of how it works. Let’s start with the classic SwiftUI “Hello, world!” example:
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}Normally, we can’t touch any of those subviews from a test. But with ViewInspector, we can say:
let sut = ContentView()
let inspectableView = try sut.inspect()
Now we have an inspectable view that mirrors the structure of the original view. Inside that, we can dig down like this:

The actual type names are different, but that’s the idea. With ViewInspector, we are working in a copy of the original view, where the copy supports testability.
Adding ViewInspector to Your Project
To install ViewInspector, add https://github.com/nalexn/ViewInspector as a Swift Package dependency. Be sure to add it to your test target — not the main app.
In my CounterApp demo app, we can take our first pseudocode test — “initial count is zero” — and turn it into an executable test.
That’s where we’ll pick up next.
Coming soon:
I’ll share more about why I trust ViewInspector — including what happened with a SwiftUI update broke everyone’s tests, and how the community fixed it.
Be sure to check out the entire TDD with SwiftUI series.
Subscribe to follow along as we continue to implement our first test.

