I've got two UnitTest projects for my Android project. One for the JUnit Test and one for the Android Unit Tests. In the JUnit Test Project I've made a class to access or set private fields, methods or constructors. (PS: For the ones that are curious of the complete code, let me know and I'll add it to the bottom of this post.)
I also have UnitTests to test these private-method accessing. Right now all of these UnitTests work, accept for one: Setting the value of a final static field.
This is the method I use for setting a private field:
// Test method to set a private Field from a class
public static void setPrivateField(Object ob, String fieldName, Object value) throws MyUnitTestException{
try {
Field field = ob.getClass().getDeclaredField(fieldName);
if(field != null){
field.setAccessible(true);
if(Modifier.isFinal(field.getModifiers())){
Field modifierField = Field.class.getDeclaredField("modifiers");
modifierField.setAccessible(true);
modifierField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
/*int modifiers = field.getModifiers();
Field modifierField = field.getClass().getDeclaredField("modifiers");
modifiers = modifiers & ~Modifier.FINAL;
modifierField.setAccessible(true);
modifierField.setInt(field, modifiers);*/
}
// ** IllegalAccessException at the following line with final static fields:
field.set(ob, value); // static fields ignore the given Object-parameter
}
}
catch (NoSuchFieldException ex){
throw new MyUnitTestException(ex);
}
catch (IllegalAccessException ex){
throw new MyUnitTestException(ex);
}
catch (IllegalArgumentException ex){
throw new MyUnitTestException(ex);
}
}
And this is the UnitTest:
@Test
public void testSetIntFields(){
MyClass myClassInstance = new MyClass();
final int value = 5;
for(int nr = 1; nr <= 4; nr++){
String nameOfField = "myInt" + nr;
try {
TestMethodsClass.setPrivateField(myClassInstance, nameOfField, value);
}
catch (MyUnitTestException ex) {
Assert.fail("setPrivateField caused an Exception: " + ex.getThrownException());
}
int x = myClassInstance.getMyInt(nr);
Assert.assertTrue("myInt " + nr + " should be above 0", x > 0);
Assert.assertEquals("myInt " + nr + " should equal the set value (" + value + ")", value, x);
}
}
With the following MyClass:
@SuppressWarnings("unused")
public class MyClass
{
private int myInt1 = 0;
private static int myInt2 = 0;
private final int myInt3 = 0;
private static final int myInt4 = 0;
public MyClass(){ }
public int getInt(int nr){
switch(nr){
case 1:
return myInt1;
case 2:
return myInt2;
case 3:
return myInt3;
case 4:
return myInt4;
}
return -1;
}
}
(And the following MyUnitTestException):
public class MyUnitTestException extends Exception
{
private static final long serialVersionUID = 1L;
private Throwable thrownException;
public MyUnitTestException(Throwable ex){
super(ex);
thrownException = ex;
}
public String getThrownException(){
if(thrownException != null)
return thrownException.getClass().getName();
else
return null;
}
}
Setting the value to the fields myInt1
, myInt2
and myInt3
works, but at myInt4
I'm getting an IllegalAccessException.
Does anyone know how I should fix this in my setPrivateField method? So it can not only set private
, private static
and private final
fields, but also private static final
ones.
EDIT 1:
After reading this article Forbidden Java actions: updating final and static final fields about in-lining at RunTime, I modified my UnitTest to this:
@Test
public void testSetIntFields(){
MyClass myClassInstance = new MyClass();
final int value = 5;
for(int nr = 1; nr <= 4; nr++){
String nameOfField = "myInt" + nr;
try {
TestMethodsClass.setPrivateField(myClassInstance, nameOfField, value);
}
catch (MyUnitTestException ex) {
Assert.fail("setPrivateField caused an Exception: " + ex.getThrownException());
}
// Get the set value using reflection
// WARNING: Since at RunTime in-lining occurs, we never use a Getter to test the set value, but instead use reflection again
int x = -1;
try {
x = (Integer)TestMethodsClass.getPrivateField(myClassInstance, nameOfField);
}
catch (MyUnitTestException ex) {
Assert.fail("getPrivateField caused an Exception: " + ex.getThrownException());
}
Assert.assertTrue("myInt " + nr + " should be above 0", x > 0);
Assert.assertEquals("myInt " + nr + " should equal the set value (" + value + ")", value, x);
}
}
(And this is my getPrivateField method, which is already completely tested and works):
// Test method to access a private Field from a class
public static Object getPrivateField(Object ob, String fieldName) throws MyUnitTestException{
Object returnObject = null;
try {
Field field = ob.getClass().getDeclaredField(fieldName);
if(field != null){
field.setAccessible(true);
returnObject = field.get(ob); // static fields ignore the given Object-parameter
}
}
catch (NoSuchFieldException ex) {
throw new MyUnitTestException(ex);
}
catch (IllegalAccessException ex) {
throw new MyUnitTestException(ex);
}
catch (IllegalArgumentException ex) {
throw new MyUnitTestException(ex);
}
return returnObject;
}
But I still get the same error.
EDIT 2:
Because I was using a getPrivateField in a UnitTest above it and tested all my UnitTests at the same time, it didn't worked. When I tested the UnitTest above separately it did work.. So I removed the getPrivateField-UnitTests (since in the code above I use both the Set and Get in one test) and now it does work.
I know this is very bad practice for UnitTests, but changing a private static final field during RunTime is already bad practice anyway. I just made the class to get and set private fields, methods and constructors, because I needed it about 3-4 times in some of my UnitTests and then I was just curious how far you can go with reflection and created a TestCase for everything I could think of. (Personally I find it a bit too far though.)
WARNING: Do not use reflection in any other case than tests. I don't recommend using it in your normal project, unless you've tried every other possible way. (I can't even think of a situation where you'd want to use reflection in your project, apart from tests.)
A primitive static final field is treated specially.
It is inlined as a constant by the compiler. The final executable does not access the field at runtime anymore.