Testing MVVM in Swift and Kotlin
The best approach to writing tests is far from obvious
In the early days of mobile, client testing primarily revolved around UI testing. Apple shipped a technology called UIAutomation, which it ended up deprecating completely in 2016 with the release of Xcode 8. The Android testing samples repository did not get created until 2014.
Because of the top-down approach required by UI testing, tests will break if you significantly redesign your UI. Furthermore, UI testing does not by itself handle the various logical states that arise from dependent systems, such as network failure responses. Confirming that a button changes color when tapped is frequently less valuable than confirming the app responds correctly when receiving a 500 response from a server. UI testing also requires an emulator in which to run, which often takes tens of seconds to start on a laptop and even longer when run over the network (such as with services like AWS Device Farm).

Though Android eventually released Espresso and Apple released XCTest, our approach to testing in the Clean MVVM architecture deprioritizes UI testing, focusing instead on testing at the ViewModel layer down.
In general, we write tests first for Repositories, then for ViewModels, and if necessary (because the ViewModel layer failed to exercise all conditions), the Logic layer.

By focusing testing first on the ViewModel, it is possible to run tests very quickly without relying on the emulator. On Android, we have achieved this by using local unit tests that run on your machine’s local Java Virtual Machine and have no Android framework dependencies. On iOS, we are in the process of moving our ViewModel, Logic, and Repository classes into Swift Packages.
Dependency Injection for the Win
Whether or not you do BDD or TDD testing, you are going to want to consider using Dependency Injection for your tests. If you don’t, then at the start of every test, you are going to need to call a method (maybe named something like buildContext
) that will effectively construct all the necessary ViewModel, Logic, and Repository classes, thereby acting as a lightweight DI system.
Here is a basic use of a DI system:
And here is how we would set up tests at the start of a test file:
What About Mocks?
One of the key benefits of using the Clean MVVM architecture is that if you have followed the pattern, you should only ever have to mock outer-layer Clean components — all APIs, and optionally local storage classes.
Mocking on Android is straightforward with mockito. Here is how you can define a mock API in Koin, inject it, and then override your mock at the start of a test:
Swift is one of the only modern languages to lack a mocking library. Consequently, you will need to define concrete classes for each API class and expose a var
that can be assigned directly to control the response.
Better Structure With BDD
Mobile apps can be thought of as a series of state transitions through which a user navigates. For example, envision the matching features of a dating app:

Once your application reaches a given degree of complexity, there can be a significant initial setup in a test to reach the state that you are trying to evaluate. To clean up our tests and reduce redundant code, we can write BDD tests that make use of code nesting to reuse initialization and setup blocks. We can use JUnit in Java and Quick in Swift to get domain-specific languages that will help us write shorter, BDD-style tests.
Here is what it might look like in Quick if we wanted to test that our instruction cards were shown at the start of a match stack:
Here are the same tests structured in BDD using Junit:
Next Up
Let’s talk about how we test asynchronicity in Swift and Kotlin.
More in This Series
- Clean MVVM in Swift & Kotlin
- ViewModels in Swift & Kotlin
- Logic Classes in Swift & Kotlin
- Repositories and Domain Models in Swift & Kotlin
- API classes in Swift & Kotlin
- Views in Swift & Kotlin
- Testing MVVM in Swift & Kotlin ← you are here
- Testing Asynchronicity in Swift & Kotlin
- Clean MVVM Summary
Other series you might like
Clean API Architecture (2021)
Classes, execution patterns, and abstractions when building a modern API endpoint.
Android Activity Lifecycle considered harmful (2021)
Android process death, unexplainable NullPointerExceptions, and the MVVM lifecycle you need right now
About the author
Eric Silverberg is CEO of Perry Street Software, publisher of the LGBTQ+ dating apps SCRUFF and Jack’d, with more than 20M members worldwide.