Search code examples
javaunit-testingpowermockito

Mock ClassLoader.getSystemClassLoader().loadClass with Powermockito


I am trying to test utility method which check if particular class is on class path, if yes return true else return false.

Why I am doing this: I have to independent classes extending same class, and only one of it will be on classpath. Need to do specific thing if one particular is on classpath.

Using kind of below method to check if particular class is on class path. This check will be done only once after first request. I'd checked Class.forName() also but decided to go with below approach.

My utility method looks something like this:

public static boolean isMyClassOnClassPath() {
    try {
        ClassLoader.getSystemClassLoader().loadClass("com.MyClass");
        return true;
    } catch (ClassNotFoundException ex) {
        return false;
    }

}

Checking false condition is easy as particular class is not not the ClassPath. I'm trying to write Junit for positive scenario when this method will return true.

@Test
public void isMyClassOnClassPathShouldReturnTrueWhenMyClassIsOnClassPath() throws Exception{
    PowerMockito.mockStatic(MyClass.class);
    ClassLoader classLoader = PowerMockito.mock(ClassLoader.class);
    PowerMockito.mockStatic(ClassLoader.class);
    PowerMockito.when(ClassLoader.getSystemClassLoader()).thenReturn(classLoader);

    //trying to mock classLoader.loadClass, below way is incorrect
    //PowerMockito.when(classLoader.loadClass("com.MyClass")).thenReturn(Class<java.lang.Object.class>);
    Assert.assertTrue(MyClassUtil.isMyClassOnClassPath());
}

So is it possible to mock classLoader.loadClass() method?


Solution

  • Honestly: don't even think about doing something like that.

    In short, you are like a person sitting on a tree that starts cutting random limbs of the tree that person is sitting on. Meaning: this is a central part of the JVM. Assume your mocking would work: then every caller to that method would receive your mocked loader! So, when your test case itself wanted to load some classes, it would run into your mock!

    And as almost usual, when people claim "I need to user Powermock for xyz" your real problem is a different one: you created untestable code. By making that static call there, you prevent yourself from testing your code!

    For starters, you can have a look here to learn how to write testable code. But in case you are curious how you could fix your design:

    class ClassPathChecker {
      private final ClassLoader classLoader;
      ClassPathChecker() { this(ClassLoader.getSystemClassLoader()); }
      ClassPathChecker(ClassLoader classLoader) {
       this.classLoader = this.classLoader);
      }
    
      boolean canClassBeLoaded(String className) {
        try {
          classLoader.loadClass ...
    

    The above uses dependency injection to insert a mocked ClassLoader; which gives you full control over everything that is going on. Without using Powermock at all.

    And out of curiosity: why do you restrict yourself to the System classloader? Wouldn't a simple call like Class.forName("yourclass") tell you the same?