Mockito 单元测试 RestTemplate
Mockito unit testing RestTemplate
我正在使用 RestTemplate postForEntity
方法将正文 post 发送到端点。我需要帮助使用 Mockito 为我的代码编写测试用例。 return 类型是无效的,但如果需要测试,可以将其更改为 Types
或 code
。我参考了许多其他文档,但它们非常笼统,我尝试使用它们,但大多数对我不起作用,因为 request
和 return 类型不同。 .任何建议表示赞赏。谢谢
这是我的Javaclass
public void postJson(Set<Type> Types){
try {
String oneString = String.join(",", Types);
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", oneString);
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<String>(jsonObject.toString(), null);
ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<>(request, getHttpHeaders()), String.class);
}
}
}
不久前,我写了一篇关于 unit testing and test doubles 的文章。您可以阅读作为如何进行单元测试的起点。
它的一些关键要点是:
- 测试行为而不是实施。独立于实现细节的测试更容易维护。在大多数情况下,测试应该侧重于测试代码的 public API,并且代码的实现细节不需要暴露给测试。
- 被测单元的大小是任意的,但单元越小越好。
- 在谈论单元测试时,一个更典型的区别是被测单元应该是社交的还是孤独的。
- 测试替身是一种可以在测试中代替真实物体的物体,类似于电影中特技替身代替演员的方式。他们是测试双打而不是模拟。模拟是测试替身之一。不同的测试替身有不同的用途。
很难编写完整的测试,因为缺少很多信息。例如。 Type
是什么。
由于您没有发布 class 的名称,我暂时将其命名为 MyClass
。
我还假设 RestTemplate 是通过
这样的构造函数注入的
MyClass(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
以下是使用 JUnit 5 and Mockito 进行单元测试的草稿。
我建议嘲笑 RestTemplate
。我不得不承认,这样我们就不会在我们的测试中覆盖测试MappingJackson2HttpMessageConverter
和StringHttpMessageConverter
的用法。
所以一个非常原始的草稿可能看起来像这样
import java.util.ArrayList;
import java.util.Set;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.http.HttpEntity;
import org.springframework.web.client.RestTemplate;
class MyClassTest {
private RestTemplate restTemplate;
private MyClass myClass;
@BeforeEach
void setUp() {
restTemplate = Mockito.mock(RestTemplate.class);
myClass = new MyClass(restTemplate);
}
@Test
void callMethod() {
Set<Type> types = Set.of(/* one of your Types */);
String url = "http://someUrl";
String httpResult = "";
Mockito.when(restTemplate.getMessageConverters()).thenReturn(new ArrayList<>());
ArgumentCaptor<HttpEntity> request = ArgumentCaptor.forClass(HttpEntity.class);
Mockito.when(restTemplate.postForObject(url, request.capture(), String.class)).thenReturn(httpResult);
myClass.callMethod(types, url);
HttpEntity<String> actualHttpEntity = request.getValue();
Assert.assertEquals(actualHttpEntity.getBody(), "");
}
}
这是我对这个问题的解决方案,但它需要一些更改。
首先,您必须将 RestTemplate
外部化为一个 bean,并在其初始化中添加转换器。这样你就可以摆脱不覆盖那些转换器的问题。
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
restTemplate.getMessageConverters().add(jsonConverter);
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
restTemplate.getMessageConverters().add(stringConverter);
return restTemplate;
}
这是包含 postJson
方法的新 class。如您所见,url
和 restTemplate
是通过构造函数注入的。这样我们就可以测试不同的情况。
public class Poster {
private RestTemplate restTemplate;
private String url;
public Poster(RestTemplate restTemplate, String url) {
this.restTemplate = restTemplate;
this.url = url;
}
public void postJson(Set<Type> types) {
try {
String oneString = types.stream().map(Type::toString).collect(Collectors.joining(","));
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", oneString);
requestBody.put("data", "aws");
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), null);
ResponseEntity<String> result = restTemplate
.postForEntity(url, new HttpEntity<>(request, getHttpHeaders()), String.class);
int code = result.getStatusCodeValue();
} catch (Exception ignored) {}
}
private HttpHeaders getHttpHeaders() {
return new HttpHeaders();
}
}
这是该方法的测试 class。
class PosterTest {
@Mock
private RestTemplate restTemplate;
private String url;
private Poster poster;
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
this.url = "http://example.com/posturl";
this.poster = new Poster(restTemplate, url);
}
@Test
void postJson() {
// set input, I used TreeSet just to have a sorted set
// so that I can check the results against
Set<Type> types = new TreeSet<>(Comparator.comparing(Type::toString));
types.add(new Type("a"));
types.add(new Type("b"));
types.add(new Type("c"));
types.add(new Type("d"));
// response entity
ResponseEntity<String> response = ResponseEntity.ok("RESPONSE");
// mockito mock
Mockito.when(restTemplate.postForEntity(
ArgumentMatchers.eq(url),
ArgumentMatchers.any(HttpHeaders.class),
ArgumentMatchers.eq(String.class)
)).thenReturn(response);
// to check the results
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", "a,b,c,d");
requestBody.put("data", "aws");
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), null);
HttpEntity<HttpEntity<String>> httpEntity = new HttpEntity<>(request, new HttpHeaders());
// actual call
poster.postJson(types);
// verification
Mockito.verify(restTemplate, times(1)).postForEntity(
ArgumentMatchers.eq(url),
ArgumentMatchers.eq(httpEntity),
ArgumentMatchers.eq(String.class));
}
}
最好有像 RestTemplate
或其他 ServiceClass
这样的依赖项,这样它们就可以很容易地被模拟和测试。
您正在测试 MyClass class 中的逻辑,因此您不应该模拟它。 RestTemplate
是 MyClass 中的依赖项,因此这正是您需要模拟的内容。一般来说,它在你的测试中应该是这样的:
这只是一个简单的例子。一个好的做法是检查传递给模拟的参数是否等于预期的参数。一种方法是将 Mockito.eq()
替换为实际预期数据。另一种是单独验证,像这样:
public ResponseEntity<String> postJson(Set<Type> Types){
try {
String oneString = String.join(",", Types);
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", oneString);
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<String>(jsonObject.toString(), null);
ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<>(request, getHttpHeaders()), String.class);
}
}
return Types;
你可以为上面的方法编写测试如下
@Mock
RestTemplate restTemplate;
private Poster poster;
HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), getHttpHeaders());
ResponseEntity<String> result = restTemplate.exchange(uri, HttpMethod.POST, request, String.class);
Mockito.verify(restTemplate, Mockito.times(1)).exchange(
Mockito.eq(uri),
Mockito.eq(HttpMethod.POST),
Mockito.eq(request),
Mockito.eq(String.class));
Assert.assertEquals(result, poster.postJson(mockData));
HttpHeaders getHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add(// whatever you need to add);
return headers;
}
我正在使用 RestTemplate postForEntity
方法将正文 post 发送到端点。我需要帮助使用 Mockito 为我的代码编写测试用例。 return 类型是无效的,但如果需要测试,可以将其更改为 Types
或 code
。我参考了许多其他文档,但它们非常笼统,我尝试使用它们,但大多数对我不起作用,因为 request
和 return 类型不同。 .任何建议表示赞赏。谢谢
这是我的Javaclass
public void postJson(Set<Type> Types){
try {
String oneString = String.join(",", Types);
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", oneString);
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<String>(jsonObject.toString(), null);
ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<>(request, getHttpHeaders()), String.class);
}
}
}
不久前,我写了一篇关于 unit testing and test doubles 的文章。您可以阅读作为如何进行单元测试的起点。
它的一些关键要点是:
- 测试行为而不是实施。独立于实现细节的测试更容易维护。在大多数情况下,测试应该侧重于测试代码的 public API,并且代码的实现细节不需要暴露给测试。
- 被测单元的大小是任意的,但单元越小越好。
- 在谈论单元测试时,一个更典型的区别是被测单元应该是社交的还是孤独的。
- 测试替身是一种可以在测试中代替真实物体的物体,类似于电影中特技替身代替演员的方式。他们是测试双打而不是模拟。模拟是测试替身之一。不同的测试替身有不同的用途。
很难编写完整的测试,因为缺少很多信息。例如。 Type
是什么。
由于您没有发布 class 的名称,我暂时将其命名为 MyClass
。
我还假设 RestTemplate 是通过
MyClass(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
以下是使用 JUnit 5 and Mockito 进行单元测试的草稿。
我建议嘲笑 RestTemplate
。我不得不承认,这样我们就不会在我们的测试中覆盖测试MappingJackson2HttpMessageConverter
和StringHttpMessageConverter
的用法。
所以一个非常原始的草稿可能看起来像这样
import java.util.ArrayList;
import java.util.Set;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.http.HttpEntity;
import org.springframework.web.client.RestTemplate;
class MyClassTest {
private RestTemplate restTemplate;
private MyClass myClass;
@BeforeEach
void setUp() {
restTemplate = Mockito.mock(RestTemplate.class);
myClass = new MyClass(restTemplate);
}
@Test
void callMethod() {
Set<Type> types = Set.of(/* one of your Types */);
String url = "http://someUrl";
String httpResult = "";
Mockito.when(restTemplate.getMessageConverters()).thenReturn(new ArrayList<>());
ArgumentCaptor<HttpEntity> request = ArgumentCaptor.forClass(HttpEntity.class);
Mockito.when(restTemplate.postForObject(url, request.capture(), String.class)).thenReturn(httpResult);
myClass.callMethod(types, url);
HttpEntity<String> actualHttpEntity = request.getValue();
Assert.assertEquals(actualHttpEntity.getBody(), "");
}
}
这是我对这个问题的解决方案,但它需要一些更改。
首先,您必须将 RestTemplate
外部化为一个 bean,并在其初始化中添加转换器。这样你就可以摆脱不覆盖那些转换器的问题。
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
restTemplate.getMessageConverters().add(jsonConverter);
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
restTemplate.getMessageConverters().add(stringConverter);
return restTemplate;
}
这是包含 postJson
方法的新 class。如您所见,url
和 restTemplate
是通过构造函数注入的。这样我们就可以测试不同的情况。
public class Poster {
private RestTemplate restTemplate;
private String url;
public Poster(RestTemplate restTemplate, String url) {
this.restTemplate = restTemplate;
this.url = url;
}
public void postJson(Set<Type> types) {
try {
String oneString = types.stream().map(Type::toString).collect(Collectors.joining(","));
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", oneString);
requestBody.put("data", "aws");
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), null);
ResponseEntity<String> result = restTemplate
.postForEntity(url, new HttpEntity<>(request, getHttpHeaders()), String.class);
int code = result.getStatusCodeValue();
} catch (Exception ignored) {}
}
private HttpHeaders getHttpHeaders() {
return new HttpHeaders();
}
}
这是该方法的测试 class。
class PosterTest {
@Mock
private RestTemplate restTemplate;
private String url;
private Poster poster;
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
this.url = "http://example.com/posturl";
this.poster = new Poster(restTemplate, url);
}
@Test
void postJson() {
// set input, I used TreeSet just to have a sorted set
// so that I can check the results against
Set<Type> types = new TreeSet<>(Comparator.comparing(Type::toString));
types.add(new Type("a"));
types.add(new Type("b"));
types.add(new Type("c"));
types.add(new Type("d"));
// response entity
ResponseEntity<String> response = ResponseEntity.ok("RESPONSE");
// mockito mock
Mockito.when(restTemplate.postForEntity(
ArgumentMatchers.eq(url),
ArgumentMatchers.any(HttpHeaders.class),
ArgumentMatchers.eq(String.class)
)).thenReturn(response);
// to check the results
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", "a,b,c,d");
requestBody.put("data", "aws");
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), null);
HttpEntity<HttpEntity<String>> httpEntity = new HttpEntity<>(request, new HttpHeaders());
// actual call
poster.postJson(types);
// verification
Mockito.verify(restTemplate, times(1)).postForEntity(
ArgumentMatchers.eq(url),
ArgumentMatchers.eq(httpEntity),
ArgumentMatchers.eq(String.class));
}
}
最好有像 RestTemplate
或其他 ServiceClass
这样的依赖项,这样它们就可以很容易地被模拟和测试。
您正在测试 MyClass class 中的逻辑,因此您不应该模拟它。 RestTemplate
是 MyClass 中的依赖项,因此这正是您需要模拟的内容。一般来说,它在你的测试中应该是这样的:
这只是一个简单的例子。一个好的做法是检查传递给模拟的参数是否等于预期的参数。一种方法是将 Mockito.eq()
替换为实际预期数据。另一种是单独验证,像这样:
public ResponseEntity<String> postJson(Set<Type> Types){
try {
String oneString = String.join(",", Types);
Map<String, String> requestBody = new HashMap<>();
requestBody.put("type", oneString);
JSONObject jsonObject = new JSONObject(requestBody);
HttpEntity<String> request = new HttpEntity<String>(jsonObject.toString(), null);
ResponseEntity result = restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<>(request, getHttpHeaders()), String.class);
}
}
return Types;
你可以为上面的方法编写测试如下
@Mock
RestTemplate restTemplate;
private Poster poster;
HttpEntity<String> request = new HttpEntity<>(jsonObject.toString(), getHttpHeaders());
ResponseEntity<String> result = restTemplate.exchange(uri, HttpMethod.POST, request, String.class);
Mockito.verify(restTemplate, Mockito.times(1)).exchange(
Mockito.eq(uri),
Mockito.eq(HttpMethod.POST),
Mockito.eq(request),
Mockito.eq(String.class));
Assert.assertEquals(result, poster.postJson(mockData));
HttpHeaders getHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.add(// whatever you need to add);
return headers;
}