添加Connection等参数后JUnit测试报错:Cannot open transaction cause it is null
Let's break down what's causing your error and fix it step by step:
Root Cause of the Error
When your test triggers the SQLException in getTotalRecords, the code enters the catch block and calls SEEntityManager.rollbackAndReOpenTransaction(). This static method is trying to access a transaction object that hasn't been initialized in your test environment, hence the "Cannot open transaction cause it is null" error. Additionally, several of your mock objects aren't properly configured to handle the secondary calls in the catch block.
Step 1: Mock the Static SEEntityManager Method
Since rollbackAndReOpenTransaction() is a static method, you'll need to mock it using Mockito's static mocking support (available in Mockito 3.4+). This prevents the method from trying to access a real, uninitialized transaction.
Step 2: Fix Mock Configurations for Dependencies
You need to properly configure all mock objects to handle every call made in the business logic, including those in the catch block:
- Mock the
Connectionto return a newStatementwhencreateStatement()is called - Mock
NotificationDatato return a valid SQL string forgetFieldSql() - Configure the initial
Statementto throw anSQLExceptionon its firstexecuteQuery()call
Revised Test Code
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class DAODefaultTest { @Test public void verifyExceptionCountRecords() throws SQLException { // 1. Create all required mock objects Connection mockConn = mock(Connection.class); Statement initialMockStmt = mock(Statement.class); Statement newMockStmt = mock(Statement.class); ResultSet mockRs = mock(ResultSet.class); NotificationData mockNotification = mock(NotificationData.class); // 2. Configure mock behaviors // Make the initial statement throw SQLException on executeQuery when(initialMockStmt.executeQuery(anyString())).thenThrow(new SQLException()); // Mock the static SEEntityManager method to avoid transaction errors try (MockedStatic<SEEntityManager> mockedSEEntityManager = mockStatic(SEEntityManager.class)) { mockedSEEntityManager.when(SEEntityManager::rollbackAndReOpenTransaction).thenReturn(null); // Make connection return a new mock statement when(mockConn.createStatement()).thenReturn(newMockStmt); // Make notification return a valid SQL string when(mockNotification.getFieldSql()).thenReturn("SELECT 1 FROM dummy"); // Configure the new statement's executeQuery (optional, since we return BROKE_SQL) when(newMockStmt.executeQuery(anyString())).thenReturn(mockRs); // 3. Execute the test int result = daoDefault.getTotalRecords(mockConn, initialMockStmt, "", mockNotification); // Note: Your business code returns BROKE_SQL, not 0 - adjust assertion accordingly assertTrue(result == DAODefault.BROKE_SQL); // 4. Verify expected behaviors verify(mockRs, never()).getInt("ROWS_COUNT"); mockedSEEntityManager.verify(SEEntityManager::rollbackAndReOpenTransaction, times(1)); } } }
Bonus: Fix Resource Leak in Business Code
Your catch block uses a raw Statement and ResultSet without proper resource management. Update it to use try-with-resources to avoid leaks:
catch (SQLException e) { try{ SEEntityManager.rollbackAndReOpenTransaction(); try(Statement newStmt = connection.createStatement(); ResultSet newRs = newStmt.executeQuery(notification.getFieldSql())) { return BROKE_SQL; } }catch (final SQLException e1){ logger.error(e1.getMessage(), e1); } }
内容的提问来源于stack exchange,提问作者Maik Costa

