Testing the untestable
Nicolas Fränkel

Nicolas Fränkel @nfrankel

About: Dev Advocate | Developer & architect | Love learning and passing on what I learned!

Location:
Geneva
Joined:
Apr 7, 2020

Testing the untestable

Publish Date: Oct 23 '25
18 3

I'm currently working on a software designed more than a decade ago. It offers a plugin architecture: you can develop a plugin whose lifecycle is handled by the software. The tough part, though, is how you access the platform capabilities: via static methods on singletons.

@Override
public boolean start() {
    var aService = AService.getInstance();
    var anotherService = AnotherService.getInstance();
    // Do something with the services
    var result = ...;
    return result;
}
Enter fullscreen mode Exit fullscreen mode

There's no easy way to test the start() method. In the old days, Mockito developers had pushed back against this feature, and the only alternative was PowerMock. The decision was reversed in 2020 with the 3.4.0 release, which introduced static method mocking in Mockito.

I liked the previous situation better. My opinion is that having to mock static methods is a sign of badly designed code. I wrote about it already ten years ago. With PowerMock, one could mock the static methods, write the test, redesign the code, and then remove PowerMock. With the current situation, one can't look at the dependencies to search for design smells. In any case, the above problem still stands, and I can't change the design. It's forced upon me.

The solution is strangely straightforward, though. Just write a wrapper method around the one:

@VisibleForTesting                                                 //1
boolean start(AService aService, AnotherService anotherService) {  //2
    // Do something with the services
    var result = ...;
    return result;
}

@Override
public boolean start() {
    var aService = AService.getInstance();
    var anotherService = AnotherService.getInstance();
    return start(aService, anotherService);                        //3
}
Enter fullscreen mode Exit fullscreen mode
  1. Method is normally private, but since we want to test it, we make it package visible. The @VisibleForTesting annotation is for documentation purposes.
  2. The testable method has parameters that can be mocked
  3. Call the testable method

In this post, I showed how one can test legacy code not built on Dependency Injection.
This is a pretty straightforward way to test untestable code.


Originally published at A Java Geek on October 19th, 2025

Comments 3 total

  • david duymelinck
    david duymelinckOct 23, 2025

    Doesn't that go against the advise to test private methods through public method behavior?
    Wouldn't it be better to have an overloaded start method that accepts the services, and test that?
    Then you have created an upgrade path for the poorly designed code.
    For me @VisibleForTesting seems like half of the way to better code.

    • Nicolas Fränkel
      Nicolas FränkelOct 24, 2025

      There's no private method to test, but a public untestable one at the unit level. I don't understand your comment.

      • david duymelinck
        david duymelinckOct 25, 2025

        The @VisibleForTesting start is package-visible, meaning it is not public. By adding the annotation it is public to tests. Don't you think that is not how it should work?

        If you make @VisibleForTesting start just an overloaded public method, you achieved three things:

        • Test a method that is part of the package API
        • Prepared the library/project for a better design
        • Moved the code from the bad method to the good one.

        With the annotation you only did one thing on that list, the last one. And made tests awkward, because in the tests you can't see the @VisibleForTesting annotation.

Add comment