Introduction
When writing unit tests for Java applications, you often need to test classes that interact with static methods. Mocking these methods can be challenging since traditional mocking frameworks like Mockito do not support them directly. However, starting from version 3.4.0, Mockito has introduced the capability to mock static methods. This tutorial will guide you through different techniques and best practices for mocking static methods using Mockito.
Why Mock Static Methods?
Static methods are often used in utility classes or when working with libraries that provide global access points, such as java.sql.DriverManager
. When testing code that depends on these static methods, it’s important to isolate the system under test from external dependencies. This isolation allows you to verify behavior without relying on actual implementations of those methods.
Using Mockito for Mocking Static Methods
Basic Setup
To mock static methods with Mockito, ensure you have at least version 3.4.0 of Mockito. If you’re using JUnit 5, include the following dependencies in your pom.xml
:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.6.0</version>
<scope>test</scope>
</dependency>
Mocking a Static Method
To mock a static method, use the MockedStatic
utility class. Here’s an example:
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
public class DatabaseConnectionFactoryTest {
@Test
public void testConnectionMethod() throws SQLException {
try (MockedStatic<DriverManager> mocked = mockStatic(DriverManager.class)) {
DriverManager.getConnection("jdbc:mysql://localhost/test", "user", "password");
mocked.when(() -> DriverManager.getConnection(eq("jdbc:mysql://localhost/test"), eq("user"), eq("password")))
.thenReturn(new Connection() {/* Mock connection behavior */});
DatabaseConnectionFactory factory = new MySQLDatabaseConnectionFactory();
factory.getConnection();
mocked.verify(() -> DriverManager.getConnection(eq("jdbc:mysql://localhost/test"), eq("user"), eq("password")));
}
}
}
Explanation
- Mocking Static Methods: Use
mockStatic
to create a mock for the static class you want to intercept. - Defining Behavior: Use
when(...).thenReturn(...)
to specify what should happen when the mocked method is called. - Verification: Use
verify(...)
to ensure that the method was called with the expected arguments.
Alternative Approaches
PowerMockito
For older versions of Mockito or specific use cases, you can use PowerMockito, a library that extends Mockito’s capabilities:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.mockito.BDDMockito.given;
import static org.powermock.api.mockito.PowerMockito.*;
@RunWith(PowerMockRunner.class)
@PrepareForTest(DriverManager.class)
public class PowerMockitoExample {
@Test
public void shouldVerifyParameters() throws Exception {
mockStatic(DriverManager.class);
given(DriverManager.getConnection("jdbc:mysql://localhost/test", "user", "password")).willReturn(new Connection());
// System Under Test (sut) code
verifyStatic();
DriverManager.getConnection("jdbc:mysql://localhost/test", "user", "password");
}
}
Wrapping Static Methods
Another approach is to wrap static methods in a non-static class. This allows you to mock the wrapper instead:
public class DriverManagerWrapper {
public static Connection getConnection(String url, String user, String password) {
return DriverManager.getConnection(url, user, password);
}
}
In your test, you can now mock DriverManagerWrapper
:
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
public class WrapperExampleTest {
@Test
public void testConnectionMethod() throws SQLException {
Connection mockedConnection = mock(Connection.class);
when(DriverManagerWrapper.getConnection("jdbc:mysql://localhost/test", "user", "password"))
.thenReturn(mockedConnection);
DatabaseConnectionFactory factory = new MySQLDatabaseConnectionFactory();
factory.getConnection();
verify(DriverManagerWrapper).getConnection(eq("jdbc:mysql://localhost/test"), eq("user"), eq("password"));
}
}
Conclusion
Mocking static methods in Java can be effectively managed using Mockito’s mockStatic
feature, PowerMockito for more complex scenarios, or by refactoring your code to avoid direct usage of static methods. Each approach has its advantages and is suitable for different contexts, so choose the one that best fits your testing requirements.