使用RestTemplate的文件下载接口单元测试出现404错误求助
首先得明确:你遇到的404不是Controller接口本身返回的,而是Controller内部用RestTemplate调用外部接口时,测试环境下目标地址不存在导致的,核心原因有几个:
核心问题根源
RestTemplate未依赖注入,Mock完全无效
你的Controller代码里直接在方法内new RestTemplate()创建实例,而不是通过构造函数或@Autowired注入。这就导致测试中@Mock的mockRestTemplate根本不会被用到,测试时依然会发起真实的HTTP请求到PSASConfig.PREFIX_URL_API_PSAS,而测试环境下这个地址大概率不存在,自然返回404。Mock方法的参数类型不匹配
就算RestTemplate被正确注入,你测试里mockexchange方法时,最后一个参数写了ArgumentMatchers.<Class<String>>any(),但原代码中实际传入的是Resource.class,类型不匹配会让Mock规则不生效,同样会触发真实请求。Mock返回的ResponseEntity不完整
当前Mock返回的ResponseEntity<Resource>没有携带有效的Resource实例,后续调用result.getBody().getInputStream()会直接抛出NullPointerException。
分步解决方案
第一步:重构Controller,让RestTemplate可注入
修改Controller,通过构造函数注入RestTemplate,替代方法内的硬编码实例化:
@Api(value = "Telecharger fichier sas") @RestController @RequestMapping(PUBLIC_BASE_URL) public class TelechargerFichierRestController extends BaseController { private static final int BUFFER_SIZE = 4096; private static final String HEADER_NAME = "REMOTE_USER"; // 注入RestTemplate实例 private final RestTemplate restTemplate; public TelechargerFichierRestController(RestTemplate restTemplate) { this.restTemplate = restTemplate; } @ApiOperation(value = "Telecharger fichier dans le répertoire download") @RequestMapping(value = "/sasDl/download", method = RequestMethod.GET) public void downloadFichier(final HttpServletRequest request, final HttpServletResponse response) throws IOException { InputStream inputStream = null; int bytesRead; // 不再手动new,使用注入的实例 HttpHeaders headers = new HttpHeaders(); String remoteUser = request.getHeader(HEADER_NAME); headers.add(HEADER_NAME, remoteUser); HttpEntity<String> entity = new HttpEntity<>("parameters", headers); ResponseEntity<Resource> result = restTemplate.exchange(PSASConfig.PREFIX_URL_API_PSAS, HttpMethod.GET, entity, Resource.class); OutputStream outStream = response.getOutputStream(); try { inputStream = result.getBody().getInputStream(); } catch (IOException e) { throw new IOException(e); } byte[] buffer = new byte[BUFFER_SIZE]; while ((bytesRead = inputStream.read(buffer)) != -1) { outStream.write(buffer, 0, bytesRead); } inputStream.close(); outStream.close(); } }
如果你的项目还没配置RestTemplate的Bean,需要在配置类中添加:
@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
第二步:修正测试代码的Mock逻辑
修改测试代码,确保Mock的参数类型匹配,并且返回有效的Resource实例:
@RunWith(MockitoJUnitRunner.class) public class TelechargerFichierRestTest { @Mock private HttpServletRequest request; @Mock private HttpServletResponse response; @Mock private RestTemplate mockRestTemplate; @Mock private Resource mockResource; @Mock private InputStream mockInputStream; @InjectMocks private TelechargerFichierRestController restController; @Test public void testDownloadFichier() throws Exception { // 模拟请求头 Mockito.when(request.getHeader("REMOTE_USER")).thenReturn("test-user"); // 模拟文件流读取行为(直接返回-1表示读取完毕) Mockito.when(mockInputStream.read(Mockito.any(byte[].class))).thenReturn(-1); Mockito.when(mockResource.getInputStream()).thenReturn(mockInputStream); // 正确匹配exchange方法的参数类型 Mockito.when(mockRestTemplate.exchange( ArgumentMatchers.eq(PSASConfig.PREFIX_URL_API_PSAS), ArgumentMatchers.eq(HttpMethod.GET), ArgumentMatchers.any(HttpEntity.class), ArgumentMatchers.eq(Resource.class) )).thenReturn(new ResponseEntity<>(mockResource, HttpStatus.OK)); // 模拟响应输出流 OutputStream mockOutputStream = Mockito.mock(OutputStream.class); Mockito.when(response.getOutputStream()).thenReturn(mockOutputStream); // 执行测试方法 restController.downloadFichier(request, response); // 验证关键交互 Mockito.verify(request).getHeader("REMOTE_USER"); Mockito.verify(response).getOutputStream(); Mockito.verify(mockInputStream).read(Mockito.any(byte[].class)); Mockito.verify(mockOutputStream).write(Mockito.any(byte[].class), Mockito.eq(0), Mockito.eq(-1)); Mockito.verify(mockInputStream).close(); Mockito.verify(mockOutputStream).close(); } }
这样修改后,测试会完全Mock掉外部接口的调用,不会再发起真实请求,也就不会出现404错误了。
内容的提问来源于stack exchange,提问作者Djbapt

