简体   繁体   中英

Using reflection to change static final File.separatorChar for unit testing?

Specifically, I'm trying to create a unit test for a method which requires uses File.separatorChar to build paths on windows and unix. The code must run on both platforms, and yet I get errors with JUnit when I attempt to change this static final field.

Anyone have any idea what's going on?

Field field = java.io.File.class.getDeclaredField( "separatorChar" );
field.setAccessible(true);
field.setChar(java.io.File.class,'/');

When I do this, I get

IllegalAccessException: Can not set static final char field java.io.File.separatorChar to java.lang.Character

Thoughts?

From the documentation for Field.set :

If the underlying field is final, the method throws an IllegalAccessException unless setAccessible(true) has succeeded for this field and this field is non-static .

So at first it seems that you are out of luck, since File.separatorChar is static . Surprisingly, there is a way to get around this: simply make the static field no longer final through reflection.

I adapted this solution from javaspecialist.eu :

static void setFinalStatic(Field field, Object newValue) throws Exception {
    field.setAccessible(true);

    // remove final modifier from field
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    field.set(null, newValue);
}

I've tested it and it works:

setFinalStatic(File.class.getField("separatorChar"), '#');
System.out.println(File.separatorChar); // prints "#"

Do exercise extreme caution with this technique . Devastating consequences aside, the following actually works:

setFinalStatic(Boolean.class.getField("FALSE"), true);
System.out.format("Everything is %s", false); // "Everything is true"

Important update : the above solution does not work in all cases. If the field is made accessible and read through Reflection before it gets reset, an IllegalAccessException is thrown. It fails because the Reflection API creates internal FieldAccessor objects which are cached and reused (see the java.lang.reflect.Field#acquireFieldAccessor(boolean) implementation). Example test code which fails:

Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null);
// call setFinalStatic as before: throws IllegalAccessException

Try invoking on an instance of file not on an instance of class File

Eg

File file = ...;    
field.setChar(file,'/');

You could also try http://code.google.com/p/jmockit/ and mock the static method FileSystem.getFileSystem(). (don't know if you can mock static variables, normally those hacks shouldn't be necessary -> write oo code and use 'only' mockito)

Just use / everywhere when constructing Files. I've been doing that for 13 years and never had a problem. Nothing to test either.

我意识到这并没有直接回答你的问题,但Apache Commons FileNameUtils会做跨平台的文件名构建,并且可以节省你编写自己的类来做到这一点。

Instead of using File.separatorChar declare your service Class, let's call it PathBuilder or something. This class will have a concatPaths() method which will concatenate the two parameters (using the OS's separator char). The beauty is that you are writing this class so you can tweak it anyway you want when you unit test it.

您可以获取java.io.File的源代码,并修改它以使separatorChar和separator不是final,并添加一个更新其中两个的setSeparatorChar方法,然后在您的bootclasspath中包含已编译的类。

here I am going to set value for "android.os.Build.VERSION.RELEASE", where VERSION is the class name and RELEASE is the final static string value.

If the underlying field is final, the method throws an IllegalAccessException so that we need to use setAccessible(true) , NoSuchFieldException needs to be added when you use field.set() method

@RunWith(PowerMockRunner.class)
@PrepareForTest({Build.VERSION.class})
public class RuntimePermissionUtilsTest {
@Test
public void hasStoragePermissions() throws IllegalAccessException, NoSuchFieldException {
    Field field = Build.VERSION.class.getField("RELEASE");
    field.setAccessible(true);
    field.set(null,"Marshmallow");
 }
}

now the value of String RELEASE will return " Marshmallow ".

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM