By Zuobing
The impetus for this change came with the upgrade to JDK11. During research to upgrade a core application within our team to JDK11, we discovered that PowerMock, according to its documentation, supports only up to JDK9 and does not support higher JDK versions. More importantly, PowerMockito has not been maintained for a long time.
Continuing to integrate an unmaintained open-source testing framework into our projects could potentially lead to issues with future JDK versions and their new features. Therefore, taking advantage of the JDK11 upgrade as an opportunity for significant refactoring, we decided to remove the PowerMock dependency and adopt the Mockito Only testing framework.
According to the documentation about the Mockito open-source repository, Mockito continues to evolve with new versions, and the testing framework also consistently adapts to newer JDK versions.
https://github.com/powermock/powermock
https://github.com/mockito/mockito/releases/tag/v5.0.0
Additionally, we would like to share some experience with teams currently using PowerMock. When attempting to upgrade the PowerMock version, we encountered memory leak issues. Users in the PowerMock community have reported similar problems, but the corresponding issues have not been resolved.
https://github.com/powermock/powermock/issues/227
To remove the PowerMock dependency, there are two main changes needed. One is replacing the corresponding Mock functionality with Mockito, and the other is updating Mockito to a newer version.
When using Mockito Only, you need to use the following JUnit Runner: @RunWith(MockitoJUnitRunner.class)
In scenarios where the spring-test framework is needed, Mockito does not have a similar annotation like @PowerMockRunnerDelegate from PowerMock; however, we can achieve the same effect by configuring a Mockito JUnit Rule within the test class.
public class ExampleTestClass {
@Rule public MockitoRule mockito = MockitoJUnit.rule();
...
@Test
public void test() {
...
}
...
}
The latest version of Mockito also supports Mock static methods, with similar usage to PowerMock.
Mockito does not support mocking private and final methods. When refactoring, some code restructuring may be necessary, as PowerMock makes it too easy to mock these methods, leading to lengthy and harder-to-test code.
Since Mockito does not support setting private and final variables, the Whitebox utility from PowerMock is no longer available. However, other third-party libraries like FieldUtils from Apache Commons can be used, but they can only set private variables and final variables still require code refactoring.
Mock rule reuse simplifies unit tests and improves the efficiency of writing them. Repeated mock rules from unit tests can be extracted to achieve writing them once and reusing them multiple times.
PowerMock achieves this through the PowerMockPolicy class. Here is an example:
public class ContextMockPolicy implements PowerMockPolicy {
@Override
public void applyClassLoadingPolicy(MockPolicyClassLoadingSettings settings) {
settings.addFullyQualifiedNamesOfClassesToLoadByMockClassloader(
Xxx.class.getName());
}
@Override
public void applyInterceptionPolicy(MockPolicyInterceptionSettings settings) {
final Method getXxx = Whitebox.getMethod(Xxx.class, "getXxx");
settings.stubMethod(getXxx, Optional.ofNullable(mockXxx());
final Method getXxxXxx = Whitebox.getMethod(Xxx.class, "getXxxXxx");
settings.stubMethod(getXxxXxx, Optional.ofNullable(Xxx));
}
}
@MockPolicy({ContextMockPolicy.class})
public class ExampleTestClass {
...
@Test
public void test() {
...
}
...
}
Using Mockito Only, you can achieve the same effect by combining the ClassRule of JUnit. Below is the corresponding implementation for Mockito Only for the example given.
public class ContextMockRule implements TestRule {
private MockedStatic<Xxx> mockedStatic;
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
mockXxx();
base.evaluate();
} finally {
if (mockedStatic != null) {
mockedStatic.close();
}
}
}
};
}
private void mockXxx() {
mockedStatic = Mockito.mockStatic(Xxx.class);
mockedStatic
.when(() -> Xxx.getXxx())
.thenReturn(Optional.ofNullable(mockXxx()));
mockedStatic
.when(() -> Xxx.getXxxXxx())
.thenReturn(Optional.ofNullable(Xxx));
}
}
public class ExampleTestClass {
@ClassRule public static ContextMockRule contextMockRule = new ContextMockRule();
...
@Test
public void test() {
...
}
...
}
Mockito has limitations in multi-threaded testing scenarios, including those involving ExecutorService and ParallelStream, where Mock static methods may not work as expected, not being aligned with PowerMock.
For the concurrent scenario with ExecutorService thread pools, we can adopt the following method of Mock ExecutorService. However, for the concurrent scenario with Java Stream ParallelStream, we have not yet found a viable solution.
ExecutorService chatExecutor = Mockito.mock(ExecutorService.class);
doAnswer(
(Answer<Object>)
invocation -> {
Object[] args = invocation.getArguments();
Callable callable = (Callable) args[0];
Object result = callable.call();
FutureTask futureTask = Mockito.mock(FutureTask.class);
Mockito.when(futureTask.get(anyLong(), any()))
.thenReturn(result);
return futureTask;
})
.when(chatExecutor)
.submit(any(Callable.class));
https://groups.google.com/g/mockito/c/8_WGBB3Jbtk/m/JUUq4EpgplcJ
I'd like to give additional info on this. The origin of these methods is they come from anything i.e. anything matches, later for shortness and cast avoidance the aliases grew, but the API naming thus became inconsistent with what a human would expect. So this behavior is being changed in mockito 2 beta, to be precise here's the status on these API in the version 2.0.5-beta :
• any, anyObject, any(Class) won't check anything (at first they were just aliases for anything and for cast avoidance)
• anyX like anyString will check the arg is not null and that has the correct type
• anyList will check the argument is not null and a List instance
• anyListOf (and the likes) at the moment are just aliases to their non generic counter part like anyList
Note this is work in progress (started here in #141), these new behavior can / will change in the beta phase. I'm especially wondering if the any familly should allow null and if not do a type check. For example with these matchers :• any, anyObject stay the same, they currently allow null and don't have to do type check anyway
• any(Class) currently allows null and doesn't do type check => allows null and if not checks for the given type
•any<Collection>Of
currently doesn't allow null and does a type check of the collection, not elements => allows null, if not checks collection type, if not empty checks element typeMaybe extend the isA family that won't allow any null arguments. Thoughts ?
Referring to discussions in the Mockito Google Group about behavioral changes after upgrading Mockito versions, and based on our own experience in project refactoring, we identified the following incompatible changes:
Methods like anyLong(), anyString(), and anyObject() that include type checks no longer support null values. For null values, you need to modify the method to be "any()" instead.
The anonymous class method for ArgumentMatcher now specifies the exact parameter type, rather than accepting any Object.
There are changes in how invocation parameters are obtained.
It is often said that an engineer who does not know how to take shortcuts is not a good product developer. For applications that have been developed and maintained for several years, there might be hundreds or even thousands of unit tests within the project. If you were to refactor these manually, it could consume a significant amount of time and effort, potentially driving away a dedicated product developer. Here are some tools that can help improve efficiency. Feel free to share better options in the comments.
If your project contains a relatively small number of unit tests, you can leverage AI to lighten your workload.
In the scenario of refactoring test code, you can define a custom Prompt to automate the refactoring of test classes. However, due to the nature of large language models, the longer the input and output, the more time it takes to generate complete answers, which makes refactoring test code quite slow. Additionally, the generated content may not always meet the requirements.
Perhaps a more sophisticated Agent approach could be adopted, but this would require development and extensive debugging tailored to real-world testing scenarios.
For projects with thousands of unit tests, the above Prompt is not feasible for speeding up refactoring.
Here is my solution. I started by manually refactoring a few unit tests and identified steps that could be automated, such as replacing annotations. Based on these observations, I created a unit test refactoring script using Python and Tree-sitter, significantly reducing repetitive tasks.
However, the automated script does not solve all incompatibility issues, such as Mockito's limitations in multi-threaded testing scenarios. These incompatibilities still require manual verification and resolution when validating unit tests.
There are fewer PowerMock-specific annotations, and the creation and disposal of Mock resources are more traceable, eliminating concerns about memory leaks.
The duration of unit test tasks is notably reduced. On my work computer, it went from 9 minutes and 53 seconds to 3 minutes and 57 seconds, and the memory usage dropped from around 5 GB to approximately 2.5 GB.
Content sourced from GTP-4o
Both Mockito and PowerMock are commonly used Java unit testing frameworks for creating and managing mock objects, but they differ in their underlying implementation principles.
Mockito is a relatively lightweight testing framework that primarily uses Java dynamic proxies and bytecode generation libraries (such as ByteBuddy) to create mock objects. Its implementation principles mainly include:
PowerMock is a more powerful testing framework that is often used alongside other mocking frameworks like Mockito and EasyMock. It can mock scenarios that Mockito cannot handle, such as static methods, constructors, and private methods. The implementation principles of PowerMock are more complex and primarily involve the following aspects:
• Mockito: Primarily generates mock objects through dynamic proxy and bytecode generation. It can only handle non-static and non-private methods of interfaces or concrete classes and is relatively lightweight.
• PowerMock: Uses advanced techniques such as bytecode manipulation and class loader substitution to handle complex scenarios like static methods, constructors, and private methods. It extends the functionality of frameworks like Mockito and EasyMock, offering more powerful features with a more complex implementation.
The choice of underlying technologies for these two frameworks differentiates their functionalities and use cases. While PowerMock can perform more "magic" operations, Mockito's simplicity and performance advantages make it more popular for most daily testing needs.
Disclaimer: The views expressed herein are for reference only and don't necessarily represent the official views of Alibaba Cloud.
Unleashing the Power of Qwen, Alibaba Cloud Kicks Off Hong Kong Inter-School AIGC Competition
1,042 posts | 256 followers
FollowChangyi - September 2, 2021
Changyi - May 27, 2021
Alibaba Cloud Community - April 24, 2023
Alibaba Cloud Community - April 24, 2023
Alibaba Clouder - April 19, 2021
Alibaba Cloud Community - March 22, 2023
1,042 posts | 256 followers
FollowExplore Web Hosting solutions that can power your personal website or empower your online business.
Learn MoreA low-code development platform to make work easier
Learn MoreExplore how our Web Hosting solutions help small and medium sized companies power their websites and online businesses.
Learn MoreHelp enterprises build high-quality, stable mobile apps
Learn MoreMore Posts by Alibaba Cloud Community