0

I have an app which uses ServiceLoader.
There is one service interface MyFancyService and its implementations: AppClassA, AppClassB and test implementations: TestClassC, TestClassD

There is META-INF.services in the app package and another META-INF.services in test package.

META-INF.services in app point to provider classes AppClassA and AppClassB
META-INF.services in test point to provider classes TestClassC and TestClassD

While running the app, ServiceLoader.load(MyFancyService.class)) loads only AppClassA, AppClassB. Obviously there are no test classes in the classpath and that's why. I understand that and it's desirable

While running tests, ServiceLoader.load(MyFancyService.java)) loads all the classes AppClassA, AppClassB, TestClassC, TestClassD.

Is there a way to limit service loader to only load TestClassC, TestClassD while running tests ?

Sachith Muhandiram
  • 2,819
  • 10
  • 45
  • 94
  • Usually test files test the application classes, what will you test without the application? – Ori Marko Nov 14 '19 at 10:34
  • Update: After writing an answer I found this thread: https://stackoverflow.com/questions/28805688/in-java-how-can-i-mock-a-service-loaded-using-serviceloader – Mark Bramnik Nov 14 '19 at 10:48

1 Answers1

0

When the test runs it has both "test" and "production" classes/resources in the classpath. That's why all four services are loaded and this is a desired behaviour, you obviously need both test and prod classpath.

So, bottom line, you have two options:

Option 1

Use tools like PowerMock/PowerMockito that would mock static method ServiceLoader.load. I haven't tried this by myself, but in general unless its some sort of legacy code, I don't reacommend to use this.

Option 2

Refactor the code and mock the call to service loader as follows:

inteface MyServiceLoader {
  void loadMyStuff(Class<?> type);
}

class MyServiceLoaderImpl implements MyServiceLoader {
   public void loadMyStuff(Class<?> type() {
       ServiceLoader.load(type);
   }
}


// and now in ClassUnderTest:

public class ClassUnderTest {

    private MyServiceLoader myServiceLoader;

    public void thisIsAMethodToTest() {
      ...
      myServiceLoader.loadMyStuff(MyFancyInterface.class); 
    }
}

In test you can mock the interface MyServiceLoader.loadMyStuff with mockito and load only test implementations.

Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
  • That's a very good answer but my ServiceLoader.load is hidden behind a custom annotation and executed by static method in one Injector class which resolves annotations. Hard to mock in that case. Maybe I over engineered that a bit. Maybe it's not that harmful to have these app classes being loaded in tests even if there's going to be a huge load of them. I wanted to achieve something like that in the end: @ServiceLoad(MyFancyInterface::class) private lateinit var Iterable< MyFancyInterface >: – Piotr Sobczyński Nov 14 '19 at 11:45
  • ServiceLoader is a very "low-level" mechanism and doesn't provide such a level of customization... – Mark Bramnik Nov 14 '19 at 12:03