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.
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.