如何Mock静态方法中调用的静态方法?JUnit数据库连接测试求助
Hey there! Let's break down how to solve your two JUnit testing challenges—first fixing the database connection issue for your getAndSavePlant method, then mocking static methods that call other static methods.
getAndSavePlant Tests Looking at your App class, the static connection field is initialized in the main method, which won't run during JUnit tests. That's why your tests are struggling to get a valid database connection. Here are two solid solutions:
Option 1: Refactor for Testability (Recommended)
Extract your database connection logic into a separate utility class instead of keeping it tied to the main method. This makes it easy to mock or replace the connection in tests.
Example refactor:
public class DatabaseUtil { private static Connection connection; public static Connection getConnection() { if (connection == null) { try { Class.forName("com.mysql.jdbc.Driver"); connection = DriverManager.getConnection("your-db-url", "username", "password"); } catch (ClassNotFoundException | SQLException e) { // Handle exception properly (log, rethrow, etc.) throw new RuntimeException("Failed to connect to database", e); } } return connection; } }
Then update your App class to use this utility:
public class App { public static void main(String[] args) { Connection conn = DatabaseUtil.getConnection(); // Rest of your main logic } public static Plant getAndSavePlant() { Connection conn = DatabaseUtil.getConnection(); // Logic to fetch and save plant } }
Now in tests, you can either:
- Mock
DatabaseUtil.getConnection()to return a mock connection (using tools like PowerMock or Mockito'smockStatic), or - Use an in-memory database (like H2) for real, isolated tests.
Option 2: Use an In-Memory Database for Realistic Testing
For tests that need to interact with actual database logic without hitting your production MySQL instance, use an in-memory H2 database.
First, add the H2 dependency to your pom.xml:
<dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>test</scope> </dependency>
Then set up the connection in your test class:
import org.junit.jupiter.api.BeforeEach; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; public class AppTest { @BeforeEach void setUp() throws Exception { // Initialize H2 in-memory connection Class.forName("org.h2.Driver"); Connection testConnection = DriverManager.getConnection( "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1", "sa", "" ); // Create your plants table (match your production schema) Statement stmt = testConnection.createStatement(); stmt.execute("CREATE TABLE plants (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255))"); // Inject the test connection into App's static connection field using reflection Field connectionField = App.class.getDeclaredField("connection"); connectionField.setAccessible(true); connectionField.set(null, testConnection); } // Test your getAndSavePlant method here }
This way, your tests run against a lightweight, isolated database that gets reset between test runs.
Mockito alone doesn't support mocking static methods, but there are two reliable tools to handle this:
Using Mockito's mockStatic (Java 8+, Mockito 3.4+)
If you're using a newer version of Mockito, its mockStatic API lets you mock static methods in a clean, try-with-resources block.
Example: Suppose getAndSavePlant calls DatabaseUtil.savePlant(), another static method. Here's how to mock it:
import org.junit.jupiter.api.Test; import static org.mockito.Mockito.*; public class AppTest { @Test void testGetAndSavePlant() { Plant testPlant = new Plant(); testPlant.setName("Sunflower"); // Mock DatabaseUtil's static methods try (MockedStatic<DatabaseUtil> mockedDbUtil = mockStatic(DatabaseUtil.class)) { // Mock getConnection to return a mock connection Connection mockConn = mock(Connection.class); mockedDbUtil.when(DatabaseUtil::getConnection).thenReturn(mockConn); // Mock savePlant to do nothing or return a value mockedDbUtil.when(() -> DatabaseUtil.savePlant(mockConn, testPlant)).thenReturn(true); // Execute the method under test App.getAndSavePlant(); // Verify the static method was called mockedDbUtil.verify(DatabaseUtil::getConnection, times(1)); } } }
The try-with-resources block ensures the static mock is cleaned up after the test, preventing side effects on other tests.
Using PowerMock (For Older Setups or Complex Scenarios)
If you're stuck with an older Mockito version or need more control, PowerMock works alongside Mockito to mock static methods.
First, add PowerMock dependencies to your pom.xml:
<dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency>
Then write your test with PowerMock annotations:
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.powermock.api.mockito.PowerMockito.*; @RunWith(PowerMockRunner.class) @PrepareForTest({DatabaseUtil.class, App.class}) // List all classes with static methods to mock public class AppTest { @Test void testGetAndSavePlant() { // Mock DatabaseUtil.getConnection() Connection mockConn = mock(Connection.class); mockStatic(DatabaseUtil.class); when(DatabaseUtil.getConnection()).thenReturn(mockConn); // Execute the method under test App.getAndSavePlant(); // Verify the static method was called verifyStatic(DatabaseUtil.class); DatabaseUtil.getConnection(); } }
Just make sure your PowerMock version is compatible with your Mockito version to avoid conflicts.
内容的提问来源于stack exchange,提问作者anais1477

