You can do this by leveraging the @RequiresOptIn
annotation. One of its intended uses is for "Internal declarations that should not be used outside the declaring library, but are public for technical reasons", though in this case, the declaration should be private
, but it is internal
for technical reasons.
First, you create an annotation with an obnoxiously long, specific name.
@RequiresOptIn(message = "This part of the API is visible only for testing.")
internal annotation class VisibleForTestingOnly_DoNotUseInProductionCode
Then, you annotate your test-only functions with that annotation so that whenever you try to use this function without explicitly opting in to using test-only code, you will get a compile-time error.
class Foo {
fun bar() {
// ...
}
@VisibleForTestingOnly_DoNotUseInProductionCode
internal fun leakSomeImplementationDetails(): LeakyInternalState {
// ...
}
}
Finally, in your tests that should use this function, add the opt-in annotation to the relevant file/class/function.
@OptIn(VisibleForTestingOnly_DoNotUseInProductionCode::class)
class FooTest {
// ...
}
It is still possible for someone to use this in production code, but the effort is now much higher because you must explicitly opt in using a class name that is telling you not to do this, and your code reviewer (you do have code reviewers, right?) will easily spot an inappropriate use of this annotation if it occurs in production code.
...but what about a module-wide opt-in?
Unfortunately, this option is still an available to you as a footgun, but adding a module-wide opt-in for org.mylibrary.VisibleForTestingOnly_DoNotUseInProductionCode
will still look pretty suspicious to any code reviewers. (You still have those code reviewers we talked about earlier, right?)