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
相关产品推荐
相关产品推荐

