If you find yourself with broken unit tests, failing to build with the error, “Use of unresolved identifier…”, you’re not alone!
Unit Tests and Swift Access Control
When Swift access control came into the picture, we suddenly had a little more to consider. From the Apple docs:
Swift access control has three access levels:
- private entities can only be accessed from within the source file where they are defined.
- internal entities can be accessed anywhere within the target where they are defined.
- public entities can be accessed from anywhere within the target and from any other context
that imports the current target’s module.
By default, most entities in a source file have internal access.
So given the following…
- Out of the box, your unit tests are part of a separate test target
- The default access control for a class is internal, (meaning that if you do not explicitly specify an access control on the class / properties / functions, they’re marked internal behind the scenes)
… we now know why the unit tests break, unless we make a few tweaks: classes marked internal are only seen within a set of specified targets and our unit tests are in a separate target that our class is not a part of by default.
It seems to me that we have two options:
- Change the access control on our class to public. Additionally, mark any methods we intend to test with public also.
- Add the class(es) you want to be able to write unit tests for to the tests target.
I found option #2 to be the easiest to implement at first. However, it turns out that this can lead to some really obscure issues. An enlightening Twitter conversation also shed some light on the subject, and pointed to the solution of testing only publicly accessible behavior that your Types expose, rather than trying to test internal implementation. That probably deserves a blog entry of its own, but for now, I’ll leave it to say that I’d recommend not adding your .swift source files to your test target, but rather to adjust the access control modifiers of the things you want to test to public (ie, Option # 1).