Java Eclipse中JUnit测试when...thenReturn失效致空指针问题
JUnit测试中@InjectMocks注入KeycloakService失败导致空指针异常
在JUnit测试的setUp方法中执行when(kc.getToken()).thenReturn(token);后,调用AasServiceImpl的update方法时触发空指针错误:
java.lang.NullPointerException: Cannot invoke "services.KeycloakService.getToken()" because "this.kc" is null
使用的依赖
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.mockito:mockito-core:5.14.2' testImplementation 'org.mockito:mockito-junit-jupiter:5.14.2'
已尝试的操作
- 执行
testMockInjection测试,所有断言全部通过; - 执行
testUpdate测试时,代码在AasServiceImpl的String zaToken = kc.getToken();行抛出空指针; - setUp方法中的日志正常打印,确认该方法已执行;
- 尝试用Spy替代Mock,问题未解决;
- 已确认导入正确注解,使用
@ExtendWith(MockitoExtension.class)、@BeforeEach、@InjectMocks,也试过MockitoAnnotations.initMocks(this),错误依旧。
服务实现代码
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; ... import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; ... import services.AasService; import services.KeycloakService; @Service public class AasServiceImpl implements AasService{ private final RestTemplate restTemplate; @Autowired private KeycloakService kc; @Value("${ass.server}") private String aasUri; @Value("${ass.id}") private String aasIdShort; public AasServiceImpl (RestTemplate restTemplate) { this.restTemplate = restTemplate; } @Override public void update(String applicationUri, String nodeId, JSONObject value) { System.out.println(applicationUri); System.out.println(nodeId); System.out.println(value); String submodelElementValueId = applicationUri.replace(":", "_") + nodeId.replace("i=", ""); System.out.println(submodelElementValueId); if (kc instanceof KeycloakServiceImpl) { System.out.println("This is original service."); } else { System.out.println("This is not the original service."); // 此处打印的是这个分支 } // 报错行 String zaToken = kc.getToken(); // 该行日志未打印 System.out.println(zaToken); try { updateValue("DefectDetectionSkill", "Inputs", submodelElementValueId, value, zaToken); } catch (Exception ex) { ex.printStackTrace(); } } }
测试代码
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; ... import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; ... import org.junit.jupiter.api.BeforeEach; import services.KeycloakService; // 这是一个接口 @ExtendWith(MockitoExtension.class) public class AasServiceImplTest { @Mock private KeycloakService kc; @Mock private RestTemplate restTemplate; @InjectMocks private AasServiceImpl aasService; @BeforeEach void setUp() { String token = "mockToken"; when(kc.getToken()).thenReturn(token); ResponseEntity<String> response= new ResponseEntity<>("", HttpStatus.OK); when(restTemplate.exchange(anyString(), any(), any(), eq(String.class))).thenReturn(response); System.out.println("set up runs"); // 该日志正常打印 } /* @Test void testMockInjection() { assertNotNull(kc, "KeycloakService mock is not injected!"); assertNotNull(restTemplate, "RestTemplate mock is not injected!"); assertNotNull(aasService, "AasServiceImpl is not initialized!"); } */ @Test void testUpdate() { // 准备测试数据 String applicationUri = "http://mock_uri"; String nodeId="i=ioanna"; JSONObject value = new JSONObject(); value.put("Value", 1234); // 执行测试方法 aasService.update(applicationUri, nodeId , value); System.out.println("Value: " + value.get("Value")); // 断言 assertNotNull(kc, "KeycloakService mock is not injected!"); // verify(kc, times(1)).getToken(); // verify(restTemplate, atLeastOnce()).exchange(anyString(), any(), any(), eq(String.class)); } }
问题原因与解决方案
原因
Mockito的@InjectMocks优先使用构造函数注入,但AasServiceImpl的构造函数仅接收RestTemplate,未包含KeycloakService,导致字段kc无法被自动注入。
解决方案
方法1:修改服务类使用全构造函数注入(推荐)
将所有依赖通过构造函数注入,这也是Spring官方推荐的依赖注入方式:
@Service public class AasServiceImpl implements AasService{ private final RestTemplate restTemplate; private final KeycloakService kc; @Value("${ass.server}") private String aasUri; @Value("${ass.id}") private String aasIdShort; // 构造函数注入所有依赖 public AasServiceImpl (RestTemplate restTemplate, KeycloakService kc) { this.restTemplate = restTemplate; this.kc = kc; } // 其余代码不变 }
修改后,@InjectMocks会自动将@Mock注解的kc和restTemplate通过构造函数注入到aasService中。
方法2:测试类中手动注入字段
如果无法修改服务类代码,可通过反射工具手动注入kc字段:
@BeforeEach void setUp() { String token = "mockToken"; when(kc.getToken()).thenReturn(token); ResponseEntity<String> response= new ResponseEntity<>("", HttpStatus.OK); when(restTemplate.exchange(anyString(), any(), any(), eq(String.class))).thenReturn(response); // 手动注入kc字段 ReflectionTestUtils.setField(aasService, "kc", kc); System.out.println("set up runs"); }
需导入org.springframework.test.util.ReflectionTestUtils,该类已包含在spring-boot-starter-test依赖中。
内容的提问来源于stack exchange,提问作者joan
相关产品推荐
相关产品推荐

