How to Test Private Variables in Swift

Let's take a look at how we can use Swift's Mirror class to allow access to a class's private properties from our testing target.

How to Test Private Variables in Swift
Photo by Christina @ wocintechchat.com / Unsplash

I learned something pretty useful at work and thought it might be of interest to others.

We've all been in a situation where we're writing a unit test and we want to inspect a private property's value, but we don't want to change its declared access level. I'll show you how we can use Mirror to inspect the property's value once the view is finished loading.

Mirror allows us to describe the parts that make up a particular instance, such as the instance’s stored properties, collection or tuple elements, or its active enumeration case.

In simple terms, Mirror provides the stored properties of a class, regardless of the access level.

In Action

Let's look at an example:

If we had a class like this, under normal circumstances we wouldn't be able to access any of the private properties.

class ViewControllerToTest: UIViewController {
    @IBOutlet private var titleLabel: UILabel!
    @IBOutlet private var headerImageView: UIImageView!
    
    private var secretAgentNames: [String]?
}

But, let me show you how we can use Mirror to change that without modifying the access level.

Let's create a MirrorObject class. This class will accept Any as input and provides a series of convenience methods for retrieving a particular property by name.

In essence, whenever extract() is called, it will attempt to find a variable that matches the same name and returns it while honoring the type of the generic parameter.

class MirrorObject {
    let mirror: Mirror

    init(reflecting: Any) {
        mirror = Mirror(reflecting: reflecting)
    }

    func extract<T>(variableName: StaticString = #function) -> T? {
        extract(variableName: variableName, mirror: mirror)
    }

    private func extract<T>(variableName: StaticString, mirror: Mirror?) -> T? {
        guard let mirror = mirror else {
            return nil
        }
			
        guard let descendant = mirror.descendant("\(variableName)") as? T else {
            return extract(variableName: variableName, mirror: mirror.superclassMirror)
        }

        return descendant
    }
}

Now, in our testing target:

final class ViewControllerToTestMirror: MirrorObject {
    init(viewController: UIViewController) {
        super.init(reflecting: viewController)
    }
    // List all private properties you wish to test using SAME NAME.
    var headerImageView: UIImageView? {
        extract()
    }
    
    var titleLabel: UILabel? {
        extract()
    }
    
    var secretAgentNames: [String]? {
        extract()
    }
}

// Create a mirror object
let viewControllerMirror = ViewControllerToTestMirror(viewController: ViewControllerToTest())

// Access the private properties in your unit tests
viewControllerMirror.headerImageView
viewControllerMirror.titleLabel
viewControllerMirror.secretAgentNames

Now, we have read-access to all of our private properties and are free to write any tests we like.


Hope you enjoyed this article. If you're interested in more articles about iOS Development & Swift, sign up for the newsletter below or follow me on Twitter.

Subscribe to Aryaman Sharda

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe