Better Programming

Advice for programmers.

Follow publication

Testing MVVM in Swift and Kotlin

Eric Silverberg
Better Programming
Published in
4 min readDec 10, 2020
Man writing on paper
Photo by Jason Coudriet on Unsplash.

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).

How many hours have I spent watching this screen?

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.

Diagram of testing approach
Image credit: Author

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:

Example flow of dating app
Example flow through a Match feature. Photo by the author.

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

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.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Eric Silverberg
Eric Silverberg

Written by Eric Silverberg

CEO, Perry Street Software. Developer. 🏳️‍🌈

Write a response