Mockito openMocks AutoCloseable in JUnit 5
IntelliJ IDEA 對 MockitoAnnotations.openMocks(this) 的警告:
‘AutoCloseable’ used without ‘try’-with-resources statement
原本寫法的問題
1
2
3
4
5
6
7
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
lenient().doNothing().when(projFormService).setValidateService(any(ValidateService.class));
lenient().doNothing().when(projFormService).setContractDao(any(ContractDao.class));
}
openMocks(this) 會初始化 @Mock、@Spy、@InjectMocks 等 Mockito annotation,同時回傳一個 AutoCloseable。這個物件代表 Mockito 幫這次測試建立的 mock session;測試結束時應該關閉,讓 Mockito 有機會清理資源與狀態。
但是 openMocks(this) 的回傳值被忽略了,所以「開了一個需要關閉的東西」,但測試沒有保留它,也沒有在測試結束時關閉。短期通常不會馬上壞,但這會讓測試生命週期不完整,IDE 才會提示 AutoCloseable 沒有被處理。
原本解法是看到專案已經有:
1
@ExtendWith(MockitoExtension.class)
就直接刪掉 MockitoAnnotations.openMocks(this)。
這在單純 @Mock 測試裡可能可行,但這個測試同時用了 @Spy、@InjectMocks,而且被測 service 裡面會呼叫 setter 建立真正的 DAO 或 service。測試靠下面這種寫法攔住 setter:
1
lenient().doNothing().when(projFormService).setAaFormDao(any(AaFormDao.class));
如果初始化順序變了,mock 沒有照原本方式注入,就會在測試中變成 NullPointerException。所以這裡不能只為了消除 IDE 警告就刪掉初始化程式。
為什麼改成這樣
最後採用的寫法是保留原本測試架構,但把 openMocks(this) 回傳的 AutoCloseable 存起來,並在 @AfterEach 關閉:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private AutoCloseable mocks;
@BeforeEach
public void setUp() {
mocks = MockitoAnnotations.openMocks(this);
lenient().doNothing().when(projFormService).setValidateService(any(ValidateService.class));
lenient().doNothing().when(projFormService).setContractDao(any(ContractDao.class));
lenient().doNothing().when(projFormService).setCommonContractService(any(CommonContractService.class));
lenient().doNothing().when(projFormService).setAaFormDao(any(AaFormDao.class));
lenient().doNothing().when(projFormService).setFormDeadlineEvaluator(any(FormDeadlineEvaluator.class));
}
@AfterEach
public void tearDown() throws Exception {
mocks.close();
}
這個寫法有幾個重點:
- 保留原本
openMocks(this)的初始化行為,不改變既有測試的 mock 注入順序。 - 補上
AutoCloseable關閉,讓 Mockito session 的生命週期完整。 - 沒有為了消除警告而重寫測試架構,避免把單純警告修正變成另一個測試重構。
如果是一個新測試,而且只需要標準 Mockito annotation,通常可以只用:
1
@ExtendWith(MockitoExtension.class)
然後不要再呼叫 MockitoAnnotations.openMocks(this)。
但如果是既有測試,而且它已經依賴手動初始化、spy、setter 攔截、或特殊注入順序,直接刪掉 openMocks 不一定安全。這次比較保險的做法,是先保留行為,再補齊關閉。
結論
IDE 的這個警告不是單純潔癖。openMocks(this) 回傳 AutoCloseable,就代表測試應該管理它的生命週期。
不過修正警告時,不能只看單行程式。要一起看測試原本怎麼建立 mock、service 內部會不會重新建立依賴、測試是不是靠 spy 攔 setter。這次的情境下,改成 AutoCloseable 欄位加上 @AfterEach close(),是對既有測試影響最小的修正。