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

Mockito模拟RestTemplate失败:无法Mock GitHubApi中RestTemplate Exchange方法的返回结果

Mockito模拟RestTemplate失败:无法Mock GitHubApi中RestTemplate Exchange方法的返回结果

看起来你遇到的问题核心有两个,咱们一步步拆解解决:

问题1:RestTemplate完全没用到你Mock的实例(最关键原因)

你看你的GitHubApi类里,getUserRepos方法每次调用都会自己new一个全新的RestTemplate:

restTemplate = new RestTemplate();

而你测试里用@InjectMocks想注入Mock的RestTemplate,但这个类根本没留注入入口——每次调用方法都会创建新实例,完全和你Mock的那个对象没关系!这就是为什么Mock完全不生效,实际还是调用了真实的GitHub API,拿到了真实的11个仓库,和你期望的1个对不上。

修复方案:改造GitHubApi,让RestTemplate可注入

推荐用构造函数注入(依赖不可变,更安全),改造后的代码如下:

@Component
public class GitHubApi {
    private final RestTemplate restTemplate;
    private static final String GITHUB_API_URL = "https://api.github.com"; // 假设你有这个常量

    // 构造函数注入RestTemplate
    public GitHubApi(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public List<GitHubRepository> getUserRepos(String user) {
        // 删掉原来的restTemplate = new RestTemplate();这一行
        HttpHeaders headers = new HttpHeaders();
        addHeaders(headers);

        HttpEntity<?> entity = new HttpEntity<>(headers);

        ResponseEntity<List<GitHubRepository>> response = restTemplate.exchange(
                GITHUB_API_URL + "/users/" + user + "/repos",
                HttpMethod.GET, entity,
                new ParameterizedTypeReference<List<GitHubRepository>>() {});
        return response.getBody();
    }

    // 你的addHeaders方法这里省略...
}

如果要兼容旧代码,也可以用Setter注入,但一定要删掉方法内new RestTemplate的逻辑:

@Component
@Setter
public class GitHubApi {
    private RestTemplate restTemplate;

    // 无参构造留空,或者初始化默认RestTemplate(用于生产环境)
    public GitHubApi() {
        this.restTemplate = new RestTemplate();
    }

    // 提供带参数的构造函数,方便测试注入Mock
    public GitHubApi(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    // 剩下的方法逻辑不变,删掉restTemplate = new RestTemplate();这一行
}

问题2:ParameterizedTypeReference的匹配会失败

就算解决了上面的问题,你测试里的eq(new ParameterizedTypeReference<List<GitHubRepository>>() {})也会匹配失败——因为每次new这个匿名类都是不同的实例,eq是按对象引用相等判断的,Mockito会认为调用参数和你Mock的参数不匹配。

修复方案:改用宽松的匹配规则

把测试里的匹配逻辑改成:

when(restTemplate.exchange(
        eq(GITHUB_API_URL + "/users/" + username + "/repos"),
        eq(HttpMethod.GET),
        any(HttpEntity.class),
        any(ParameterizedTypeReference.class) // 用any匹配,而不是eq
)).thenReturn(mockResponse);

如果需要更精确的匹配,可以用argThat判断类型:

when(restTemplate.exchange(
        eq(GITHUB_API_URL + "/users/" + username + "/repos"),
        eq(HttpMethod.GET),
        any(HttpEntity.class),
        argThat(typeRef -> 
            typeRef.getType().getTypeName().equals(List.class.getName() + "<" + GitHubRepository.class.getName() + ">")
        )
)).thenReturn(mockResponse);

修正后的完整测试代码

别忘了给测试类加Mockito的扩展注解,不然@Mock和@InjectMocks不会生效:

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.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class) // JUnit 5用这个,JUnit 4用@RunWith(MockitoJUnitRunner.class)
public class GitHubApiTest {
    @Mock
    private RestTemplate restTemplate;

    @InjectMocks
    private GitHubApi gitHubApi;

    private static final String GITHUB_API_URL = "https://api.github.com";

    @Test
    public void getUserRepos_ShouldReturnRepositories() {
        String username = "user";

        GitHubRepository mockedRepository = GitHubRepository.builder()
                .id(1L)
                .name("repo1")
                .fullName("user/repo1")
                .build();
        List<GitHubRepository> expected = List.of(mockedRepository);

        ResponseEntity<List<GitHubRepository>> mockResponse = new ResponseEntity<>(expected, HttpStatus.OK);

        when(restTemplate.exchange(
                eq(GITHUB_API_URL + "/users/" + username + "/repos"),
                eq(HttpMethod.GET),
                any(HttpEntity.class),
                any(ParameterizedTypeReference.class)
        )).thenReturn(mockResponse);

        List<GitHubRepository> repositories = gitHubApi.getUserRepos(username);

        assertNotNull(repositories);
        assertEquals(1, repositories.size());
        assertEquals("repo1", repositories.getFirst().getName());
    }
}

额外小提醒

  • 如果你用的是JUnit 4,记得把@ExtendWith(MockitoExtension.class)换成@RunWith(MockitoJUnitRunner.class)。
  • 如果你在Spring Boot环境下,也可以用@SpringBootTest配合@MockBean来Mock RestTemplate,但纯单元测试用Mockito更轻量。

备注:内容来源于stack exchange,提问作者pmalys

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 09:38:05