項目場景:
在練習黑馬點評的邏輯過期解決緩存擊穿時,編寫了一個預熱緩存數據的單元測試
@SpringBootTest
public class HmDianPingApplicationTests {@Resourceprivate ShopServiceImpl shopService;@Testpublic void testSaveShop() throws InterruptedException {shopService.saveShop2Redis(1L,10L);}}
運行時報空指針異常
java.lang.NullPointerException: Cannot invoke "com.hmdp.service.impl.ShopServiceImpl.saveShop2Redis(java.lang.Long, java.lang.Long)" because "this.shopService" is null
原因分析:
這是由于選擇的@Test注解選錯了,應該選擇org.junit.jupiter.api的那一個而不是org.junit的那一個。前者對應JUnit 5單元測試框架 后者對應JUnit 4單元測試框架。兩者的對比如下:
JUnit 4: org.junit.Test
-
用途: JUnit 4 提供的基礎單元測試注解,用于標記測試方法。
-
特點: 不支持重復測試、參數化測試等高級功能(需要額外擴展)。
-
測試方法必須是 public 的。(這也是為什么如果用了這個注解,測試方法沒加public的話就沒有啟動運行的綠色箭頭的原因)
-
必須配合 JUnit 4 的測試運行器使用(如 @RunWith)。
JUnit 5: org.junit.jupiter.api.Test
-
用途: JUnit 5(JUnit Jupiter)提供的現代化測試注解,是 JUnit 的全新版本。
-
特點: 無需方法是public(包級權限或 protected 也可)。
-
支持更多功能,如重復測試(@RepeatedTest)、參數化測試(@ParameterizedTest)。
更強的擴展性,支持條件測試(如 @EnabledIf)。 -
無需 @RunWith,改用 @ExtendWith 或直接支持 Spring的測試環境。
JUnit 4 中,Spring 和測試的整合依賴于 @RunWith(SpringRunner.class)(或較早的 SpringJUnit4ClassRunner)。
如果沒有添加 @RunWith(SpringRunner.class) 注解,Spring 的測試上下文不會加載,@SpringBootTest 的功能無法生效。
結果是,@Resource(或 @Autowired)的依賴注入不會生效,導致 shopService 為 null。
JUnit 5 使用 SpringExtension(通過 @ExtendWith(SpringExtension.class) 內部實現)來加載 Spring 測試上下文。
@SpringBootTest 會自動應用 SpringExtension,所以無需顯式聲明 @ExtendWith,Spring 上下文會被正確加載。
因此,在 JUnit 4 中,如果你遺漏了 @RunWith(SpringRunner.class),Spring 的功能不會生效,而在 JUnit 5 中,這一步是自動完成的。
所以為什么 shopService 是 null呢?
shopService 的注入依賴于 Spring 容器。如果 Spring 測試上下文沒有正確加載:
@Resource 或 @Autowired 不會生效。
導致 shopService 沒有被注入,值為 null。
調用 shopService.saveShop2Redis(...) 時自然會拋出 NullPointerException。
在 JUnit 4 中,如果沒有正確使用 @RunWith(SpringRunner.class):
Spring 測試上下文未加載,shopService 不會被注入。
這也是為什么用 JUnit 4 會拋出 NullPointerException,而 JUnit 5 不會的根本原因。
解決方案:
方法一 :修復JUnit 4的問題 ,在使用時添加@RunWith(SpringRunner.class)
@RunWith(SpringRunner.class) // 啟用 Spring 的測試功能
@SpringBootTest
public class HmDianPingApplicationTests {@Resourceprivate ShopServiceImpl shopService;@Testpublic void testSaveShop() {shopService.saveShop2Redis(1L, 10L);}
}
方法二:優先選用JUnit 5的Test注解