0

This question is related to Kotlin: Make an internal function visible for unit tests, but this question deals with how to make internal functions unavailable to code that is not unit tests.

Suppose I have some methods that I want to use for unit testing, but I want to prevent them from being used in production code. How can I make it as difficult as possible to use the function in production code without resorting to any reflection tricks (such as modifying the visibility of a function) that could be blocked by a SecurityManager?

Essentially, can I make something like @VisibleForTesting, but with some enforcement behind it? Or emulate the encapsulation abilities of Rust unit tests which can be embedded directly in the module they are testing, thus allowing the tests to access items in that module without requiring those items to be visible outside the module?

cafce25
  • 15,907
  • 4
  • 25
  • 31
Matthew Pope
  • 7,212
  • 1
  • 28
  • 49

1 Answers1

0

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?)

Matthew Pope
  • 7,212
  • 1
  • 28
  • 49