Image for post

Photo by ThisisEngineering RAEng on Unsplash.

One of the hardest things with testing your Swift code is getting started. The blank canvas of a fresh app can be intimidating and it’s easy to postpone writing your tests until you’ve finished writing some code.

“I’ll get to it when I’ve written some code so I know what the architecture of the app is going to look like. This will make it easier for me to add tests later.”

This often leads to a situation where you’ve written plenty of code and suddenly it becomes even harder to start writing tests because, well, your code has essentially become untestable. This isn’t because you’re a bad coder — the code was simply not written with testability in mind.

In this article, we will see how depencency injection can help you architect your apps up front and solve some of your testing issues. It will even help you refactor later on.


Introduction to Dependency Injection

The main idea to take away from dependency injection (DI) is that it is a technique in which a certain object (e.g. a view controller) can receive an object it depends on (e.g. a view model) through some sort of configuration method or property. The great thing about this is that it is not tied to any specific software architecture (MVC or MVVM) and does not require any third-party framework to apply it (even though such frameworks do exist). It is actually quite a basic design pattern, but it gives you great flexibility.

Typically, there are three ways you can inject a dependency:

  1. Through a custom initializer — A great option if you have full control of the initialization of the object (custom classes, structs, etc.), but not an option if you don’t (e.g. view controllers).
  2. Through a property — Simply create a publicly accessible property that allows you to set the dependency. Nice and simple, but not if you do not want the dependency to be accessible to others.
  3. Through a configuration method — Allows you to set the dependency easily and also allows you to hide the dependency from the outside world. Great if you want to make sure other pieces of code do not access your dependencies directly.

Each is more or less equal but offers slightly different (dis)advantages. We’ll explore DI using some sample code that injects a web service into a view model.

Without dependency injection

Assume we’re building a view model that needs to fetch a list of book authors to display in our app. The code could look something like this:

Even though we’ve made a nice separation between our view model and the actual service that fetches the authors, there is an issue here: We cannot test our view model in isolation because it calls the service and there’s no way to stop that from happening. Ideally, we would like to test the view model and the service separately. If we were to test the view model as-is, we would be testing too much and would have a hard time in the future if we wanted to refactor or replace our service.

With dependency injection

We’ll solve this problem by adding the option to inject the service and change it so it can easily be swapped to a different service (for testing purposes). Since we have full control over this custom class, we’ll choose the initializer-based approach. The result looks like this:

#programming #mobile-development #ios #swift #xcode

Better Swift Testing With Dependency Injection
1.10 GEEK