以前写过关于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