JAVA resttemplate 和 retryTemplate 的 mockito 单元测试
JAVA mockito unit test for resttemplate and retryTemplate
我目前正在为以下方法编写单元测试
@Autowired
private RequestConfig requestConfig;
@Autowired
private RetryTemplate retryTemplate;
public ResponseEntity<String> makeGetServiceCall(String serviceUrl) throws Exception {
try {
return retryTemplate.execute(retryContext -> {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = requestConfig.createHttpHeaders();
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
ResponseEntity<String> response = restTemplate.exchange(serviceUrl, HttpMethod.GET, entity, String.class);
return response;
});
} catch (Exception e) {
throw new Exception("Generic exception while makeGetServiceCall due to" + e + serviceUrl);
}
}
更新方法:
@Autowired
private RequestConfig requestConfig;
@Autowired
private RetryTemplate retryTemplate;
@Autowired
private RestTemplate restTemplate;
public ResponseEntity<String> makeGetServiceCall(String serviceUrl) throws Exception {
try {
return retryTemplate.execute(retryContext -> {
HttpHeaders headers = requestConfig.createHttpHeaders();
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
ResponseEntity<String> response = restTemplate.exchange(serviceUrl, HttpMethod.GET, entity, String.class);
return response;
});
} catch (Exception e) {
throw new Exception("Generic exception while makeGetServiceCall due to" + e + serviceUrl);
}
}
我尝试了所有的可能性,但我无法做到正确。这是我的以下测试。
@Mock
private RestTemplate restTemplate;
@Mock
public RequestConfig requestConfig;
@InjectMocks
private RetryTemplate retryTemplate;
ServiceRequest serviceRequest;
@Test
public void makeGetServiceCall() throws Exception {
String url = "http://localhost:8080";
RetryTemplate mockRetryTemplate = Mockito.mock(RetryTemplate.class);
RestTemplate mockRestTemplate = Mockito.mock(RestTemplate.class);
ResponseEntity<String> myEntity = new ResponseEntity<>(HttpStatus.ACCEPTED);
Mockito.when(mockRetryTemplate.execute(ArgumentMatchers.any(RetryCallback.class), ArgumentMatchers.any(RecoveryCallback.class), ArgumentMatchers.any(RetryState.class))).thenReturn(myEntity);
Mockito.when(mockRestTemplate.exchange(
ArgumentMatchers.eq(url),
ArgumentMatchers.eq(HttpMethod.GET),
ArgumentMatchers.<HttpEntity<String>>any(),
ArgumentMatchers.<Class<String>>any())
).thenReturn(myEntity);
ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);
Assert.assertEquals(myEntity, response);
}
更新的测试用例:
@Mock
public RequestConfig requestConfig;
@Mock
private RestTemplate restTemplate;
@Mock
private RetryTemplate retryTemplate;
@InjectMocks
ServiceRequest serviceRequest;
@Test
public void makeGetServiceCall() throws Exception {
//given:
String url = "http://localhost:8080";
when(requestConfig.createHttpHeaders()).thenReturn(null);
ResponseEntity<String> myEntity = new ResponseEntity<>( HttpStatus.ACCEPTED);
when(retryTemplate.execute(any(RetryCallback.class), any(RecoveryCallback.class), any(RetryState.class))).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(/*here goes RetryContext but it's ignored in ServiceRequest*/null);
});
when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), eq(String.class)))
.thenReturn(myEntity);
//when:
ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);
//then:
assertEquals(myEntity, response);
}
我从方法调用中获得的响应对象 makeGetServiceCall
始终 return 为空。当我调试代码时,我在 return myEntity
的 resttemplate
模拟中看到异常 org.mockito.exceptions.misusing.WrongTypeOfReturnValue: ResponseEntity cannot be returned by toString() toString() should return String
错误
我不确定我错过了什么。
嗯,你犯了很多错误...
- 我确定您想用
@Mock
注释 private RetryTemplate retryTemplate;
,而不是 @InjectMocks
@InjectMocks
应该继续 ServiceRequest serviceRequest;
- 您在
mockRetryTemplate
和 mockRestTemplate
上定义了与 serviceRequest
无关的交互。相反,您应该使用带 @Mock
注释的字段来定义交互,因为它们被注入到您的被测对象中 (serviceRequest
)
- 此外,您通常不能模拟
RestTemplate
并将其注入到您的 ServiceRequest
中,因为您首先没有在 ServiceRequest
中对 RestTemplate
使用依赖注入=].您只需在 ServiceRequest.makeGetServiceCall
中实例化它的实例
- 您在第
Mockito.when(retryTemplate.execute(...
行定义了错误方法的交互。您的交互指定 RetryTemplate.execute(RetryCallback, RecoveryCallback, RetryState)
而您的 ServiceRequest
使用另一种方法 RetryTemplate.execute(RetryCallback)
- 你还应该注意到
RetryTemplate.execute
是最终的,所以你不能像 here 解释的那样在没有额外努力的情况下模拟它。通常,您应该更喜欢接口而不是 类,例如RestOperations
和 RetryOperations
分别优于 RestTemplate
和 RetryTemplate
,更加灵活。
也就是说,下面是解决您问题的工作测试。但是请注意从 ServiceRequest
中删除 RestTemplate restTemplate = new RestTemplate();
并使 restTemplate
成为一个字段,以便它被依赖注入。
@RunWith(MockitoJUnitRunner.class)
public class ServiceRequestTest {
@Mock
private RestTemplate restTemplate;
@Mock
public RequestConfig requestConfig;
@Mock
private RetryTemplate retryTemplate;
@InjectMocks
ServiceRequest serviceRequest;
@Test
public void makeGetServiceCall() throws Exception {
//given:
String url = "http://localhost:8080";
ResponseEntity<String> myEntity = new ResponseEntity<>(HttpStatus.ACCEPTED);
when(retryTemplate.execute(any(RetryCallback.class))).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(/*here goes RetryContext but it's ignored in ServiceRequest*/null);
});
when(restTemplate.exchange(eq(url), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class)))
.thenReturn(myEntity);
//when:
ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);
//then:
assertEquals(myEntity, response);
}
}
下面对我有效,否则,它总是返回 null
when(retryTemplate.execute(any(),any(),any())).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(null);
});
导入是
import static org.mockito.ArgumentMatchers.any;
通用解决方案:
Mockito.when(retryTemplate.execute(Matchers.any(),Matchers.any(),Matchers.any())).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgumentAt(0,Matchers.any());
return retry.doWithRetry(null);
});
对我有用!
@ExtendWith(MockitoExtension.class)
class RetryableRestClientTest {
@Mock
private RestTemplate restTemplate;
@Mock
private RetryTemplate retryTemplate;
@InjectMocks
private RetryableRestClient client;
@SuppressWarnings("rawtypes")
@Test
void test_get() {
String url = "https://faked-url";
ResponseEntity<String> expectedResponseEntity = new ResponseEntity<>(HttpStatus.OK);
Mockito.when(retryTemplate.execute(Mockito.any(), Mockito.any(), Mockito.any()))
.thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(null);
});
Mockito.when(restTemplate.exchange(Mockito.eq(url), Mockito.eq(HttpMethod.GET), Mockito.any(HttpEntity.class), Mockito.eq(String.class)))
.thenReturn(expectedResponseEntity);
ResponseEntity<String> actualResponseEntity = client.get(url);
Assertions.assertEquals(expectedResponseEntity, actualResponseEntity);
}
}
@Component
public class RetryableRestClient {
@Autowired
private RetryTemplate retryTemplate;
@Autowired
private RestTemplate restTemplate;
private HttpHeaders fakeHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
// fake browser's behavior
headers.add("authority", "m.nowscore.com");
headers.add("cache-control", "max-age=0");
headers.add("sec-ch-ua", "\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"91\", \"Chromium\";v=\"91\"");
headers.add("sec-ch-ua-mobile", "?0");
headers.add("upgrade-insecure-requests", "1");
headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36");
headers.add("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
headers.add("sec-fetch-site", "none");
headers.add("sec-fetch-mode", "navigate");
headers.add("sec-fetch-user", "?1");
headers.add("sec-fetch-dest", "document");
headers.add("accept-language", "en-US,en;q=0.9");
return headers;
}
public final ResponseEntity<String> get(String url) {
return retryTemplate.execute(context -> restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, fakeHttpHeaders()), String.class));
}
}
我目前正在为以下方法编写单元测试
@Autowired
private RequestConfig requestConfig;
@Autowired
private RetryTemplate retryTemplate;
public ResponseEntity<String> makeGetServiceCall(String serviceUrl) throws Exception {
try {
return retryTemplate.execute(retryContext -> {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = requestConfig.createHttpHeaders();
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
ResponseEntity<String> response = restTemplate.exchange(serviceUrl, HttpMethod.GET, entity, String.class);
return response;
});
} catch (Exception e) {
throw new Exception("Generic exception while makeGetServiceCall due to" + e + serviceUrl);
}
}
更新方法:
@Autowired
private RequestConfig requestConfig;
@Autowired
private RetryTemplate retryTemplate;
@Autowired
private RestTemplate restTemplate;
public ResponseEntity<String> makeGetServiceCall(String serviceUrl) throws Exception {
try {
return retryTemplate.execute(retryContext -> {
HttpHeaders headers = requestConfig.createHttpHeaders();
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
ResponseEntity<String> response = restTemplate.exchange(serviceUrl, HttpMethod.GET, entity, String.class);
return response;
});
} catch (Exception e) {
throw new Exception("Generic exception while makeGetServiceCall due to" + e + serviceUrl);
}
}
我尝试了所有的可能性,但我无法做到正确。这是我的以下测试。
@Mock
private RestTemplate restTemplate;
@Mock
public RequestConfig requestConfig;
@InjectMocks
private RetryTemplate retryTemplate;
ServiceRequest serviceRequest;
@Test
public void makeGetServiceCall() throws Exception {
String url = "http://localhost:8080";
RetryTemplate mockRetryTemplate = Mockito.mock(RetryTemplate.class);
RestTemplate mockRestTemplate = Mockito.mock(RestTemplate.class);
ResponseEntity<String> myEntity = new ResponseEntity<>(HttpStatus.ACCEPTED);
Mockito.when(mockRetryTemplate.execute(ArgumentMatchers.any(RetryCallback.class), ArgumentMatchers.any(RecoveryCallback.class), ArgumentMatchers.any(RetryState.class))).thenReturn(myEntity);
Mockito.when(mockRestTemplate.exchange(
ArgumentMatchers.eq(url),
ArgumentMatchers.eq(HttpMethod.GET),
ArgumentMatchers.<HttpEntity<String>>any(),
ArgumentMatchers.<Class<String>>any())
).thenReturn(myEntity);
ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);
Assert.assertEquals(myEntity, response);
}
更新的测试用例:
@Mock
public RequestConfig requestConfig;
@Mock
private RestTemplate restTemplate;
@Mock
private RetryTemplate retryTemplate;
@InjectMocks
ServiceRequest serviceRequest;
@Test
public void makeGetServiceCall() throws Exception {
//given:
String url = "http://localhost:8080";
when(requestConfig.createHttpHeaders()).thenReturn(null);
ResponseEntity<String> myEntity = new ResponseEntity<>( HttpStatus.ACCEPTED);
when(retryTemplate.execute(any(RetryCallback.class), any(RecoveryCallback.class), any(RetryState.class))).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(/*here goes RetryContext but it's ignored in ServiceRequest*/null);
});
when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), eq(String.class)))
.thenReturn(myEntity);
//when:
ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);
//then:
assertEquals(myEntity, response);
}
我从方法调用中获得的响应对象 makeGetServiceCall
始终 return 为空。当我调试代码时,我在 return myEntity
resttemplate
模拟中看到异常 org.mockito.exceptions.misusing.WrongTypeOfReturnValue: ResponseEntity cannot be returned by toString() toString() should return String
错误
我不确定我错过了什么。
嗯,你犯了很多错误...
- 我确定您想用
@Mock
注释private RetryTemplate retryTemplate;
,而不是@InjectMocks
@InjectMocks
应该继续ServiceRequest serviceRequest;
- 您在
mockRetryTemplate
和mockRestTemplate
上定义了与serviceRequest
无关的交互。相反,您应该使用带@Mock
注释的字段来定义交互,因为它们被注入到您的被测对象中 (serviceRequest
) - 此外,您通常不能模拟
RestTemplate
并将其注入到您的ServiceRequest
中,因为您首先没有在ServiceRequest
中对RestTemplate
使用依赖注入=].您只需在ServiceRequest.makeGetServiceCall
中实例化它的实例
- 您在第
Mockito.when(retryTemplate.execute(...
行定义了错误方法的交互。您的交互指定RetryTemplate.execute(RetryCallback, RecoveryCallback, RetryState)
而您的ServiceRequest
使用另一种方法RetryTemplate.execute(RetryCallback)
- 你还应该注意到
RetryTemplate.execute
是最终的,所以你不能像 here 解释的那样在没有额外努力的情况下模拟它。通常,您应该更喜欢接口而不是 类,例如RestOperations
和RetryOperations
分别优于RestTemplate
和RetryTemplate
,更加灵活。
也就是说,下面是解决您问题的工作测试。但是请注意从 ServiceRequest
中删除 RestTemplate restTemplate = new RestTemplate();
并使 restTemplate
成为一个字段,以便它被依赖注入。
@RunWith(MockitoJUnitRunner.class)
public class ServiceRequestTest {
@Mock
private RestTemplate restTemplate;
@Mock
public RequestConfig requestConfig;
@Mock
private RetryTemplate retryTemplate;
@InjectMocks
ServiceRequest serviceRequest;
@Test
public void makeGetServiceCall() throws Exception {
//given:
String url = "http://localhost:8080";
ResponseEntity<String> myEntity = new ResponseEntity<>(HttpStatus.ACCEPTED);
when(retryTemplate.execute(any(RetryCallback.class))).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(/*here goes RetryContext but it's ignored in ServiceRequest*/null);
});
when(restTemplate.exchange(eq(url), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class)))
.thenReturn(myEntity);
//when:
ResponseEntity<String> response = serviceRequest.makeGetServiceCall(url);
//then:
assertEquals(myEntity, response);
}
}
下面对我有效,否则,它总是返回 null
when(retryTemplate.execute(any(),any(),any())).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(null);
});
导入是
import static org.mockito.ArgumentMatchers.any;
通用解决方案:
Mockito.when(retryTemplate.execute(Matchers.any(),Matchers.any(),Matchers.any())).thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgumentAt(0,Matchers.any());
return retry.doWithRetry(null);
});
对我有用!
@ExtendWith(MockitoExtension.class)
class RetryableRestClientTest {
@Mock
private RestTemplate restTemplate;
@Mock
private RetryTemplate retryTemplate;
@InjectMocks
private RetryableRestClient client;
@SuppressWarnings("rawtypes")
@Test
void test_get() {
String url = "https://faked-url";
ResponseEntity<String> expectedResponseEntity = new ResponseEntity<>(HttpStatus.OK);
Mockito.when(retryTemplate.execute(Mockito.any(), Mockito.any(), Mockito.any()))
.thenAnswer(invocation -> {
RetryCallback retry = invocation.getArgument(0);
return retry.doWithRetry(null);
});
Mockito.when(restTemplate.exchange(Mockito.eq(url), Mockito.eq(HttpMethod.GET), Mockito.any(HttpEntity.class), Mockito.eq(String.class)))
.thenReturn(expectedResponseEntity);
ResponseEntity<String> actualResponseEntity = client.get(url);
Assertions.assertEquals(expectedResponseEntity, actualResponseEntity);
}
}
@Component
public class RetryableRestClient {
@Autowired
private RetryTemplate retryTemplate;
@Autowired
private RestTemplate restTemplate;
private HttpHeaders fakeHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
// fake browser's behavior
headers.add("authority", "m.nowscore.com");
headers.add("cache-control", "max-age=0");
headers.add("sec-ch-ua", "\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"91\", \"Chromium\";v=\"91\"");
headers.add("sec-ch-ua-mobile", "?0");
headers.add("upgrade-insecure-requests", "1");
headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36");
headers.add("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9");
headers.add("sec-fetch-site", "none");
headers.add("sec-fetch-mode", "navigate");
headers.add("sec-fetch-user", "?1");
headers.add("sec-fetch-dest", "document");
headers.add("accept-language", "en-US,en;q=0.9");
return headers;
}
public final ResponseEntity<String> get(String url) {
return retryTemplate.execute(context -> restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, fakeHttpHeaders()), String.class));
}
}