Mocking static methods has just been made possible in Mockito 3.4.0, which goes really well with JUnit 5 and reduces reliance on PowerMock and JUnit Vintage.
The use of static methods in Java can be seen as somewhat controversial. It doesn't align too well with the principles of Inversion of Control and Dependency Injection. Some developers shun static methods entirely, some limit their use to static factories or pure functions with no external dependencies and extremely simple input/output based on simple, immutable types. Whether we like them or not, we do sometimes end up relying on a static method.
In many cases, such a dependency will be completely transparent to someone using our API. The odd static method could be performing some string operations or similar logic we might not be inclined to abstract away using mocks in our unit tests. It is when the static utility is responsible for something more complex, such as changing mutable state or performing input/output operations that we face the somewhat unpleasant chore of mocking it.
There's a few APIs in Apache Sling and Adobe Experience Manager that expose static utility methods. Over the years, libraries such as Sling Mocks and wcm.io AEM Mocks have reduced our reliance on manually configured mocks, allowing the test code to be executed against a more or less complete implementation of the content repository and the layers built on top of it, rather than rely on meticulous configuration of every method invocation. Nevertheless, manually setting up mocks is still not unheard of, and this includes mocks of static methods.
For a good number of years, the prevalent way to go about mocking static utility methods in unit tests in Java has been to use PowerMock. Indeed, a quick Google search for mocking static methods in Java yields this highly up-voted answer on Stack Overflow, which explains how it can be done.
A little over a week ago, I saw a notification on Twitter, announcing the release of Mockito 3.4.0, which saw the introduction of static mocking capabilities. I didn't think much of it at the time, vaguely registering it as yet another alternative to a few techniques I had previously known. As it turns out, it didn't take long to come in handy.
The project I'm currently working on has a codebase barely a few months old. It's a relatively modern setup with AEM 6.5, Core Components and solid unit testing setup based on JUnit 5. The build has a hard quality gate on test coverage on new code. A particular feature we were introducing happened to rely on a static method which we weren't able to rewrite. It also turned out that we had not previously mocked a static method in that particular codebase.
PowerMockito
maybe?
Almost instinctively, we tried importing PowerMockito
. It didn't take long to realise that the previous time we had
used it, it was via a JUnit 4 runner. It appears PowerMock
hasn't been updated to use JUnit 5 extensions yet. Now, JUnit 5
comes with a very neat way to support older engines via
JUnit Vintage, so we could have
written the test with PowerMock and JUnit 4 test and ran it that way.
We would have had to add the JUnit Vintage and PowerMock dependencies, though, which felt like a bit of technical debt to introduce.
Mockito 3.4.0 (and above)
I must say, vintage bikes and clothing have a certain appeal. Vintage tests... not so much, although JUnit Vintage has its rightful place in migrations from JUnit 3 or 4 to JUnit 5 (that's a brilliant design and using it has been a blast). Somewhat disinclined to introduce the Vintage engine to a shiny new codebase, we decided to give the freshly released Mockito a try.
To make this possible, a single dependency had to be added to the pom.xml
(on top of mockito-junit-jupiter
which we
were already using)
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.4.4</version>
<scope>test</scope>
</dependency>
The code in which we used this is private, so let's use the following class with a single static method as an example instead:
public final class MysteryBox {
private MysteryBox() {
// cheeky, static utility class
}
public static Optional<Mystery> amaze(String codeWord) {
// Mind-boggling mixture of magic and quantum computing, omitted for brevity
}
}
This class belongs to a 3rd party library that we obtained in an old oddity shop. The shopkeeper cackled as we left. The
code is obfuscated and it works in mysterious ways, involving random factors. All we know is we can pass it a String
and we may potentially receive a mysterious result. When we came to return the class, the shop was no longer there. I
suppose we have no choice but to mock the method in our tests ;)
Let's also introduce a class to use this quirky, static method.
public class HaplessUser {
public Mystery unleashMystery(String magicSpell) {
Optional<Mystery> om = MysteryBox.amaze(magicSpell);
return om.orElseThrow(() -> new FailureToAmazeException("The box was empty"));
}
}
the HaplessUser
class relies on the MysteryBox
yielding a result upon receiving a rather arbitrary string as an
input. The problem with the MysteryBox#amaze
method is that it appears to be as whimsical and unreliable in
consistently returning the same result, as it is static. Let's see how the new Mockito can help mitigate this...
The test code looks as follows:
@Test
@DisplayName("Should throw an exception upon failing to uncover mind-boggling mysteries")
void testUncoverMysteries() {
// Instantiate a MockedStatic in a try-with-resources block
try (MockedStatic<MysteryBox> mb = Mockito.mockStatic(MysteryBox.class)) {
// stub the static method that is called by the class under test
mb.when(() -> { MysteryBox.amaze(any(String.class )) })
.thenReturn(Optional.empty());
// the subject under test uses MysteryBox internally, to uncover an amazing secret
// we make sure it throws an exception when the box fails to deliver
assertThrows(FailureToAmazeException.class, () -> subjectUnderTest.unleashMystery("Abracadabra"));
}
// the mock is not visible outside the block above
}
and that's it!
A few things to note here:
- The mocked static method is only visible in the try-with-resources block. Different outcomes can be tested in isolation.
- Static methods can only be mocked inline. This is good because it helps limit the scope and potential confusion.
- Since the introduction of the feature in Mockito 3.4.0, a JUnit 4
MockitoExtension
incompatibility bug fix was introduced in version 3.4.2. I recommend picking up the latest version available. - As of Mockito 3.4.6, the
MockedStatic
class is still marked with the@Incubating
annotation, which is the Mockito team's way of saying they're still gathering feedback and the API might change, albeit the chance of that happening isincredibly small
.
Whether static utility methods constitute good design warrants a post of its own. In many cases, my preferred way of dealing with this kind of situation would be to refactor my code so that mocking a static method wouldn't be necessary. That said, I do occasionally find myself in situations when such a mock is helpful and I think this API is a useful addition to Mockito. Neat!