Expose private API to XCTest

Currently it is impossible to test private API. As workaround we have to expose them as internal so that XCTest can see them. But this breaks the encapsulation and pollutes the autocomplete list. It's been torturing me for a long time. Is it possible for compiler to expose private API only to XCTest(or considering other testing frameworks, in a special testing mode)?

How would you disambiguate duplicated declarations?

You can't, even with @testable. Normally, we don't unit test private methods, but if for some reason you really want to expose it, perhaps you can do something like this:

// FileA.swift in SomeModule

class MyClass {
  private func myPrivateMethod(arg1: Int) { ... } 
}

#if DEBUG
extension MyClass {
    func _myPrivateMethod(arg1: Int) {
        myPrivateMethod(arg1: arg1)
    }
}
#endif

// In test target
@testable import SomeModule
myClass._myPrivateMethod(arg1: 0)

Although in this case I think you're probably better off declaring it as internal...

1 Like

Honestly until we get first class inline unit tests I decided to not use private or fileprivate access modifiers at all. Instead I would communicate that through an underscore prefix. But even when we get the mentioned feature, Swift still lacks of a type private access control (not visible to subtypes though). That said, I‘m fine with internal, public and open, even though I have an issue with open since Swift 3 era.

Why would you want to test private methods in the first place?

This is extracted form a Martin Fowler's article (The Practical Test Pyramid):

If you ever find yourself in a situation where you really really need to test a private method you should take a step back and ask yourself why.
I'm pretty sure this is more of a design problem than a scoping problem. Most likely you feel the need to test a private method because it's complex and testing this method through the public interface of the class requires a lot of awkward setup.
Whenever I find myself in this situation I usually come to the conclusion that the class I'm testing is already too complex. It's doing too much and violates the single responsibility principle - the S of the five SOLID principles.
The solution that often works for me is to split the original class into two classes. It often only takes one or two minutes of thinking to find a good way to cut the one big class into two smaller classes with individual responsibility. I move the private method (that I urgently want to test) to the new class and let the old class call the new method. Voilà, my awkward-to-test private method is now public and can be tested easily. On top of that I have improved the structure of my code by adhering to the single responsibility principle.

I hope it helps.

2 Likes

One option is to make the method fileprivate, subclass it (perhaps guarding the subclass to be compiled in DEBUG or TEST only mode), and then in the subclass encapsulate it in another internal method that XCTest can access.