Spring Boot 2.x 基础教程:构建 RESTful API 与单元测试 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
dyc87112
V2EX    Java

Spring Boot 2.x 基础教程:构建 RESTful API 与单元测试

  •  
  •   dyc87112
    dyc87112 2019-10-11 09:59:31 +08:00 3488 次点击
    这是一个创建于 2191 天前的主题,其中的信息可能已经有所发展或是发生改变

    首先,回顾并详细说明一下在快速入门中使用的@Controller@RestController@RequestMapping注解。如果您对 Spring MVC 不熟悉并且还没有尝试过快速入门案例,建议先看一下快速入门的内容。

    • @Controller:修饰 class,用来创建处理 http 请求的对象
    • @RestController:Spring4 之后加入的注解,原来在@Controller中返回 json 需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回 json 格式
    • @RequestMapping:配置 url 映射。现在更多的也会直接用以 Http Method 直接关联的映射注解来定义,比如:GetMappingPostMappingDeleteMappingPutMapping

    下面我们通过使用 Spring MVC 来实现一组对 User 对象操作的 RESTful API,配合注释详细说明在 Spring MVC 中如何映射 HTTP 请求、如何传参、如何编写单元测试。

    ** RESTful API 具体设计如下:**

    定义 User 实体

    @Data public class User { private Long id; private String name; private Integer age; } 

    注意:相比1.x 版本教程中自定义 set 和 get 函数的方式,这里使用@Data注解可以实现在编译器自动添加 set 和 get 函数的效果。该注解是 lombok 提供的,只需要在 pom 中引入加入下面的依赖就可以支持:

    <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> 

    实现对 User 对象的操作接口

    @RestController @RequestMapping(value = "/users") // 通过这里配置使下面的映射都在 /users 下 public class UserController { // 创建线程安全的 Map,模拟 users 信息的存储 static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); /** * 处理"/users/"的 GET 请求,用来获取用户列表 * * @return */ @GetMapping("/") public List<User> getUserList() { // 还可以通过 @RequestParam 从页面中传递参数来进行查询条件或者翻页信息的传递 List<User> r = new ArrayList<User>(users.values()); return r; } /** * 处理"/users/"的 POST 请求,用来创建 User * * @param user * @return */ @PostMapping("/") public String postUser(@RequestBody User user) { // @RequestBody 注解用来绑定通过 http 请求中 application/json 类型上传的数据 users.put(user.getId(), user); return "success"; } /** * 处理"/users/{id}"的 GET 请求,用来获取 url 中 id 值的 User 信息 * * @param id * @return */ @GetMapping("/{id}") public User getUser(@PathVariable Long id) { // url 中的 id 可通过 @PathVariable 绑定到函数的参数中 return users.get(id); } /** * 处理"/users/{id}"的 PUT 请求,用来更新 User 信息 * * @param id * @param user * @return */ @PutMapping("/{id}") public String putUser(@PathVariable Long id, @RequestBody User user) { User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } /** * 处理"/users/{id}"的 DELETE 请求,用来删除 User * * @param id * @return */ @DeleteMapping("/{id}") public String deleteUser(@PathVariable Long id) { users.remove(id); return "success"; } } 

    这里相较1.x 版本教程中,用更细化的@GetMapping@PostMapping等系列注解替换了以前的@RequestMaping注解;另外,还使用@RequestBody替换了@ModelAttribute的参数绑定。

    编写单元测试

    下面针对该 Controller 编写测试用例验证正确性,具体如下。当然也可以通过浏览器插件等进行请求提交验证。

    @RunWith(SpringRunner.class) @SpringBootTest public class Chapter21ApplicationTests { private MockMvc mvc; @Before public void setUp() { mvc = MockMvcBuilders.standaloneSetup(new UserController()).build(); } @Test public void testUserController() throws Exception { // 测试 UserController RequestBuilder request; // 1、get 查一下 user 列表,应该为空 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[]"))); // 2、post 提交一个 user request = post("/users/") .contentType(MediaType.APPLICATION_JSON) .content("{\"id\":1,\"name\":\"测试大师\",\"age\":20}"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 3、get 获取 user 列表,应该有刚才插入的数据 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"测试大师\",\"age\":20}]"))); // 4、put 修改 id 为 1 的 user request = put("/users/1") .contentType(MediaType.APPLICATION_JSON) .content("{\"name\":\"测试终极大师\",\"age\":30}"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 5、get 一个 id 为 1 的 user request = get("/users/1"); mvc.perform(request) .andExpect(content().string(equalTo("{\"id\":1,\"name\":\"测试终极大师\",\"age\":30}"))); // 6、del 删除 id 为 1 的 user request = delete("/users/1"); mvc.perform(request) .andExpect(content().string(equalTo("success"))); // 7、get 查一下 user 列表,应该为空 request = get("/users/"); mvc.perform(request) .andExpect(status().isOk()) .andExpect(content().string(equalTo("[]"))); } } 

    对 MockMvc 不熟悉的读者,可能会碰到一些函数不存在而报错。必须引入下面这些静态函数的引用:

    import static org.hamcrest.Matchers.equalTo; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 

    这里相较1.x 版本教程中,主要有两个地方不同。测试类采用@RunWith(SpringRunner.class)@SpringBootTest修饰启动;另外,由于 POST 和 PUT 接口的参数采用@RequestBody注解,所以提交的会是一个 json 字符串,而不是之前的参数形式,这里在定义请求的时候使用contentType(MediaType.APPLICATION_JSON)指定提交内容为 json 格式,使用content传入要提交的 json 字符串。如果用 @ModelAttribute 的话就得用param方法添加参数,具体可以看1.x 版本的教程

    至此,我们通过引入 web 模块(没有做其他的任何配置),就可以轻松利用 Spring MVC 的功能,以非常简洁的代码完成了对 User 对象的 RESTful API 的创建以及单元测试的编写。其中同时介绍了 Spring MVC 中最为常用的几个核心注解:@RestController,RequestMapping以及一些参数绑定的注解:@PathVariable,@RequestBody等。

    代码示例

    本文的相关例子可以查看下面仓库中的chapter2-1目录:

    如果您觉得本文不错,欢迎Star支持,您的关注是我坚持的动力!

    5 条回复    2019-10-11 13:16:25 +08:00
    zhazi
        1
    zhazi  
       2019-10-11 10:45:18 +08:00 via Android
    朋友 你这个是集成模式 而且测试的方式也有问题
    chendy
        2
    chendy  
       2019-10-11 10:55:43 +08:00
    2.x 就用 Junit5 吧
    另外这个 UT 一点也不 U
    IMCA1024
        3
    IMCA1024  
       2019-10-11 11:05:03 +08:00
    lombok 这个的话。Spring Book 1.x 也可以用啊。
    在开发工具 比如 idea 要装插件
    ingdawn
        4
    ingdawn  
       2019-10-11 11:28:33 +08:00
    程序员 dd 哦
    Macolor21
        5
    Macolor21  
       2019-10-11 13:16:25 +08:00   1
    不是,天天分享这些重复的“基础教程” ,一搜一大把,什么时候能发一些干货呢?
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4942 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 03:56 PVG 11:56 LAX 20:56 JFK 23:56
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86