The summary of PowerMock
PowerMock website
Mockito alone is not enough to write unit tests. Because Mockito cannot mock private methods, final methods, static methods, etc.
PowerMock is a framework designed to extend other mock frameworks such as Mockito and EasyMock. It uses a custom classloader to manipulate bytecodes beyond Mockito’s inability to mock static methods, constructors, final classes, final methods, and private methods.
Still, it sounds awesome. But a lot of times you can do without it. There is an easy way to replace this. For example, you can increase the visibility of those member variables, member methods, and then annotate that variable or method with @visibleForTesting. Yeah, it’s that simple. The Android source code does the same. You can see many such comments in the source code.
However, if you want to mock static methods or private methods from a third-party library, use PowerMock tearfully.
PowerMock integration
1. Add dependencies under build.gradle of app
testImplementation "Org. Powermock: powermock - core: 2.0.2"
testImplementation "Org. Powermock: powermock - module - junit 4:2.0.2"// Used with mockitotestImplementation "Org. Powermock: powermock - API - mockito2:2.0.2"
Copy the code
Note: When using PowerMock as a testing supplement to Mockito, the version of PowerMock should correspond to the version of Mockito being used.
Using PowerMock with Mockito
The use of PowerMock
1. The mock variables
PowerMock can mock variables. It’s not dark technology, it’s actually modifying variables through Java reflection. PowerMock simply encapsulates Java reflection and provides an API for us to use. Corresponding methods, within the WhiteBox class, are listed below as two commonly used methods:
Reflection modifies the value of a variable. When changing the value of a static variable, pass the corresponding Class as the first parameter. public static voidsetInternalState(Object Object, String fieldName, Object Value) // Reflection gets the value of a variable. To get the value of a static variable, pass the corresponding Class as the second argument. public static Object getFieldValue(final Field field, final Object object)Copy the code
Note that PowerMock, since it modifies the corresponding value through reflection, means that it cannot mock primitives and String variables that are modified by final (note: wrapper types for primitives are not included). As for why, this question will also be discussed at the end of the article.
2. The mock method
WhiteBox also encapsulates the corresponding methods so that we can call private methods through reflection. Of course, that’s not the point, just the word count.
Public static synchronized <T> T invokeMethod(Object instance, String methodToExecute, Object... Arguments) // Public static synchronized <T> T invokeMethod(Class<? > clazz, String methodToExecute, Object... arguments)Copy the code
Because PowerMock is a box that extends on top of Mockito, many of its apis are similar to Mockito. But there are many differences. PowerMock can mock member methods, both private and static.
PowerMock uses the following annotation first when making a mock method:
@prepareForTest (PowerMockSample. Class) public class @prepareForTest (PowerMockSample PowerMockSampleTest { }Copy the code
PowerMock provides mock methods that can be divided into mock(Class
type), SPY (T object), mockStatic(Class
type, Class
… Types), spy(Class
type). The first two are used for mock member methods and the last two are used for mock static methods. So the first two will return the corresponding mock instance, and the last two will return no value.
In PowerMock, the difference between Spy and mock is similar to Mocktio. So please refer to the previous blog post on Mockito without further ado. Here, just to emphasize:
Mock instances generated via SPY (T object) via when… thenReturn… Mock a method. When the mock instance then calls the method (or attempts to call the static method of the class), the real logic is followed before returning the specified value. But by doReturn… when… Mock a method that does not follow real logic.
After calling spy(Class
type), when… thenReturn… Mock a static method. Then, when the static method is called, the real logic is followed before returning the specified value. But by doReturn… when… Mock a static method that does not follow real logic.
1) Mock member methods
Mock (Class
type) and SPY (T Object) both generate a mock instance. Only mock instances can call PowerMock’s APIS to mock member methods.
@Test public void spyObject_mockPrivateMethodCalculateThrowException() throws Exception { int expected = 10; powerMockSample = PowerMockito.spy(powerMockSample); // Do not use when(... .thenReturn(...) . The actual logic that calls the method you want to mock. The result of the mock is then returned. //doComments on the Return method also provide explanations and examples. //when(powerMockSample,"privateMethodCalculateThrowException", isA(int.class), isA(int.class)).thenReturn(expected);
doReturn(expected).when(powerMockSample, "privateMethodCalculateThrowException", isA(int.class), isA(int.class));
int actual = Whitebox.invokeMethod(powerMockSample, "privateMethodCalculateThrowException", 1, 2); assertEquals(expected, actual); } @Test public void mockClass_mockPrivateMethodCalculateThrowException() throws Exception { int expected = 10; powerMockSample = PowerMockito.mock(PowerMockSample.class); // Both are acceptable. They don't follow real logic. //when(powerMockSample,"privateMethodCalculateThrowException", isA(int.class), isA(int.class)).thenReturn(expected);
doReturn(expected).when(powerMockSample, "privateMethodCalculateThrowException", isA(int.class), isA(int.class));
int actual = Whitebox.invokeMethod(powerMockSample, "privateMethodCalculateThrowException", 1, 2);
assertEquals(expected, actual);
}
Copy the code
2) Mock static methods
mockStatic(Class
type, Class
… Types), spy (Class < T > type)
@Test
public void mockStatic_mockPublicStaticMethodReturnStringButThrowException() throws Exception {
String newValue = "mockPublicStaticMethodReturnStringButThrowException"; PowerMockito.mockStatic(PowerMockSample.class); // No exception is thrown, so no real logic is taken. Null is returned. assertEquals(null, PowerMockSample.publicStaticMethodReturnStringButThrowException()); //when... thenReturn... Same effect. They don't follow real logic. // There is no difference between the two forms of when. //when(PowerMockSample.class,"publicStaticMethodReturnStringButThrowException").thenReturn(newValue); //when(PowerMockSample.publicStaticMethodReturnStringButThrowException()).thenReturn(newValue); / / errordoThe Return of writing. Flip UnfinishedStubbingException anomalies. //doReturn(newValue).when(PowerMockSample.publicStaticMethodReturnStringButThrowException());
doReturn(newValue).when(PowerMockSample.class,"publicStaticMethodReturnStringButThrowException");
assertEquals(newValue, PowerMockSample.publicStaticMethodReturnStringButThrowException());
}
@Test
public void spyClass_mockPublicStaticMethodNoReturnThrowException() throws Exception {
PowerMockito.spy(PowerMockSample.class);
boolean isThrowException = false;
try {
Whitebox.invokeMethod(PowerMockSample.class, "privateStaticMethodNoReturnThrowException");
} catch (Exception e) {
isThrowException = true; } // Throw an exception and execute real logic. assertEquals(true,isThrowException); // Mock class // no return value method, can only passdoNothing... when...doNothing().when(PowerMockSample.class, "publicStaticMethodNoReturnThrowException");
PowerMockSample.publicStaticMethodNoReturnThrowException();
}
Copy the code
Note:
1. Unlike spy(Class
type) in Mockito, spy(Class
type) has no return value. It would have been better named spyStatic. This is because, like mockStatic, it is the API used when mocking static methods.
2. Call doReturn… when… When mocking a static method, do not call the method directly from the class name, as in:
/ / flip UnfinishedStubbingException anomaliesdoReturn(expected).when(PowerMockSample.publicStaticMethodCalculate(isA(int.class),isA(int.class)));
Copy the code
The correct approach is as follows:
doReturn(expected).when(PowerMockSample.class, "publicStaticMethodCalculate", isA(int.class), isA(int.class));
Copy the code
Is reflection really unable to modify primitive types and String variables modified by final?
As mentioned earlier, PowerMock achieves the purpose of mocking by modifying the corresponding value through reflection. This means that you cannot mock basic and String variables that are modified by final.
This, track PowerMock WhiteBox. SetInternalState () method of the source code can be found.
private static void checkIfCanSetNewValue(Field fieldToSetNewValueTo) {
int fieldModifiersMask = fieldToSetNewValueTo.getModifiers();
boolean isFinalModifierPresent = (fieldModifiersMask & Modifier.FINAL) == Modifier.FINAL;
boolean isStaticModifierPresent = (fieldModifiersMask & Modifier.STATIC) == Modifier.STATIC;
if(isFinalModifierPresent && isStaticModifierPresent){
boolean fieldTypeIsPrimitive = fieldToSetNewValueTo.getType().isPrimitive();
if (fieldTypeIsPrimitive) {
throw new IllegalArgumentException("You are trying to set a private static final primitive. Try using an object like Integer instead of int!");
}
boolean fieldTypeIsString = fieldToSetNewValueTo.getType().equals(String.class);
if (fieldTypeIsString) {
throw new IllegalArgumentException("You are trying to set a private static final String. Cannot set such fields!"); }}}Copy the code
But there are some problems with this code:
1) The relevant code does not involve the modifier private, so the exception information thrown is misleading
2) The point is that final primitive types and String variables cannot be reflected to mock. It doesn’t have anything to do with being static.
3) Also, if the test class annotates @runwith (PowerMockRunner. Class), the exception cannot be thrown normally and should be tried.
But is reflection really unable to modify primitive types and String variables that are modified by final?
Let’s start with the following test:
@Test
public void mockPublicStaticFinalInt() {
//public static final int publicStaticFinalInt = 1;
int newValue = 2;
Whitebox.setInternalState(PowerMockSample.class, "publicStaticFinalInt", newValue); AssertEquals (newValue, getStaticFieldValue(powermockSample.class,"publicStaticFinalInt")); // Note that this is not equal. assertNotEquals(newValue, PowerMockSample.publicStaticFinalInt); }Copy the code
Why the value of the variable publicStaticFinalInt. It’s changed, but it’s not the same, right? Because assertNotEquals (newValue, PowerMockSample. PublicStaticFinalInt); This code in the PowerMockSample. PublicStaticFinalInt at compile time, in the bytecode has been replaced with the corresponding values. So, you can change the value of the publicStaticFinalInt variable. It has nothing to do with it.
Let’s move on to another piece of code:
int mInt = 1;
String mString = "string";
static int mStaticInt = 1;
static String mStaticString = "staticString";
final int mFinalInt = 1;
final String mFinalString = "finalString";
final static int mFinalStaticInt = 1;
final static String mFinalStaticString = "finalStaticString";
public void test() {
int _int = mInt;
String string = mString;
int staticInt = mStaticInt;
String staticString = mStaticString;
int finalInt = mFinalInt;
String finalString = mFinalString;
int finalStaticInt = mFinalStaticInt;
String finalStaticString = mFinalStaticString;
}
Copy the code
The bytecode corresponding to the test() method:
public void test(a); Code: 0: aload_0 1: getfield#2 // Field mInt:I
4: istore_1
5: aload_0
6: getfield #4 // Field mString:Ljava/lang/String;
9: astore_2
10: getstatic #8 // Field mStaticInt:I
13: istore_3
14: getstatic #9 // Field mStaticString:Ljava/lang/String;
17: astore 4
19: iconst_1
20: istore 5
22: ldc #6 // String finalString
24: astore 6
26: iconst_1
27: istore 7
29: ldc #11 // String finalStaticString
31: astore 8
33: return
Copy the code
Take a close look at the bytecode of the test() method. Variables that are not final, such as mInt, mString, mStaticInt, and mStaticString, are all assigned to instance and class variables via the bytecode instructions getField or getStatic. For final variables mFinalInt and mFinalStaticInt, the bytecode instruction iconst_1 loads the corresponding value 1 before assigning, and the variables mFinalString and mFinalStaticString pass the bytecode instruction LDC. Load the corresponding value directly from the constant pool, regardless of the corresponding instance variable or class variable.
So, the answer to the question is: reflection is the ability to modify a final primitive or String variable to point to a new value. But you can’t change the value in any other code that uses this variable. Because in that code, the value of the variable is either directly replaced at compile time with the corresponding value, or refers to a constant in the constant pool.
Afterword.
PowerMock makes up for the fact that Mockito cannot mock private, static, or final methods, making it easier to write test cases without breaking code encapsulation.
Related test examples and more can be found in UnitTest.
For more test examples and how to use the API, see the Test Cases in the PowerMock source code.