Dependency injection with property wrappers
Feel the relief of one less third-party library — tested.
Today we’ll go over how to use dependency injection with Swift 5.1 property wrappers. This approach is particularly interesting because its clean and very lightweight since it doesn’t require any third-party library.
When using property wrappers, we need to declare the wrappedValue
. In the initializer we are resolving the dependency which will bring us the instance that was stored in the container for that type.
The container holds a private static variable which will be used on static functions such as resolve and register and it also holds a private dictionary for the dependencies. As you can see below, the dictionary has its key type as String
because after registering dependencies we want to be able to retrieve them using their type as the key.
An example of a dependency is a coordinator. (Kudos to Soroush Khanlou)
With the precondition we created at line 31 from the container, we explicit tell the other developers that we need to register the dependency before initializing anything that holds a reference to it. We can register our coordinator at didFinishLaunchingWithOptions
on AppDelegate
but as our codebase expands we’ll probably want to put those somewhere else.
Testing
This coordinator doesn’t have any dependencies, so when we test it there’s no need to inject anything:
Now, when testing our screen which has the coordinator dependency we want to register a double coordinator that conforms to DashboardCoordinating
protocol prior to the screen init. At this point we can either override the registered dependency or disable AppDelegate as main.
Override
The container that holds the registered dependencies is a dictionary, so it can override the instance by using the same key (which is just the same protocol). In another words, if we register an instance of DashboardCoordinator
as DashboardCoordinating
and later on register an instance of DashboardCoordinatorDouble
as DashboardCoordinating
, the last one will override the first one.
Skip AppDelegate
There’s a trick that we can do in order to skip AppDelegate
from our tests.
- Remove
@UIApplicationMain
from yourAppDelegate
- Create a main.swift file
- Import
UIKit
and check for ‘existance’ ofXCTestCase
class. This is basically checking if its running on a test target or not. - Write the actual main.
Now that we skipped AppDelegate
when running a test target, we don’t have the real dependency registered which is fine because we’ll register the double coordinator before initializing the screen we want to test and the cool thing of this trick is exactly that — we must make sure we’re using the proper dependencies on our tests.