You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何仅用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 06:36:50