可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
If you use PowerMock, you can mock static methods -- which is what you should do.
回答2:
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);
回答3:
You could use PowerMock (with Mockito) or JMockit to mock the static class to have it do whatever you want in each test.
回答4:
Three suggestions,
Call the static method from the @Before
setting it to some known value.
Use ReflectionTestUtils
to set the value via reflection.
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.
回答5:
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.
回答6:
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);
}
}
}
回答7:
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