Want to start a religious discussion? Start asking your teammates whether or not you should mock static method calls. It's debatable if this is an absolute no-go or sometimes a last resort. I agree that you should rethink your implementation if you find yourself googling: Mocking static calls Java. On the other side, there are still valid use cases where this approach is considerable. In the past, PowerMock was the most popular solution for this problem in Java. Starting with version 3.4.0, Mockito now supports mocking static methods.
PS: Before arguing with your co-workers, read through the different opinions around mocking static methods at the corresponding GitHub issue from Mockito.
Required Mockito Maven Setup
Mocking static methods is part of Mockito since version 3.4.0.
If we are using Maven, we can either update our existing Mockito version or include the following dependency to your pom.xml
:
1 2 3 4 5 6 7 | <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <!-- version needs to be >= 3.4.0 --> <version>4.6.1</version> <scope>test</scope> </dependency> |
If our project uses mockito-core
, we'll see the following exception and should replace it with mockito-inline
.
1 2 3 4 5 6 | org.mockito.exceptions.base.MockitoException: The used MockMaker SubclassByteBuddyMockMaker does not support the creation of static mocks Mockito's inline mock maker supports static mocks based on the Instrumentation API. You can simply enable this mock mode, by placing the 'mockito-inline' artifact where you are currently using 'mockito-core'. Note that Mockito's inline mock maker is not supported on Android. |
When developing an application with Spring Boot and the Spring Boot Starter Test, we can update to Spring Boot Version 2.4.0-M2. This version includes the Mocktio dependency in a compatible version (> 3.4.0).
If our project can't update the main Spring Boot version (yet), we can manually override the used Mockito version using:
1 2 3 | <properties> <mockito.version>4.6.1</mockito.version> </properties> |
And include mockito-inline
if required:
1 2 3 4 5 | <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <scope>test</scope> </dependency> |
Update: Starting with Mockito version 5.0.0, the mockito-inline
is now the default MockMaker
. If we're using this version, we can skip any manual configuration to use the InlineMockMaker
.
Important: The upcoming code examples use Java 11. Not everything will work with Java 8 as expected, as the internals of UUID.toString()
did change between those two Java releases.
Mocking Static Methods With Java
Let's take a look at how to use this feature for a Java method that accesses two static methods: UUID.randomUUID()
and LocalDateTime.now()
.
Whether or not this implementation or the corresponding tests make sense is not up for discussion. The example is only for demonstration purposes on how to mock static method calls with Mockito.
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class OrderService { public Order createOrder(String productName, Long amount, String parentOrderId) { Order order = new Order(); order.setId(parentOrderId == null ? UUID.randomUUID().toString() : parentOrderId); order.setCreationDate(LocalDateTime.now()); order.setAmount(amount); order.setProductName(productName); return order; } } |
With the first test, we want to make sure to use a random UUID
whenever we don't pass a parentOrderId
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class OrderServiceTest { private OrderService cut = new OrderService(); private UUID defaultUuid = UUID.fromString("8d8b30e3-de52-4f1c-a71c-9905a8043dac"); private LocalDateTime defaultLocalDateTime = LocalDateTime.of(2020, 1, 1, 12, 0); @Test void shouldIncludeRandomOrderIdWhenNoParentOrderExists() { try (MockedStatic<UUID> mockedUuid = Mockito.mockStatic(UUID.class)) { mockedUuid.when(UUID::randomUUID).thenReturn(defaultUuid); Order result = cut.createOrder("MacBook Pro", 2L, null); assertEquals("8d8b30e3-de52-4f1c-a71c-9905a8043dac", result.getId()); } // ... next test } |
Here we are using a try-with-resources statement to create a mocked version of the UUID
class. This is because MockedStatic
extends the ScopedMock
interface, which itself extends AutoClosable
. The static mocks are thread-local scoped and hence need to be closed.
Inside the try-with-resources statement, we then get access to the mocked version of UUID
and can define its behavior using Mockito's well-known when().thenReturn()
stubbing setup.
As an alternative, we could also manually call .close()
inside @AfterEach
.
The same is true when we write a test that includes mocking LocalDateTime.now()
:
1 2 3 4 5 6 7 8 9 10 | @Test void shouldIncludeCurrentTimeWhenCreatingANewOrder() { try (MockedStatic<LocalDateTime> mockedLocalDateTime = Mockito.mockStatic(LocalDateTime.class)) { mockedLocalDateTime.when(LocalDateTime::now).thenReturn(defaultLocalDateTime); Order result = cut.createOrder("MacBook Pro", 2L, "42"); assertEquals(defaultLocalDateTime, result.getCreationDate()); } } |
Mocking Static Methods With Method Arguments
Both mocking examples above were using Java's method reference. That's a convenient way to write compact lambda expressions by referring to an existing method. However, this doesn't apply to mocking every static method.
Let's use the following MyUtils
class as an example:
1 2 3 4 5 6 7 8 9 10 | public class MyUtils { public static String getWelcomeMessage(String username, boolean isCustomer) { if (isCustomer) { return "Dear " + username; } else { return "Hello " + username; } } } |
When we now want to mock the static .getWelcomeMessage()
method, we can't use the method reference. Mockito's .when()
takes a Verification
as an argument. This is a functional interface as it has one public method: .apply()
.
Hence for both UUID.randomUUID()
and LocalDateTime.now()
we could use their method reference like.when(LocalDateTime::now)
.
That's not possible for .getWelcomeMessage(String username, boolean isCustomer)
. If we want to mock this utility method, we can write a custom lambda expression to provide a Verification
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class MyUtilsTest { @Test void shouldMockStaticMethod() { try (MockedStatic<MyUtils> mockedStatic = Mockito.mockStatic(MyUtils.class)) { mockedStatic .when(() -> MyUtils.getWelcomeMessage(eq("duke"), anyBoolean())) .thenReturn("Howdy duke!"); String result = MyUtils.getWelcomeMessage("duke", false); assertEquals("Howdy duke!", result); } } } |
Mocking Static Methods With Kotlin and Mockito
Let's take a look at the OrderService
again, but this time we use Kotlin to implement its functionality:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class OrderService { fun createOrder(productName: String, amount: Long, parentOrderId: String?) = Order( id = parentOrderId ?: UUID.randomUUID().toString(), creationDateTime = LocalDateTime.now(), amount = amount, productName = productName ) } data class Order( val productName: String, val amount: Long, val id: String, val creationDateTime: LocalDateTime ) |
Mocking the static method calls also works here. To write even more idiomatic Kotlin code, we can replace the try-with-resources statement with the use
function of the Kotlin standard library:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class OrderServiceKtTest { private val cut = OrderService() private val defaultUuid = UUID.fromString("8d8b30e3-de52-4f1c-a71c-9905a8043dac") @Test fun `should include random order id when no parent order exists`() { Mockito.mockStatic(UUID::class.java).use { mockedUuid -> mockedUuid.`when`<Any> { UUID.randomUUID() }.thenReturn(defaultUuid) val result = cut.createOrder("MacBook Pro", 2L, null) assertEquals("8d8b30e3-de52-4f1c-a71c-9905a8043dac", result.id) } } } |
Since when
is a reserved keyword in Kotlin, we have to use the backticks when stubbing the behavior.
Both mockito-kotlin and Mockk seem not to support this yet.
Refactored Alternative to Avoid Mocking Static Methods
As an alternative, let's have a look at how we can avoid mocking static methods for our OrderService
. If our project uses a framework that supports dependency injection (e.g., Spring, Jakarta EE with CDI, MicroProfile, Guice), we can slightly refactor our current implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class OrderServiceRefactored { private final Clock clock; private final OrderIdGenerator orderIdGenerator; public OrderServiceRefactored(Clock clock, OrderIdGenerator orderIdGenerator) { this.clock = clock; this.orderIdGenerator = orderIdGenerator; } public Order createOrder(String productName, Long amount, String parentOrderId) { Order order = new Order(); order.setId(parentOrderId == null ? orderIdGenerator.generateOrderId() : parentOrderId); order.setCreationDate(LocalDateTime.now(clock)); order.setAmount(amount); order.setProductName(productName); return order; } } |
With this approach, we outsource the creation of the orderId
to another component. In addition to this, we use an overloaded version of LocalDateTime.now()
that takes a Clock
to request the current date & time.
This small refactoring allows us to mock both the Clock
and OrderIdGenerator
while unit testing our OrderServiceRefactored
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class OrderServiceRefactoredTest { private OrderIdGenerator orderIdGenerator = mock(OrderIdGenerator.class); private Clock clock = mock(Clock.class); private OrderServiceRefactored cut = new OrderServiceRefactored(clock, orderIdGenerator); @Test void shouldIncludeRandomIdAndCurrentDateTime() { when(orderIdGenerator.generateOrderId()).thenReturn("8d8b30e3-de52-4f1c-a71c-9905a8043dac"); LocalDateTime defaultLocalDateTime = LocalDateTime.of(2020, 1, 1, 12, 0); Clock fixedClock = Clock.fixed(defaultLocalDateTime.toInstant(ZoneOffset.UTC), ZoneId.of("UTC")); when(clock.instant()).thenReturn(fixedClock.instant()); when(clock.getZone()).thenReturn(fixedClock.getZone()); Order result = cut.createOrder("MacBook Pro", 2L, null); assertEquals("8d8b30e3-de52-4f1c-a71c-9905a8043dac", result.getId()); assertEquals(defaultLocalDateTime, result.getCreationDate()); } } |
We can now use standard Mockito functionality to mock the behavior of our external components during the test.
Remember: Having access to a tool does not mean you should always use it.
The refactored example should give you valuable hints on how to work around mocking static calls. If there is still the need to mock them, you don't need to include PowerMock, as Mockito now supports it.
Are you looking for further practical resources on Mockito? Consider enrolling in my Hands-On With Mockito Online Course to learn the ins and outs of the most popular mocking library for JVM applications.
You can find related Mockito articles here:
- Mock Java Constructors With Mockito | Configuration and Examples
- @Mock vs. @MockBean When Testing Spring Boot Applications
- Maven Setup For Testing Java Applications
The source code for this Mockito example is available on GitHub.
Have fun mocking static method calls with Mockito,
Philip