Check if app is running unit tests the Swift way
While writing unit tests for function in my framework, I ran into a problem in the following function:
public func pushViewController(
_ viewController: UIViewController,
animated: Bool,
completion: (() -> Void)?
) {
self.pushViewController(viewController, animated: animated)
guard animated, let coordinator = self.transitionCoordinator else {
DispatchQueue.main.async { completion?() }
return
}
coordinator.animate(alongsideTransition: nil) { _ in completion?() }
}
When writing tests that might invoke this function,
with the animated parameter set to bool, in a normal behavior self.transitionCoordinator != nil
when animated: true
,
but since no actual transition is happening (in a unit test), it is nil, and it breaks the completion block.
So I needed a way to set animation to false, even if it was set to true from the outside during unit tests. #if DEBUG
was not an option, obviously.
I did further digging and noticed that if you run Thread.current.threadDictionary
the thread running the XCTest contains the following key: com.apple.dt.xctest.waiter-manager
So I wrote this extension:
extension Thread {
var isRunningXCTest: Bool {
for key in self.threadDictionary.allKeys {
guard let keyAsString = key as? String else {
continue
}
if keyAsString.split(separator: “.”).contains(“xctest”) {
return true
}
}
return false
}
}
I opted to check for xctest alone and not the whole key to future proof it, from any possibile changes to the key.
So now the code looks like this:
public func pushViewController(
_ viewController: UIViewController,
animated: Bool,
completion: (() -> Void)?
) {
if Thread.current.isRunningXCTest {
self.pushViewController(viewController, animated: false)
} else {
self.pushViewController(viewController, animated: animated)
}
guard animated, let coordinator = self.transitionCoordinator else {
DispatchQueue.main.async { completion?() }
return
}
coordinator.animate(alongsideTransition: nil) { _ in completion?() }
}