最近项目组需要做开发者测试,了解了MockMvc和TestRestTemplate,最终选择了TestRestTemplate,具体实现如下。
1.pom依赖test相关jar包
<!-- test start-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
<!--test end-->
2. H2数据库配置,application.properties中配置
需要在scheme.sql中加入待测试类依赖的建表语句,data.sql中insert初始化数据。
spring.datasource.driverClassName=org.h2.Driver
# jdbc:h2:mem代表为内存数据库;
# uap_test为数据库名称;
# MODE=PostgreSQL代表数据库为PostgreSQL
spring.datasource.url=jdbc:h2:mem:uap_test;MODE=PostgreSQL
# 从schema.sql初始化数据库;
spring.datasource.username=
spring.datasource.password=
spring.datasource.schema=classpath:schema.sql
spring.datasource.data=classpath:data.sql
spring.h2.console.enabled=true
# mybatis-plus配置
mybatis-plus.configuration.map-underscore-to-camel-case=false
3.核心类
AbstractContextTest
该类启动Spring上下文,指定随机端口,防止冲突,使用Mockito打桩设置clusterService获取集群id默认都为1,所有用例涉及到集群id的都设为1,clusterService此为我们业务相关类,读者可自行根据自己业务场景设置打桩数据,同时还可以结合PowerMock,具体的注解可以写为:
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PowerMockIgnore({“javax.management.", "javax.net.ssl.”})
@PrepareForTest({XXX.class, YYY.class})
@SpringBootTest(classes = {TestApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {TestApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class AbstractContextTest {
@MockBean
private ClusterService clusterService;
/**
* Do before
*/
@Before
public void doBefore() {
ClusterEntity clusterEntity = new ClusterEntity();
clusterEntity.setAppName("URP");
clusterEntity.setId(1);
Mockito.when(clusterService.queryByIdInCache(Mockito.anyInt())).thenReturn(clusterEntity);
}
}
AbstractRest
该类使用TestRestTemplate 提供通用的exchange方法调用指定的rest请求
public abstract class AbstractRest {
@Autowired(required = false)
private TestRestTemplate restTemplate;
/**
* exchange
*
* @param url url
* @param method method
* @param restEntity restEntity
* @param urlVariables urlVariables
* @param <T> T
* @return ResponseEntity
*/
protected <T> ResponseEntity<T> exchange(
String url, HttpMethod method, RestEntity<T> restEntity, Object... urlVariables) {
MultiValueMap<String, String> headers = restEntity.getHeaders();
if (headers == null) {
headers = new LinkedMultiValueMap<>();
headers.add("uap-app", "URP");
headers.add("uap-cluster", "1");
headers.add("Accept-Language", "zh-CN");
}
String params = restEntity.getParams()
.entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
String requestUrl = url;
if (!StringUtils.isEmpty(params)) {
requestUrl += "?" + params;
}
return this.restTemplate.exchange(requestUrl, method, new HttpEntity<>(restEntity.getBody(), headers),
restEntity.getResponseType(), urlVariables);
}
/**
* exchangeWithPageable
*
* @param url url
* @param method method
* @param restEntity restEntity
* @param urlVariables urlVariables
* @param <T> T
* @return ResponseEntity
*/
protected <T> ResponseEntity<ListPage<T>> exchangeWithPageable(
String url, HttpMethod method, RestEntity<T> restEntity, Object... urlVariables) {
MultiValueMap<String, String> headers = restEntity.getHeaders();
if (headers == null) {
headers = new LinkedMultiValueMap<>();
headers.add("uap-app", "URP");
headers.add("uap-cluster", "1");
headers.add("Accept-Language", "zh-CN");
}
String params = restEntity.getParams()
.entrySet()
.stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
String requestUrl = url;
if (!StringUtils.isEmpty(params)) {
requestUrl += "?" + params;
}
return this.restTemplate.exchange(requestUrl, method, new HttpEntity<>(restEntity.getBody(), headers),
new ParameterizedTypeReference<ListPage<T>>() {}, urlVariables);
}
}
RestEntity
用于封装请求头,请求体,请求参数,返回体
@Data
@Accessors(chain = true)
public class RestEntity<T> {
private MultiValueMap<String, String> headers;
private Object body;
private Class<T> responseType;
private Map<String, Object> params = new HashMap<>();
/**
* RestEntity
*
* @param body body
* @param responseType responseType
*/
public RestEntity(Object body, Class<T> responseType) {
this.body = body;
this.responseType = responseType;
}
/**
* RestEntity
*
* @param responseType responseType
*/
public RestEntity(Class<T> responseType) {
this.responseType = responseType;
}
/**
* addParam
*
* @param key key
* @param value value
* @return RestEntity
*/
public RestEntity<T> addParam(String key, Object value) {
this.params.put(key, value);
return this;
}
}
4.测试类
测试类需要继承AbstractRest 类,每个Rest增删改查请求路径定义好
@Service
public class NoteIndexRest extends AbstractRest {
/**
* Query note index response entity
*
* @param <T> parameter
* @param restEntity rest entity
* @return the response entity
*/
public <T> ResponseEntity<T> queryNoteIndex(RestEntity<T> restEntity) {
return this.exchange("/noteindex/configs", HttpMethod.GET, restEntity);
}
/**
* Add note index response entity
*
* @param <T> parameter
* @param restEntity rest entity
* @return the response entity
*/
public <T> ResponseEntity<T> addNoteIndex(RestEntity<T> restEntity) {
return this.exchange("/noteindex/configs", HttpMethod.POST, restEntity);
}
/**
* Modify note index response entity
*
* @param <T> parameter
* @param restEntity rest entity
* @return the response entity
*/
public <T> ResponseEntity<T> modifyNoteIndex(RestEntity<T> restEntity, Integer noteIndex) {
return this.exchange("/noteindex/configs/{noteIndex}", HttpMethod.PUT, restEntity, noteIndex);
}
/**
* Remove note index response entity
*
* @param <T> parameter
* @param restEntity rest entity
* @return the response entity
*/
public <T> ResponseEntity<T> removeNoteIndex(RestEntity<T> restEntity, String ids) {
return this.exchange("/noteindex/configs/{noteIndex}", HttpMethod.DELETE, restEntity, ids);
}
}
NoteIndexRestTest 继承AbstractContextTest 类,避免重复写注解和通用处理。
public class NoteIndexRestTest extends AbstractContextTest {
@Autowired
private NoteIndexRest noteIndexRest;
/**
* Add one note index
*/
@Test
public void should_200_when_add_one_noteIndex() {
NoteIndexDto noteIndexDto = new NoteIndexFactory().createNoteIndexDto();
ResponseEntity<NoteIndexDto> responseEntity = noteIndexRest.addNoteIndex(new RestEntity<>(noteIndexDto, NoteIndexDto.class));
Assert.assertSame(HttpStatus.OK, responseEntity.getStatusCode());
noteIndexRest.removeNoteIndex(new RestEntity<>(Object.class), String.valueOf(noteIndexDto.getNoteIndex()));
}
/**
* Query all note index by cluster id
*/
@Test
public void should_200_when_query_all_noteIndex_by_clusterId() {
NoteIndexDto noteIndexDto = new NoteIndexFactory().createNoteIndexDto();
noteIndexRest.addNoteIndex(new RestEntity<>(noteIndexDto, NoteIndexDto.class));
Assert.assertNotNull(noteIndexRest.queryNoteIndex(new RestEntity<>(DtoResponse.class)).getBody());
noteIndexRest.removeNoteIndex(new RestEntity<>(Object.class), String.valueOf(noteIndexDto.getNoteIndex()));
}
/**
* Remove one note index
*/
@Test
public void should_200_when_remove_one_noteIndex() {
NoteIndexDto noteIndexDto = new NoteIndexFactory().createNoteIndexDto();
noteIndexRest.addNoteIndex(new RestEntity<>(noteIndexDto, NoteIndexDto.class));
ResponseEntity<Object> responseEntity = noteIndexRest.removeNoteIndex(new RestEntity<>(Object.class), String.valueOf(noteIndexDto.getNoteIndex()));
Assert.assertSame(HttpStatus.OK, responseEntity.getStatusCode());
}
/**
* Modify one note index
*/
@Test
public void should_200_when_modify_one_noteIndex() {
NoteIndexDto noteIndexDto = new NoteIndexFactory().createNoteIndexDto();
noteIndexRest.addNoteIndex(new RestEntity<>(noteIndexDto, NoteIndexDto.class));
noteIndexDto = new NoteIndexFactory().createUpdateNoteIndexDto();
ResponseEntity<NoteIndexDto> responseEntity = noteIndexRest.modifyNoteIndex(new RestEntity<>(noteIndexDto, NoteIndexDto.class), noteIndexDto.getNoteIndex());
Assert.assertSame(HttpStatus.OK, responseEntity.getStatusCode());
noteIndexRest.removeNoteIndex(new RestEntity<>(Object.class), String.valueOf(noteIndexDto.getNoteIndex()));
}
}
NoteIndexFactory 用于生成各种Dto,通过RestEntity封装传递给Rest接口。
public class NoteIndexFactory {
/**
* Create note index dto note index dto
*
* @return the note index dto
*/
public NoteIndexDto createNoteIndexDto() {
NoteIndexDto noteIndexDto = new NoteIndexDto();
noteIndexDto.setClusterId(-1);
noteIndexDto.setAppName("UAP");
noteIndexDto.setClusterName("URP");
noteIndexDto.setNoteIndex(1);
noteIndexDto.setNote1(1);
noteIndexDto.setNote2(255);
noteIndexDto.setNote3(255);
noteIndexDto.setNote4(255);
noteIndexDto.setNote5(255);
noteIndexDto.setNote6(255);
noteIndexDto.setNote7(255);
noteIndexDto.setNote8(255);
noteIndexDto.setNoteNum(1);
return noteIndexDto;
}
/**
* Create update note index dto note index dto
*
* @return the note index dto
*/
public NoteIndexDto createUpdateNoteIndexDto() {
NoteIndexDto noteIndexDto = this.createNoteIndexDto();
noteIndexDto.setNoteNum(2);
noteIndexDto.setNote2(2);
return noteIndexDto;
}
}