12

I'm trying to unit test a class that references static data from another class. I cannot "not" use this static class, but obviously running multiple tests has become problematic. So my question is this. Is there a way in a junit test to reinitialize a static class? That way one test is not effected by a previous test?

So in other words some way of doing this:

Foo.setBar("Hello");

// Somehow reinitialize Foo

String bar = Foo.getBar(); // Gets default value of bar rather than "Hello"

Unfortunately, I cannot change Foo, so I'm stuck using it.

Edit It appears I made my example a bit too simple. In the real code "Bar" is set by a system property and gets set to an internal static variable. So once it starts running, I can't change it.

Jason Thompson
  • 4,643
  • 5
  • 50
  • 74
  • It's not clear what you're asking. Are you looking for a JUnit feature to run code at a specific time (when?), or are you asking about whether it's possible at all to modify the external `Foo` class once initialized? – chrylis -cautiouslyoptimistic- Oct 10 '13 at 17:43
  • You could, of course, modify Foo so that it's mutable, but you're apparently prohibited from that. The only other option is to use private class loaders and reflections to allow the class to be reloaded. But the `Foo.getBar()` calls would have to be reworked. – Hot Licks Oct 10 '13 at 17:45
  • 1
    possible duplicate of [Java: how to "restart" a static class?](http://stackoverflow.com/questions/4631565/java-how-to-restart-a-static-class) – Étienne Miret Oct 10 '13 at 17:45
  • Does `Foo` have any setters on it that can get it back to its initialized state with effort? – Daniel Kaplan Oct 10 '13 at 17:45

7 Answers7

7

Though it was a bit dirty, I resolved this by using reflections. Rather than rerunning the static initializer (which would be nice), I took the fragile approach and created a utility that would set the fields back to known values. Here's a sample on how I would set a static field.

final Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
final Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

field.set(null, value);
Jason Thompson
  • 4,643
  • 5
  • 50
  • 74
4

If you use PowerMock, you can mock static methods -- which is what you should do.

emory
  • 10,725
  • 2
  • 30
  • 58
2

You could use PowerMock (with Mockito) or JMockit to mock the static class to have it do whatever you want in each test.

Kevin M
  • 2,717
  • 4
  • 26
  • 31
2

Three suggestions,

  1. Call the static method from the @Before setting it to some known value.

  2. Use ReflectionTestUtils to set the value via reflection.

  3. Update your code to have a instance wrapper class that wraps the call to the static method in an instance method / class. Mock the wrapper and inject into your class under test.

John B
  • 32,493
  • 6
  • 77
  • 98
2

Here is a little example where a utility class using static initializer is re-loaded to test initialization of that utility. The utility uses a system property to initialize a static final value. Normally this value cannot be changed at runtime. So the jUnit-test reloads the class to re run the static initializer…

The utility:

public class Util {
    private static final String VALUE;

    static {
        String value = System.getProperty("value");

        if (value != null) {
            VALUE = value;
        } else {
            VALUE = "default";
        }
    }

    public static String getValue() {
        return VALUE;
    }
}

The jUnit-test:

import static org.junit.Assert.assertEquals;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.Test;

public class UtilTest {

    private class MyClassLoader extends ClassLoader {

        public Class<?> load() throws IOException {
            InputStream is = MyClassLoader.class.getResourceAsStream("/Util.class");

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = -1;

            while ((b = is.read()) > -1) {
                baos.write(b);
            }

            return super.defineClass("Util", baos.toByteArray(), 0, baos.size());
        }
    }

    @Test
    public void testGetValue() {
        assertEquals("default", getValue());
        System.setProperty("value", "abc");
        assertEquals("abc", getValue());
    }

    private String getValue() {
        try {
            MyClassLoader myClassLoader = new MyClassLoader();
            Class<?> clazz = myClassLoader.load();
            Method method = clazz.getMethod("getValue");
            Object result = method.invoke(clazz);
            return (String) result;
        } catch (IOException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            throw new IllegalStateException("Error at 'getValue': " + e.getLocalizedMessage(), e);
        }
    }
}
André
  • 308
  • 2
  • 17
  • I found this at my office work, had to come back and +1. The sad part is jacoco or intellij coverage runners are not considering these junits when tests are ran as a whole (gradlew test). But if coverage is ran for single class (UtilTest) then it is considered. Not sure why :( – m0hithreddy Jun 12 '21 at 03:06
0

I would use Factory pattern with init and destroy static methods that should take care about all instances.

Something like:

public class FooFactory {

  private static Foo mFoo  = null;

  public static Foo init(){

      if(mFoo == null){
          mFoo = new Foo();
      }
      return mFoo;
  }

  public static void destroy(){
      if(mFoo != null){
          mFoo = null;
      }
  } 
}

So per unitest enough to run:

FooFactory.init();// on start

....

FooFactory.destroy();// on finish
bartonstanley
  • 1,167
  • 12
  • 25
Maxim Shoustin
  • 77,483
  • 27
  • 203
  • 225
0

Technically, it is possible to load the class (together with some other classes that are needed for the test) into its own class loader - you'd have to make sure that the class is not reachable from the root class loader, though, so it would require quite some hacking to do this, and I doubt it is possible in a normal unit test. Then you can drop the classloader and reinitialize it for the next test - each classloader has its own static variables for all classes loaded by it.

Alternatively, do it a bit more heavyweight and fork a new JVM for each test. I've done this before, it works (especially useful to do more complex integration tests that mess with system properties, which cannot easily be mocked otherwise), but it is probably not what you want for unit tests that run for every build...

Of course, these techniques can be also combined (if you don't get the class out of the root classloader) - fork a new JVM with a minimal "driver" on the classpath, that initializes a new classloader with the "normal" classpath for each test to run.

mihi
  • 6,507
  • 1
  • 38
  • 48