Spring Boot测试@PostConstruct的init()方法遇空指针异常求助
解决Spring Boot单元测试中@Autowired注入空指针的问题
首先,你遇到的空指针异常核心原因是:你的测试类用错了注解。@SpringBootApplication是用来标记Spring Boot应用的主启动类的,不是测试类的注解——Spring测试框架不会用这个注解来初始化测试上下文,所以@Autowired的userIdService根本没有被实例化,自然会抛出NullPointerException。
下面给你两种可行的解决方案,分别对应集成测试和纯单元测试场景:
方案一:集成测试(加载完整Spring上下文)
如果你想测试真实的Spring环境下UserIdService的初始化行为(包括真实加载CSV文件),可以用@SpringBootTest注解标记测试类。这个注解会启动完整的Spring上下文,自动注入你的UserIdService实例,而且@PostConstruct修饰的init()方法会在Bean初始化时自动执行,不需要你手动调用。
修改后的测试类代码如下:
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest class UserIdServiceTest { @Autowired private UserIdService userIdService; @Test void testInit_ValidRecordsLoaded() { // 验证CSV文件中的有效记录是否被正确加载 // 假设你的测试CSV里有一个有效号码"1234567890" String validNumber = PhoneNumberHelper.convertPhoneNumber("1234567890"); assertTrue(userIdService.contains(validNumber)); // 验证加载的记录数符合预期(比如CSV里有3条有效记录) assertEquals(3, userIdService.get(validNumber).size()); } @Test void testAdd_NewRecord() { UserId newRecord = new UserId("Alice", "9876543210", "personal"); assertTrue(userIdService.add(newRecord)); assertTrue(userIdService.contains("9876543210")); } // 其他testGet、testContains方法可以类似编写 }
方案二:纯单元测试(不依赖Spring上下文)
如果想更高效地测试init()方法的逻辑(比如验证有效/无效号码的处理、CSV解析逻辑),可以用Mockito模拟ResourceLoader依赖,不需要启动整个Spring容器,测试速度更快。
Spring Boot的spring-boot-starter-test已经包含了Mockito,直接用即可:
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import java.io.ByteArrayInputStream; import java.io.InputStream; import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; @ExtendWith(MockitoExtension.class) class UserIdServiceUnitTest { @Mock private ResourceLoader resourceLoader; // 模拟ResourceLoader依赖 @InjectMocks private UserIdService userIdService; // 自动注入模拟的依赖到UserIdService @Test void testInit_LoadsValidAndSkipsInvalidRecords() throws Exception { // 构造测试用的CSV内容:一行有效号码,一行无效号码 String testCsvContent = "1234567890,work,John Doe\ninvalid-phone,home,Jane Doe"; InputStream testInputStream = new ByteArrayInputStream(testCsvContent.getBytes()); // 模拟ResourceLoader返回的资源 Resource mockResource = mock(Resource.class); when(mockResource.getInputStream()).thenReturn(testInputStream); when(resourceLoader.getResource("classpath:data/userid-data.csv")).thenReturn(mockResource); // 调用init方法 userIdService.init(); // 验证有效号码被正确加载 String convertedValidNumber = PhoneNumberHelper.convertPhoneNumber("1234567890"); assertTrue(userIdService.contains(convertedValidNumber)); assertEquals(1, userIdService.get(convertedValidNumber).size()); // 验证无效号码被跳过 assertFalse(userIdService.contains(PhoneNumberHelper.INVALID_INPUT)); } @Test void testAdd_DuplicateContextReturnsFalse() { UserId firstRecord = new UserId("John", "1234567890", "work"); userIdService.add(firstRecord); // 尝试添加相同号码和上下文的记录 UserId duplicateRecord = new UserId("Jane", "1234567890", "work"); assertFalse(userIdService.add(duplicateRecord)); } }
关键注意点
- 不要在测试类上使用
@SpringBootApplication,测试类的正确注解是@SpringBootTest(集成测试)或@ExtendWith(MockitoExtension.class)(单元测试)。 - 集成测试中,
@PostConstruct的init()方法会自动执行,不需要手动调用userIdService.init(),否则会重复执行初始化逻辑。 - 单元测试中,通过Mockito模拟外部依赖(比如
ResourceLoader),可以完全控制输入,专注测试业务逻辑本身。
内容的提问来源于stack exchange,提问作者maciekgt3
相关产品推荐
相关产品推荐

