After so many years of hopeless waiting, Mockito 2.x is released to solve many problems that most of the android developers were having in their tests.
There are great features of Mockito 2.x which includes:
- Finally mocking final classes and methods.
- Support for Java 8.
- Migration from CGLIB to ByteBuddy.
But if you are having today large tests written in Mockito 1.x, will it be an easy task to migrate?
Unfortunately, the migration most probably will be a painful task because Mockito 2.x does not respect the old behavior of Mockito 1.x. Adding to this complexity, If you are having PowerMock in your old tests, then you will have to face another dimension of complexity since most of PowerMock’s versions are having integration issues with Mockito 2.x.
Regarding PowerMock’s early issues with Mockito 2.x, PowerMock team announced that PowerMock 1.6.5 has an experimental support for Mockito 2.x but unfortunately it was not that great. It has some problems with Mockito 2.x.
In the beginning when just changing Mockito version to 2.x in your build.gradle file, you may find more than 50% of your tests were failing due to a lot of issues, Null pointer exceptions, compilation errors, No class definition found, un-expected thrown exception, …etc, and this is how you may look in the beginning of the migration.
Do not panic and do not be sad, this blog post mentions some of the important challenges that you may face during the migration and tips to overcome these challenges to save your time.
1. Use the proper PowerMock’s Mockito API extension
powermock-api-mockito extension does not work with Mockito 2.x, you will have the following exception when running your unit tests if you stick to the old extension:
java.lang.NoClassDefFoundError: org/mockito/cglib/proxy/MethodInterceptor at org.powermock.api.mockito.internal.mockmaker.PowerMockMaker.<init>(PowerMockMaker.java:43) ... Caused by: java.lang.ClassNotFoundException: org.mockito.cglib.proxy.MethodInterceptor at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 40 more
In order to fix this issue, you should use the right Mockito’s API extension which is:
2. Avoid using Incompatible versions of Mockito and PowerMock
Always make sure to use compatible versions of Mockito and PowerMock, for example the following two versions are compatible:
- PowerMock version 1.7.0 RC2.
- Mockito version 2.1.0.
3. Say Goodbye to Mockito’s Whitebox
Mockito 2.x does not have Whitebox anymore.
So what is the solution then?
Initially, you can use PowerMock’s Whitebox instead of the removed Mockito’s 2.x Whitebox. However, you need to know that this does not come without problems, one of the problem which I reported to PowerMock’s issues is (
org.powermock.reflect.exceptions.FieldNotFoundException: No instance field named XXX could be found in the class hierarchy):
So if the initial solution does not work with you, why not writing your own one, it is not really hard.
4. Using the Right Matchers
Never forget to always use org.mockito.ArgumentMatchers instead of the old org.mockito.Matchers.
For example, replace the old matcher imports:
import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq;
With the following ones:
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq;
And so on with other matchers.
5. anyInt() does not match Long literals anymore
After the upgrade, you may find anyInt() does not work because it cannot match long literals such as 0L for example :).
So in order to fix this issue, just replace anyInt() with anyLong() and you will be fine.
OT note, if you can match with the actual value instead of anyXXX(), this can be much better and will give your test more transparency.
6. anyString() does not now match null anymore
This can fail many tests, anyString() now does not include null anymore in Mockito 2.x. This applies also to any(xxx), for example, any(InputStream.class) does not now match null in Mockito 2.x as well.
Again OT note, if you can match with the actual value instead of anyXXX(), this can be much better and will give your test more transparency.
7. Mockito 2.7.1 is not a real friend to PowerMock
Unfortunately, if you use PowerMock 1.6.5 or even PowerMock 1.7.0RC2 with Mockito 2.7.1 (the latest version at the time of writing this post), you will find the following exception with
java.lang.IllegalAccessError: tried to access method org.mockito.internal.stubbing.answers.DoesNothing.<init>()V from class org.powermock.api.mockito.PowerMockito
Fortunately, this issue is fixed with PowerMock
1.7.0RC4, and below is the fix issue URL for your information:
So in summary if you use Mockito 2.7.1, do not forget to use PowerMock 1.7.0RC4.
8. Original test exceptions are wrapped as RuntimeExceptionProxy in Mockito 2.x with PowerMock
Unfortunately as a workaround, you have to modify all the broken
@Test(expected=Exception.class) since original exceptions are wrapped as Mockito
RuntimeExceptionProxy in Mockito 2.x with PowerMock.
This issue really requires further investigation to know why Mockito 2.x does this wrapping with PowerMock. if you have a better solution for this, feel free to comment to the post.
9. Move away from PowerMock and depend on Mockito 2.x only
Try to create a plan to remove PowerMock by refactoring your app classes to be testable. Mockito 2.x is really enough now.
10. Review old tests
Take this migration as a chance to review the old tests and to improve them in order to have a better maintainable tests. This can help you strengthen your product code and allow easier refactoring for the current code base without surprises.