Kotlin — Unit Testing Classes Without Leaking Public API!
By: Stanislav Kozlovski
Tags: JVM, Java, Kotlin, Unit Testing, Git

Introduction

Kotlin is an amazing new up-and-coming language. It’s very actively developed and has a ton of features that make it very appealing.

It’s been steadily gaining market share ever since Google added Android Development support for it (back in 2017) and made it the preferred language for such development exactly one year ago (May 2019)

For any Java developer coming into the language, spoiler alert, there is one big surprise that awaits you — the package-private visibility modifier is missing. It doesn’t exist.

Access Modifiers

An access modifier is the way to set the accessibility of a class/method/variable in object-oriented languages. It’s instrumental in facilitating proper encapsulation of components.

Java

In Java, we have four access modifiers for any methods inside a class:

void doSomething() - the default modifier, we call this package-private . Any such declarations are only visible within the same package (hence the name, private for the package)

- the default modifier, we call this . Any such declarations are only visible within the same (hence the name, private for the package) private void doSomething() — the private modifier, declarations of which are visible only within the same class

— the private modifier, declarations of which are visible only within the same class protected void doSomething() — declarations only visible within the package or all subclasses.

— declarations only visible within the package or all subclasses. public void doSomething() — declarations are visible everywhere.

This wide choice of options allows us to tightly-control what our class exposes. It also gives us greater control over what constitutes a unit of testable code and what isn’t. See the following crude example:

JSomethingDoer, doing things since 2020. Source: https://github.com/stanislavkozlovski/unit-test-kt/blob/master/src/main/java/doer/something/JSomethingDoer.java

We can only directly test three of the five methods above. Because doSecondLittleThing() and feelingAmbitious() are private, our unit tests literally cannot call those methods, hence we cannot test them directly.

The only way to test them is through the package-private method maybeDoSecondLittleThing() , which can be called in our unit tests because they’re in the same package as the class they’re testing.

The package-private modifier comes very useful in this example, because it allows us to test the maybeDoSecondLittleThing() method directly!

Testing would be more cumbersome if we had to test all code paths of the maybeDoSecondLittleThing() method by calling the public doSomething() method.

The package-private modifier also ensures that we don’t leak any such internal methods to packages outside of this one.

Notice the different package.

Kotlin

Kotlin is a bit weirder in this regard. It also has four modifiers:

private fun doSomething() — vanilla private modifier

— vanilla private modifier protected fun doSomething() — the same as Java’s protected — only visible within this class and subclasses.

— the same as Java’s protected — only visible within this class and subclasses. public fun doSomething() — visible everywhere

These three are pretty standard. I want to focus on the fourth one:

internal fun doSomething() — Internal declarations are visible anywhere inside the same module.

This is as good as public inside the same module, in that sense. Many small projects have no need for multiple modules, but they’re likely to have multiple packages.

There is basically no way to limit access to a Kotlin method to only be within the same package.

What does this mean? It means that we either need to make our methods private, thus making our testing life harder, or make them public, thus exposing unnecessary API to components in the same module.

Our same example from above, again in the doer.something package:

But this time, from the doer package, we can call every method:

That’s unfortunate. We don’t want to have a leaky API just because we want to have better tests.

How do we solve it?

Solution 1 — Refactor!

Us refactoring our code. Photo by Oleksandr Baiev on Unsplash

It seems to be commonly believed that we shouldn’t test methods that are/should be private. The idea being that:

all private methods are reachable by the public methods

the public methods expose the interface/contract of a class

private methods are implementation details, and we want to test the class’ functionality, not implementation

While that makes sense in theory, reality is not as black-and-white.

Regardless, you should treat any need to test non-public methods as a code smell.

Re-evaluate your design and rethink whether it makes sense to refactor the logic around such that you get the same test coverage by testing public interface contracts, rather than implementation details.

In some cases, though, it is cleaner and more maintainable to get full test coverage by testing each tiny private method code thoroughly, rather than testing each conditional branch via public methods or creating unnecessary boilerplate (e.g additional classes).

Solution 2 — Reflection

Photo by Simon Zhu on Unsplash

An alternative solution is to keep the methods private and use the language’s reflection features to call into said private methods.