Spring RestTemplate.postForEntity 的 Mockito 测试抛出 IllegalArgumentException:URI 不是绝对的
Spring Mockito test of RestTemplate.postForEntity throws IllegalArgumentException: URI is not absolute
我的 Controller 调用该服务以获取有关汽车的 post 信息,如下所示,它工作正常。但是,我的单元测试因 IllegalArgumentException: URI is not absolute 异常而失败,SO 上 post 的 none 能够提供帮助。
这是我的控制器
@RestController
@RequestMapping("/cars")
public class CarController {
@Autowired
CarService carService;
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<CarResponse> getCar(@RequestBody CarRequest carRequest, @RequestHeader HttpHeaders httpHeaders) {
ResponseEntity<CarResponse> carResponse = carService.getCard(carRequest, httpHeaders);
return carResponse;
}
}
这是我的服务class:
@Service
public class MyServiceImpl implements MyService {
@Value("${myUri}")
private String uri;
public void setUri(String uri) { this.uri = uri; }
@Override
public ResponseEntity<CarResponse> postCar(CarRequest carRequest, HttpHeaders httpHeaders) {
List<String> authHeader = httpHeaders.get("authorization");
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", authHeader.get(0));
HttpEntity<CarRequest> request = new HttpEntity<CarRequest>(carRequest, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<CarResponse> carResponse = restTemplate.postForEntity(uri, request, CarResponse.class);
return cardResponse;
}
}
但是,我无法让我的单元测试工作。下面的测试抛出 IllegalArgumentException: URI is not absolute exception:
public class CarServiceTest {
@InjectMocks
CarServiceImpl carServiceSut;
@Mock
RestTemplate restTemplateMock;
CardResponse cardResponseFake = new CardResponse();
@BeforeEach
void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
cardResponseFake.setCarVin(12345);
}
@Test
final void test_GetCars() {
// Arrange
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", anyString());
ResponseEntity<CarResponse> carResponseEntity = new ResponseEntity(carResponseFake, HttpStatus.OK);
String uri = "http://FAKE/URI/myapi/cars";
carServiceSut.setUri(uri);
when(restTemplateMock.postForEntity(
eq(uri),
Mockito.<HttpEntity<CarRequest>> any(),
Mockito.<Class<CarResponse>> any()))
.thenReturn(carResponseEntity);
// Act
**// NOTE: Calling this requires real uri, real authentication,
// real database which is contradicting with mocking and makes
// this an integration test rather than unit test.**
ResponseEntity<CarResponse> carResponseMock = carServiceSut.getCar(carRequestFake, headers);
// Assert
assertEquals(carResponseEntity.getBody().getCarVin(), 12345);
}
}
更新 1
我想通了为什么抛出“Uri 不是绝对的”执行。这是因为在我上面的 carService
中,我使用 @Value
从 application.properties
文件中注入 uri
,但在单元测试中,没有注入。
所以,我添加了 public 属性 以便能够设置它并更新了上面的代码,但后来我发现 uri
必须是 real uri 到 real 后端,需要 real 数据库。
换句话说,如果我传递的 uri
是假的 uri,上面对 carServiceSut.getCar
的调用将失败,这意味着这会将测试变成集成测试。
这与在单元测试中使用模拟相矛盾。
我不想调用真正的后端,restTemplateMock
应该被模拟并注入到 carServiceSut
中,因为它们分别被注释为 @Mock
和 @InjectMock
。因此,它应该保持单元测试并被隔离,而不需要调用真正的后端。我感觉 Mockito 和 RestTemplate 不能很好地协同工作。
尝试将 URI 更改为
String uri = "http://some/fake/url";
您需要正确构建被测系统。
当前,MyServiceImpl.uri
为空。
更重要的是,你mock的RestTemplate并没有被注入到任何地方,你在被测方法中构造了一个新的RestTemplate。
由于Mockito不支持部分注入,需要在测试中手动构建实例
我会:
使用构造函数注入同时注入 restTemplate 和 uri:
@Service
public class MyServiceImpl implements MyService {
private RestTemplate restTemplate;
private String uri;
public MyServiceImpl(RestTemplate restTemplate, @Value("${myUri}") uri) {
this.restTemplate = restTemplate;
this.uri = uri;
}
手动构建实例:
- 删除@Mock 和@InjectMocks
- 挂机Mockito.initMocks通话
- 在测试中使用Mockito.mock和构造函数
public class CarServiceTest {
public static String TEST_URI = "YOUR_URI";
RestTemplate restTemplateMock = Mockito.mock(RestTemplate.class);
CarServiceImpl carServiceSut = new CarServiceImpl(restTemplateMock, TEST_URI):
}
在被测方法中删除 restTemplate
的创建。
如果需要,添加一个配置 class 提供 RestTemplate bean(对于应用程序,测试不需要):
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
注意 RestTemplate 是线程安全的,每个应用一个实例就足够了:Is RestTemplate thread safe?
我的 Controller 调用该服务以获取有关汽车的 post 信息,如下所示,它工作正常。但是,我的单元测试因 IllegalArgumentException: URI is not absolute 异常而失败,SO 上 post 的 none 能够提供帮助。
这是我的控制器
@RestController
@RequestMapping("/cars")
public class CarController {
@Autowired
CarService carService;
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<CarResponse> getCar(@RequestBody CarRequest carRequest, @RequestHeader HttpHeaders httpHeaders) {
ResponseEntity<CarResponse> carResponse = carService.getCard(carRequest, httpHeaders);
return carResponse;
}
}
这是我的服务class:
@Service
public class MyServiceImpl implements MyService {
@Value("${myUri}")
private String uri;
public void setUri(String uri) { this.uri = uri; }
@Override
public ResponseEntity<CarResponse> postCar(CarRequest carRequest, HttpHeaders httpHeaders) {
List<String> authHeader = httpHeaders.get("authorization");
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", authHeader.get(0));
HttpEntity<CarRequest> request = new HttpEntity<CarRequest>(carRequest, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<CarResponse> carResponse = restTemplate.postForEntity(uri, request, CarResponse.class);
return cardResponse;
}
}
但是,我无法让我的单元测试工作。下面的测试抛出 IllegalArgumentException: URI is not absolute exception:
public class CarServiceTest {
@InjectMocks
CarServiceImpl carServiceSut;
@Mock
RestTemplate restTemplateMock;
CardResponse cardResponseFake = new CardResponse();
@BeforeEach
void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
cardResponseFake.setCarVin(12345);
}
@Test
final void test_GetCars() {
// Arrange
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", anyString());
ResponseEntity<CarResponse> carResponseEntity = new ResponseEntity(carResponseFake, HttpStatus.OK);
String uri = "http://FAKE/URI/myapi/cars";
carServiceSut.setUri(uri);
when(restTemplateMock.postForEntity(
eq(uri),
Mockito.<HttpEntity<CarRequest>> any(),
Mockito.<Class<CarResponse>> any()))
.thenReturn(carResponseEntity);
// Act
**// NOTE: Calling this requires real uri, real authentication,
// real database which is contradicting with mocking and makes
// this an integration test rather than unit test.**
ResponseEntity<CarResponse> carResponseMock = carServiceSut.getCar(carRequestFake, headers);
// Assert
assertEquals(carResponseEntity.getBody().getCarVin(), 12345);
}
}
更新 1
我想通了为什么抛出“Uri 不是绝对的”执行。这是因为在我上面的 carService
中,我使用 @Value
从 application.properties
文件中注入 uri
,但在单元测试中,没有注入。
所以,我添加了 public 属性 以便能够设置它并更新了上面的代码,但后来我发现 uri
必须是 real uri 到 real 后端,需要 real 数据库。
换句话说,如果我传递的 uri
是假的 uri,上面对 carServiceSut.getCar
的调用将失败,这意味着这会将测试变成集成测试。
这与在单元测试中使用模拟相矛盾。
我不想调用真正的后端,restTemplateMock
应该被模拟并注入到 carServiceSut
中,因为它们分别被注释为 @Mock
和 @InjectMock
。因此,它应该保持单元测试并被隔离,而不需要调用真正的后端。我感觉 Mockito 和 RestTemplate 不能很好地协同工作。
尝试将 URI 更改为
String uri = "http://some/fake/url";
您需要正确构建被测系统。
当前,MyServiceImpl.uri
为空。
更重要的是,你mock的RestTemplate并没有被注入到任何地方,你在被测方法中构造了一个新的RestTemplate。
由于Mockito不支持部分注入,需要在测试中手动构建实例
我会:
使用构造函数注入同时注入 restTemplate 和 uri:
@Service
public class MyServiceImpl implements MyService {
private RestTemplate restTemplate;
private String uri;
public MyServiceImpl(RestTemplate restTemplate, @Value("${myUri}") uri) {
this.restTemplate = restTemplate;
this.uri = uri;
}
手动构建实例:
- 删除@Mock 和@InjectMocks
- 挂机Mockito.initMocks通话
- 在测试中使用Mockito.mock和构造函数
public class CarServiceTest {
public static String TEST_URI = "YOUR_URI";
RestTemplate restTemplateMock = Mockito.mock(RestTemplate.class);
CarServiceImpl carServiceSut = new CarServiceImpl(restTemplateMock, TEST_URI):
}
在被测方法中删除 restTemplate
的创建。
如果需要,添加一个配置 class 提供 RestTemplate bean(对于应用程序,测试不需要):
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
注意 RestTemplate 是线程安全的,每个应用一个实例就足够了:Is RestTemplate thread safe?