How to write the Unit Test for static functions?

Recently I came across a requirement where we need to integrate App Tracking Transparency (ATT for short) which is a new feature of iOS, iPadOS and for the tvOS 14.5 that requires applications to ask for permission if they track user activity across other companies apps (where they access the unique adverting identifiers).

So we did it but since we love TDD and we try to make sure whatever new feature/changes we are doing should have unit test.

If you see the Apple documentation here it says requestTrackingAuthorization(completionHandler:) is the function to request for user authorisation to access app-related data.

class func requestTrackingAuthorization(completionHandler completion: @escaping (ATTrackingManager.AuthorizationStatus) -> Void)

Which is a class function with the completion handler, So how will you test it?

Give a pause and think if you have an answer for it already ( Previously I used to integrate some 3rd party library to mock the static func).

OK let’s see the trick.

  1. Create a protocol with the same signature of the static func we want to test.
protocol PrivacyManagerBridge {
    static func requestLocation(completionHandler completion: @escaping (Bool) -> Void)
}

2. Let the ATTrackingManager to conform the protocol

extension ATTrackingManager: PrivacyManagerBridge {}

3. Create a new protocol with the same signature but it’s a instance func, create a class which conform the protocol ( let’s say the class responsibility is to handle the privacy with in the app) so I gave the name AppPrivacyManager

protocol AppPrivacyManagerContract {
    func requestTrackingAuthorization(completionHandler completion: @escaping (ATTrackingManager.AuthorizationStatus) -> Void)
}

class AppPrivacyManager: AppPrivacyManagerContract {
    private var trackable: PrivacyManagerBridge.Type!

    init(trackable: PrivacyManagerBridge.Type = ATTrackingManager.self) {
        self.trackable = trackable
    }

    func requestTrackingAuthorization(completionHandler completion: @escaping (ATTrackingManager.AuthorizationStatus) -> Void) {
        self.trackable.requestTrackingAuthorization(completionHandler: completion)
    }
}

If you see when we initialise this class, it takes a parameter which is PrivacyManagerBridge.Type and that’s the trick and now when it comes to test this piece of code we can create a mock by conforming the PrivacyManagerBridge

class ATTrackingManagerMock: PrivacyManagerBridge {
    static func requestTrackingAuthorization(completionHandler completion: @escaping (ATTrackingManager.AuthorizationStatus) -> Void) {
        // Return whichever flow you want to test.
        completion(.denied)
    }
}

Let’s write the test.

let privacyManager = AppPrivacyManager(trackable: ATTrackingManagerMock.self)
privacyManager.requestTrackingAuthorization { result in
    // result here
}

That’s it, Thanks for reading.


Here is the Github Gist.
https://gist.github.com/buntylm/9d878d0219dd307667f51bbb71158576