如何仅用Mockito为含内部RestTemplate的SenderServiceImpl编写JUnit测试?
解决SenderServiceImpl的Mockito测试问题
看起来你遇到的核心问题是类内部直接new的RestTemplate无法被Mockito Spy替换——因为这个RestTemplate实例是在类初始化或者方法执行时创建的,Mockito没法拦截这个创建过程来替换它。下面我给你两种方案,优先推荐重构代码(这也是更符合Spring最佳实践的方式),然后是对应的Mockito测试写法:
第一步:重构代码,让RestTemplate可注入
首先,我们需要把原来硬编码的new RestTemplate()改成依赖注入的形式,这样测试时可以轻松传入Mock对象。同时建议把url、headers这些配置也改成可注入的,让测试更灵活:
@Service public class SenderServiceImpl implements SenderService{ private final Logger logger; private final RestTemplate restTemplate; private final String notificationUrl; private final HttpHeaders headers; // 通过构造器注入所有依赖(Spring 4.3+可以省略@Autowired) public SenderServiceImpl(Logger logger, RestTemplate restTemplate, @Value("${notification.api.url}") String notificationUrl) { this.logger = logger; this.restTemplate = restTemplate; this.notificationUrl = notificationUrl; // 初始化headers的逻辑移到构造器,或者也可以做成配置Bean注入 this.headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); // 其他header配置比如授权信息等 } @Override public void sendNotification(SenderNotification notification) { try { HttpEntity<SenderNotification> sendRequestBody = new HttpEntity<>(notification, headers); ResponseEntity<String> response = restTemplate.postForEntity(notificationUrl, sendRequestBody, String.class); // 可以添加响应状态码的判断逻辑,比如如果不是2xx就记录日志 } catch (RestClientException e) { logger.error("Failed to send notification", e); } } }
第二步:编写Mockito测试用例
现在依赖都是可注入的,我们可以用Mockito轻松Mock RestTemplate和Logger,测试sendNotification方法的逻辑:
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.http.HttpEntity; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class SenderServiceImplTest { // Mock RestTemplate,让它返回我们预设的结果或抛出异常 @Mock private RestTemplate restTemplate; // Mock Logger,验证日志是否正确输出 @Mock private Logger logger; // 测试用的URL,和配置文件保持一致或者自定义 private final String TEST_NOTIFICATION_URL = "http://test-api.example.com/notify"; // 手动构造被测试对象,传入Mock依赖和测试URL private SenderServiceImpl senderService = new SenderServiceImpl(logger, restTemplate, TEST_NOTIFICATION_URL); @Test void sendNotification_ShouldSuccessfullyCallRestTemplate() { // 1. 准备测试数据 SenderNotification testNotification = new SenderNotification(); testNotification.setUserId("123"); testNotification.setContent("Test notification content"); // 2. 模拟RestTemplate的postForEntity方法返回成功响应 ResponseEntity<String> mockSuccessResponse = ResponseEntity.ok("Notification sent"); when(restTemplate.postForEntity( eq(TEST_NOTIFICATION_URL), any(HttpEntity.class), eq(String.class) )).thenReturn(mockSuccessResponse); // 3. 执行被测试方法 senderService.sendNotification(testNotification); // 4. 验证RestTemplate的post方法被调用了一次 verify(restTemplate, times(1)).postForEntity( eq(TEST_NOTIFICATION_URL), any(HttpEntity.class), eq(String.class) ); // 验证错误日志没有被调用 verify(logger, never()).error(anyString(), any(Exception.class)); } @Test void sendNotification_WhenRestCallFails_ShouldLogError() { // 1. 准备测试数据 SenderNotification testNotification = new SenderNotification(); // 2. 模拟RestTemplate抛出异常 RestClientException mockException = new RestClientException("Connection timeout"); when(restTemplate.postForEntity( eq(TEST_NOTIFICATION_URL), any(HttpEntity.class), eq(String.class) )).thenThrow(mockException); // 3. 执行被测试方法 senderService.sendNotification(testNotification); // 4. 验证错误日志被正确调用 verify(logger, times(1)).error(eq("Failed to send notification"), eq(mockException)); } }
为什么之前Spy没成功?
你之前尝试Spy SenderServiceImpl但没效果,是因为类内部直接new的对象不受Mockito控制。Spy只能替换对象中已经存在的依赖,但你的RestTemplate是在类初始化时创建的真实实例,Mockito没法拦截这个创建过程来替换它。重构后我们把依赖外部化,就能轻松用Mock对象替换真实的RestTemplate了。
如果实在不能重构代码(不推荐),那可能需要用到PowerMock来拦截new RestTemplate()的调用,但PowerMock不属于Mockito核心库,而且会增加测试复杂度,所以优先推荐上面的重构方案。
内容的提问来源于stack exchange,提问作者Roshni
相关产品推荐
相关产品推荐

