以前写过关于springboot Controller层单元测试的系列文章(Spring Controller层测试)。但是那几篇文章还是更偏方法论一些,不能直接拿来使用。所以有了这偏内容,目的主要是记录下平时使用的Controller层单元测试方案。
在这里先定义一个普通的api接口类WorkerApi
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
@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
类内容如下:
1 2 3 4 |
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class TestBase { } |
通过类内容可以看到,在测试中会启动一个内嵌的WebServer来加载Spring Context。这样的测试比较重一些,不过也和实际使用场景更一致一些。
ApiTestBase
类继承了TestBase
类。在这个类里引用了TestRestTemplate
的实例执行具体的接口调用,此外还定义了一些公用方法来减少使用时的代码量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
@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(); } |
这里为每种请求都定义了两个方法,以根据需要返回不同形式的返回值。
看下是怎样使用的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
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); } } |
有时候还会有在执行测试前添加虚拟机环境参数的需要,此时可以视情况在TaseBase或ApiTestBase或测试类中添加静态代码块并设置虚拟机参数:
1 2 3 4 5 6 7 8 9 |
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class TestBase { static { System.setProperty("env", "DEV"); } } |
就这样。代码已经传到了GitHub上,有需要请自行查阅: GitHub / zhyea
发表评论