以前写过关于springboot Controller层单元测试的系列文章( Spring Controller层测试 )。但是那几篇文章还是更偏方法论一些,不能直接拿来使用。所以有了这偏内容,目的主要是记录下平时使用的Controller层单元测试方案。
在这里先定义一个普通的api接口类 WorkerApi :
@RestController
@RequestMapping(value = "/api/worker")
public class WorkerApi {
@Autowired
private WorkerService workerService;
@PostMapping
public int add(@RequestBody Worker worker) {
System.out.println("------>>> add worker: " + worker.getName());
return 9;
}
@PutMapping
public boolean update(@RequestBody Worker worker) {
System.out.println("------>>> update worker: " + worker.getName());
return true;
}
@GetMapping("/{id}")
public Worker get(@PathVariable("id") int id) {
System.out.println("------->>> get worker: " + id);
return workerService.get(id);
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable("id") int id) {
System.out.println("------->>> delete worker: " + id);
return true;
}
}
这个接口里的4个方法覆盖了平时常用的四种Http请求方案,并将请求结果用一个统一的 ResultWrapper 类进行了封装(关于如何封装请求结果请参考上一篇文章 SpringBoot Controller返回值封装 )。
然后是单元测试方案。这里有两个超类: TestBase 和 ApiTestBase 。前者用来对普通的注入实例进行测试,后者主要用来对Api接口进行测试。
TestCase 类内容如下:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestBase {
}
通过类内容可以看到,在测试中会启动一个内嵌的WebServer来加载Spring Context。这样的测试比较重一些,不过也和实际使用场景更一致一些。
ApiTestBase 类继承了 TestBase 类。在这个类里引用了 TestRestTemplate 的实例执行具体的接口调用,此外还定义了一些公用方法来减少使用时的代码量:
@Autowired
private TestRestTemplate restTemplate;
protected abstract String parentPath();
protected <T> Object testPost(String path, T param) {
path = buildPath(path);
System.out.println(toJson(param));
ResponseEntity<ResultWrapper> response = restTemplate.postForEntity(path, param, ResultWrapper.class);
ResultWrapper w = response.getBody();
return getResponse(w);
}
protected <T, R> R testPost(String path, T param, Class<R> tClass) {
Object r = testPost(path, param);
String json = toJson(r);
return fromJson(json, tClass);
}
protected <T> Object testPut(String path, T param) {
path = buildPath(path);
System.out.println(toJson(param));
ResponseEntity<ResultWrapper> response = restTemplate.exchange(path, HttpMethod.PUT, new HttpEntity<T>(param), ResultWrapper.class);
ResultWrapper w = response.getBody();
return getResponse(w);
}
protected <T, R> R testPut(String path, T param, Class<R> tClass) {
Object r = testPut(path, param);
String json = toJson(r);
return fromJson(json, tClass);
}
protected Object testGet(String path) {
path = buildPath(path);
ResponseEntity<ResultWrapper> response = restTemplate.getForEntity(path, ResultWrapper.class);
ResultWrapper w = response.getBody();
return getResponse(w);
}
protected <T> T testGet(String path, Class<T> tClass) {
Object r = testGet(path);
String json = toJson(r);
return fromJson(json, tClass);
}
protected Object testDelete(String path) {
path = buildPath(path);
ResponseEntity<ResultWrapper> response = restTemplate.exchange(path, HttpMethod.DELETE, null, ResultWrapper.class);
ResultWrapper w = response.getBody();
return getResponse(w);
}
protected <T> T testDelete(String path, Class<T> tClass) {
Object r = testDelete(path);
String json = toJson(r);
return fromJson(json, tClass);
}
private String buildPath(String path) {
String p = parentPath();
if (!p.endsWith("/") && !path.startsWith("/")) {
return p + "/" + path;
} else {
return p + path;
}
}
private Object getResponse(ResultWrapper wrapper) {
System.out.println(toJson(wrapper));
Assert.assertNotNull(wrapper);
Assert.assertEquals(HttpStatus.OK.value(), wrapper.getCode());
return null == wrapper.getResult() ? "" : wrapper.getResult();
}
这里为每种请求都定义了两个方法,以根据需要返回不同形式的返回值。
看下是怎样使用的:
public class WorkerApiTest extends ApiTestBase {
@Override
protected String parentPath() {
return "/api/worker";
}
@Test
public void add() {
Map<String, Object> param = new HashMap<>(2);
param.put("name", "zhyea.com");
param.put("age", 5);
Integer id = testPost("", param, Integer.class);
Assert.assertEquals(9, id.intValue());
}
@Test
public void update() {
Map<String, Object> param = new HashMap<>(2);
param.put("id", 9);
param.put("name", "chobit.org");
param.put("age", 5);
Boolean r = testPut("", param, Boolean.class);
Assert.assertTrue(r);
}
@Test
public void get() {
Worker w = testGet("/1", Worker.class);
Assert.assertEquals(33, w.getAge());
}
@Test
public void delete() {
Boolean r = testDelete("/9", Boolean.class);
Assert.assertTrue(r);
}
}
就这样。代码已经传到了GitHub上,有需要请自行查阅: GitHub / zhyea