Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PowerMock: mock out private static final variable, a concrete example

Tags:

what is the absolute minimal mocking that must be done to pass this test?

code:

class PrivateStaticFinal {     private static final Integer variable = 0;     public static Integer method() { return variable + 1; } } 

test:

@RunWith(PowerMockRunner.class) @PrepareForTest(PrivateStaticFinal.class) class PrivateStaticFinalTest {     @Test     public void testMethod() {         //TODO PrivateStaticFinal.variable = 100         assertEquals(PrivateStaticFinal.method(), 101);     } } 

related: Mock private static final variables in the testing class (no clear answer)

like image 260
sam boosalis Avatar asked Apr 18 '14 21:04

sam boosalis


People also ask

How do you mock a private static method?

For Mockito, there is no direct support to mock private and static methods. In order to test private methods, you will need to refactor the code to change the access to protected (or package) and you will have to avoid static/final methods.

Can we mock final class using Mockito?

You cannot mock a final class with Mockito, as you can't do it by yourself.


1 Answers

Disclaimer: After a lot of hunting around on various threads I have found an answer. It can be done, but the general concensus is that it is not very safe but seeing as how you are doing this ONLY IN UNIT TESTS, I think you accept those risks :)


The answer is not Mocking, since most Mocking does not allow you to hack into a final. The answer is a little more "hacky", where you are actually modifying the private field when Java is calling is core java.lang.reflect.Field and java.lang.reflect.Modifier classes (reflection). Looking at this answer I was able to piece together the rest of your test, without the need for mocking that solves your problem.

The problem with that answer is I was running into NoSuchFieldException when trying to modify the variable. The help for that lay in another post on how to access a field that was private and not public.

Reflection/Field Manipulation Explained:

Since Mocking cannot handle final, instead what we end up doing is hacking into the root of the field itself. When we use the Field manipulations (reflection), we are looking for the specific variable inside of a class/object. Once Java finds it we get the "modifiers" of it, which tell the variable what restrictions/rules it has like final, static, private, public, etc. We find the right variable, and then tell the code that it is accessible which allows us to change these modifiers. Once we have changed the "access" at the root to allow us to manipulate it, we are toggling off the "final" part of it. We then can change the value and set it to whatever we need.

To put it simply, we are modifying the variable to allow us to change its properties, removing the propety for final, and then changing the value since it is no longer final. For more info on this, check out the post where the idea came from.

So step by step we pass in the variable we want to manipulate and...

// Mark the field as public so we can toy with it field.setAccessible(true); // Get the Modifiers for the Fields Field modifiersField = Field.class.getDeclaredField("modifiers");   // Allow us to change the modifiers modifiersField.setAccessible(true);  // Remove final modifier from field by blanking out the bit that says "FINAL" in the Modifiers modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); // Set new value field.set(null, newValue);  

Combining this all into a new SUPER ANSWER you get.

@RunWith(PowerMockRunner.class) @PrepareForTest() class PrivateStaticFinalTest {     @Test     public void testMethod(){       try {         setFinalStatic(PrivateStaticFinal.class.getDeclaredField("variable"), Integer.valueOf(100));       }        catch (SecurityException e) {fail();}       catch (NoSuchFieldException e) {fail();}       catch (Exception e) {fail();}       assertEquals(PrivateStaticFinal.method(), Integer.valueOf(101));     }      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);     } } 

Update The above solution will work only for those constants which is initialized in static block.When declaring and initializing the constant at the same time, it can happen that the compiler inlines it, at which point any change to the original value is ignored.

like image 123
Walls Avatar answered Sep 21 '22 13:09

Walls