Unit test SwiftUI views without a view model

Introduction

The most common architecture pattern in SwiftUI is MVVM. This is a great pattern, but it can be a bit overkill for simple views. In this post, we’re going to talk about how to unit test SwiftUI views without a view model. This is a great way to keep your code clean and simple, and it’s also a great way to write unit tests for your views.

Reasoning

Not every view needs a view model. I know this might be a controversial statement, but hear me out. If your view is simple enough, you can just use the view itself as the model. This is especially true for views that are just displaying data and don’t have any complex logic. In these cases, you can just use the view itself as the model and write your tests against it.

So if you’ve been writing a view with small logic, but you still want to make sure that your logic is fully covered, repeatable, and predictable, this is my way of doing it.

Static methods

This is the most obvious way to do it. You can just write a static method in your view and call it from your tests. This is a great way to keep your code clean and simple, and it’s also a great way to write unit tests for your views. It also helps with forcing you to have pure functions, which is something I personally love.

Here’s an example of how to do this:

The View

struct ContentView: View {
  @State private var someString = "Hello, world!"
    var body: some View {
        VStack {
            Text(someString)
          Button("reverse string") {
            someString =  Self.reverseString(someString)
          }
        }
        .padding()
    }

  static func reverseString(_ string: String) -> String {
    String(string.reversed())
  }
}

As you can see in this very important method that’s clearly carrying the business logic of the app, we have a static method that reverses a string. This is a pure function, and it doesn’t depend on any state. This is a great way to write unit tests for your views, and it’s also a great way to keep your code clean and simple.

The Test

import Testing
@testable import SwiftUI_unit_testing

struct ContentViewTests {

@Test
  func testStringBeingReversed() {
    let baseString = "abc"
    let expectedString = "cba"
    let reversedString = ContentView.reverseString(baseString)
    #expect(reversedString == expectedString)
  }

}

Simple enough, isn’t it? All we need to do is call the static method and check if the result is what we expect. This is a great way to write unit tests for your views, and it’s also a great way to keep your code clean and simple. There isn’t even a need to instantiate the view!

Pitfalls

Like everything else in life, there are upsides and downsides to this approach. Here are a few things to keep in mind:

Conclusion

In this short post, we’ve discussed how to unit test SwiftUI views without a view model. As you can see, like anything else, it’s a decision and you should be mindful when you make it. Sometimes, a view model is just not needed. Other times, it absolutely is, if you want to test side effects of methods, different states, and so on. This should just be yet another tool in your belt. Don’t fall into the tribalism of “MVVM is the only way” or “MV is the only way.” Use the right tool for the job, and you’ll be fine.