Injecting and Mocking static frameworks in Swift

Cyril Le Pottier
Oct 23 · 5 min read

Unit testing is all about testing individual units of code in an isolated context. It relies on mocking and injecting the dependencies of units of code. It’s quite simple given the right code architecture (see Single Responsibility and Dependency Inversion principles for more), however it can become slightly harder when your dependencies are third-party frameworks. This is especially true in Swift where many of them are static, final or can be instantiated only under very specific conditions. They are unfriendly to unit testing because they can’t easily be injected nor mocked. Fortunately, and this is the subject of this article, there is a technique to inject and mock such frameworks in Swift.

In this article, I will first take some times to explain our unit testing philosophy, here at OpenClassrooms, because we really value it. If you just want to read code, no worries, you can jump straight onto the second part where I will describe the technique to inject and mock static frameworks.

Our unit testing philosophy

At OpenClassrooms, we strongly believe that everything but views and controllers should be unit tested. Therefore, one on the very first tasks of the iOS project was to add a basic yet powerful rule to our Continuous Integration workflow: you can’t merge if the code coverage is not 100%.

Although I agree that not every single line of code is worth being tested, I still value the 100% code coverage rule as it guarantees that a decent amount of unit tests have been written for any given change. This rule is one of the reasons we can confidently release a new version of the OpenClassrooms app every two weeks.

This is all perfect! Or almost… The iOS community doesn’t strongly support unit testing as much as we would like. We regularly use frameworks that don’t support unit testing… They are a problem to us since they create untestable code that we have to, pardon my French, ignore from the code coverage calculation… 😨 Fortunately and thanks to some key swift features, we rarely have to take such action! The code that uses static, final or other singleton based frameworks can indeed be united tested.

Testing code based on static dependencies

Let’s focus on the most common of the unfriendly frameworks: the static ones. It generally comes with this kind of classes:

class IsThirteenStaticLib {    static func isThirteen(_ value: Any?) -> Bool}

And you generally want to use it your services:

 protocol ThirteenValidator {    func validate(_ value: Any?) -> Bool}class ThirteenValidatorStaticLibImpl: ThirteenValidator {    func validate(_ value: Any?) -> Bool {        return IsThirteenStaticLib.isThirteen(value)    }}

The above service can unfortunately not be unit tested… Let’s fix that.

Injecting static classes

Our first step is injection. The tricky part is that we have to inject a class, and not an instance of it.

The good news is that static classes can be injected and this is all thanks to the Swift metatype type. This is a very interesting type and a lot can be said about it but I will try to keep it short and simple. Metatype is a type, like any type, it can be declared as a variable which will then refer to it. This is exactly what we need to inject our static class, so let’s use it!

We will upgrade our untestable service with the power of the metatype type. We will use the .Type keyword in order to declare a variable that will refer to the metatype type of the static class. We will then use the variable to access the static method, like if it was an instance method.

class ThirteenValidatorStaticLibImpl: ThirteenValidator {    private var isThirteenLib: IsThirteenStaticLib.Type!    func validate(_ value: Any?) -> Bool {        return self.isThirteenLib.isThirteen(value)
// previously returned IsThirteenStaticLib.isThirteen(value)
} func setIsThirteenLib(_ lib: IsThirteenStaticLib.Type) { self.isThirteenLib = lib }}

We will now use a second keyword, .self, in order to inject the metatype value to our service.

let validator = ThirteenValidatorStaticLibImpl()
validator.setIsThirteenLib(IsThirteenStaticLib.self)

And here we are already! We injected our static framework.

Mocking static classes

Our second and last step is to mock our static class, it’s straightforward.

class IsThirteenStaticLibMock: IsThirteenStaticLib {    override static func isThirteen(_ value: Any?) -> Bool {      ❌        // Mocked code    }}

The compiler reminds us that we cannot override static method, great, this article would lost a lot of its purpose if we could. We can fortunately workaround this constraint by creating a protocol that will act as an overridable bridge.

protocol IsThirteenStaticLibBridge {    static func isThirteen(_ value: Any?) -> Bool}

We can now come back to our mock that couldn’t override static methods. Instead of subclassing the static lib, it will now implement the bridge protocol including the static method.

class IsThirteenStaticLibMock: IsThirteenStaticLibBridge {    static func isThirteen(_ value: Any?) -> Bool {        // Mocked code    }}

It looks good, doesn’t it? But we are not quite done here since our mock doesn’t have the same type than our static class, we cannot inject it to our service.

Easy fix, we will update again our now-almost-testable service to use the bridge we created earlier instead of the static class.

class ThirteenValidatorStaticLibImpl: ThirteenValidator {    private var isThirteenLib: IsThirteenStaticLibBridge.Type!    func validate(_ value: Any?) -> Bool {        return self.isThirteenLib.isThirteen(value)   }   func setIsThirteenLib(_ lib: IsThirteenStaticLibBridge.Type) {        self.isThirteenLib = lib    }}

Thanks to this change we can now inject our mock in our service 😎 But because of this change we can no longer inject our static class in our service, oops… 😤 No worries, our library naturally complies to our bridge, we just have to let the Swift compiler know.

extension IsThirteenStaticLib: IsThirteenStaticLibBridge { }

Note that this is important for the methods of the bridge to be exactly the same that the static class so the latter can naturally comply to the former. It if wasn’t the case, untestable mapping code would have to be written, which is ultimately moving the original problem elsewhere.

Wrap-up

The code now compiles. There is an obvious last step, someone has to write tests to bring back that code coverage to 100%! We won’t do that here, the scope of the article was to bring the code back to a testable state, which it did!

I would like to point out that the exact same technique works for classes that:

I created an example code available on GitHub for the purpose of this article. It contains the code shown here with some extras: a mocking of a final class and of UIApplication.open(url:).

For the ones that want a “next step”, here is a challenge: Inject and mock Data.write(toFile:options:). Data is not overridable, it’s a struct, and it applies the write function on itself (not on an argument) which is next level trouble… A little secret, in the OpenClassrooms codebase, it is ignored from code coverage calculation. 🤫

Tech@OpenClassrooms

Tech@OpenClassrooms

Cyril Le Pottier

Written by

Lead iOS developer at OpenClassrooms

Tech@OpenClassrooms

Tech@OpenClassrooms

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade