(01)Restful风格的增删改查案例及其junit测试详解

Posted javasl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(01)Restful风格的增删改查案例及其junit测试详解相关的知识,希望对你有一定的参考价值。

  一、相关注解

  @GetMapping:等价于@RequestMapping(method=RequestMethod.GET)

  @PostMapping:等价于@RequestMapping(method=RequestMethod.POST)

  @PutMapping:等价于@RequestMapping(method=RequestMethod.PUT)

  @DeleteMapping:等价于@RequestMapping(method=RequestMethod.DELETE)

  @RequestBody:映射请求体到java方法的参数

  @RequestParam:映射请求参数到java方法的参数

  @PageableDefault:指定分页参数默认值

  @PathVariable: 映射url片段到java方法的参数

  @JsonView:控制json输出内容,可以指定哪些字段显示

  @Valid:验证字段是否合法

  BindingResult:验证不合法时获取提示信息,@Valid注解和BingResult验证请求参数的合法性并处理校验结果

  二、演示增删查改

  User.java

package com.edu.sl.dto;

import java.util.Date;

public class User {

    private String id;
    private String username;
    private String password;
    private Date birthDay;
    
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public Date getBirthDay() {
        return birthDay;
    }
    public void setBirthDay(Date birthDay) {
        this.birthDay = birthDay;
    }
}

  UserController.java

package com.edu.sl.controller;

import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController { ... ... }

  UserControllerTest.java

package com.edu.sl.controller;

import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {

    @Autowired
    private WebApplicationContext wac;
    
    private MockMvc mockMvc;
    
    @Before
    public void setUp(){
        mockMvc=MockMvcBuilders.webAppContextSetup(wac).build();
    }
   ... ...    
}

  测试依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency>

  1、查询操作

  使用@GetMapping注解,测试使用get方法。

  1)不传递参数

@GetMapping
public List<User> query(){
  List<User> users = new ArrayList<User>();
  users.add(new User());
  users.add(new User());
  users.add(new User());
  return users;
}
@Test
public void query() throws Exception{
  String content=mockMvc.perform(MockMvcRequestBuilders.get("/user")
                   .contentType(MediaType.APPLICATION_JSON_UTF8))
                   .andExpect(MockMvcResultMatchers.status().isOk())
                   .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3))
                   .andReturn().getResponse().getContentAsString();
    System.out.println(content);
}

  2)传递参数

@GetMapping("/a")public List<User> query2(String username,String password){
    System.out.println("username:"+username);
    System.out.println("password:"+password);
    List<User> users = new ArrayList<User>();
    users.add(new User());
    users.add(new User());
    users.add(new User());
    return users;
}
@Test
public void query() throws Exception{
    String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/a")
                          .param("username","sl")
                          .param("password","123456")
                          .contentType(MediaType.APPLICATION_JSON_UTF8))
                          .andExpect(MockMvcResultMatchers.status().isOk())
                          .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3))
                          .andReturn().getResponse().getContentAsString();
    System.out.println(content);
}

  3)@RequestParam注解

  使用此注解,默认情况下传递的实参中必须含有它指定的参数名,否则报400。此注解含有4个属性可以改变默认情况。

  defaultValue:如果没有实参,形参中默认使用的值。

  name:起别名,默认实参、形参名字一致,使用name属性时形参可以随意命名,会映射过来。

  required:是否必须,默认false必填 ,否则报400。

  value:最终得到的值。

@GetMapping("/b")
public List<User> query3(@RequestParam(name="username",required=false,defaultValue="tom") String nikename){
    System.out.println("nikename:"+nikename);
    List<User> users = new ArrayList<User>();
    users.add(new User());
    users.add(new User());
    users.add(new User());
    return users;
}
@Test
public void query3() throws Exception{
    String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/b")
                          .param("username","sl")
                          .contentType(MediaType.APPLICATION_JSON_UTF8))
                          .andExpect(MockMvcResultMatchers.status().isOk())
                          .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3))
                          .andReturn().getResponse().getContentAsString();
    System.out.println(content);
}

  如果实参中有username这个属性,就取它的值赋给nikename,没有这个属性,nikename就取tom这个值

  4)@PageableDefault注解

  使用此注解指定分页参数默认值,配合Pageable使用,需要引入以下依赖

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-commons</artifactId>
</dependency>
@GetMapping("/c")
public List<User> query4(User user,@PageableDefault(page=10,size=20,sort="age,asc") Pageable pageable){
    System.out.println("page:"+pageable.getPageNumber());
    System.out.println("size:"+pageable.getPageSize());
    System.out.println("sort:"+pageable.getSort());
    List<User> users = new ArrayList<User>();
    users.add(new User());
    users.add(new User());
    users.add(new User());
    return users;
}
@Test
public void query4() throws Exception{
    String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/c")
                          .param("username","sl")
                          .param("size","10")
                          .param("page","2")
                          .param("sort","username,desc")
                          .contentType(MediaType.APPLICATION_JSON_UTF8))
                          .andExpect(MockMvcResultMatchers.status().isOk())
                          .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3))
                          .andReturn().getResponse().getContentAsString();
    System.out.println(content);
}

  如果实参中传递了size、page、sort参数,则实际使用传递的,如果实参中没有传递,则使用形参中默认的值,不会报错。

  实参中的属性会自动匹配到形参的类的对象的属性上面,如username会自动匹配到实参中user中的username中。

  5)@PathVariable注解

  此注解把声明的url(value="/{id}")中的片段id的值作为参数传到java方法的id上

@GetMapping(value="/{id3}")
public User getUserInfo(@PathVariable("id3") String id){
   System.out.println("id:"+id);//可以获取到 User user
=new User(); user.setUsername("tom"); return user; }
@Test
public void getUserInfo() throws Exception{
    String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/1")
    .contentType(MediaType.APPLICATION_JSON_UTF8))
    .andExpect(MockMvcResultMatchers.status().isOk())
    .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("tom"))
    .andReturn().getResponse().getContentAsString();
    System.out.println(content);
}

  @PathVariable里面有name,value属性,作用一样,用来指定变量的名字,例如上例中id3会映射到id上。两个id3要保持一致。

  大括号中的变量可以使用正则表达式,例如只希望id是整数,可以如下:

@GetMapping(value="/a/{id3:\\d+}")
public User getUserInfo2(@PathVariable("id3") String id){
    System.out.println("id:"+id);
    User user=new User();
    user.setUsername("tom");
    return user;
}
@Test
public void getUserInfo2() throws Exception{
    String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/a/13")
    .contentType(MediaType.APPLICATION_JSON_UTF8))
    .andExpect(MockMvcResultMatchers.status().isOk())
    .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("tom"))
    .andReturn().getResponse().getContentAsString();
    System.out.println(content);
}
@Test
public void getUserInfo2_fail() throws Exception{
    mockMvc.perform(MockMvcRequestBuilders.get("/user/a/1.3")
    .contentType(MediaType.APPLICATION_JSON_UTF8))
    .andExpect(MockMvcResultMatchers.status().is4xxClientError());
}

  如果将参数值13改为字母或者小数,报404。

  6)@JsonView注解

  分三步:用接口定义视图名称,在get方法上指定视图,在Controller方法上指定视图。

  修改User类,做如下修改:

public interface UserSimpleView{};
public interface UserDetailView extends UserSimpleView{};

@JsonView(User.UserSimpleView.class)
public String getUsername() {
    return username;
}

@JsonView(User.UserDetailView.class)
public String getPassword() {
    return password;
}
@GetMapping("/d")
@JsonView(User.UserSimpleView.class)
public List<User> query5(){
    List<User> list=new ArrayList<User>();
    list.add(new User());
    list.add(new User());
    list.add(new User());
    return list;
}

@GetMapping("/b/{id:\\d+}")
@JsonView(User.UserDetailView.class)
public User getUserInfo3(@PathVariable("id") String id){
    User user=new User();
    user.setUsername("tom");
    return user;
@Test
public void query5() throws Exception{
    String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/d")
                          .contentType(MediaType.APPLICATION_JSON_UTF8))
                          .andExpect(MockMvcResultMatchers.status().isOk())
                          .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3))
                          .andReturn().getResponse().getContentAsString();
    System.out.println(content);
}

@Test
public void getUserInfo3() throws Exception{
    String content=mockMvc.perform(MockMvcRequestBuilders.get("/user/b/13")
                          .contentType(MediaType.APPLICATION_JSON_UTF8))
                          .andExpect(MockMvcResultMatchers.status().isOk())
                          .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("tom"))
                          .andReturn().getResponse().getContentAsString();
    System.out.println(content);
}

  从System输出的结果中可以看到,query5返回json只有一个username属性,getUserInfo3含有username和password属性。

  2、新增操作

  使用@PostMapping注解,测试使用post方法。

  1)不校验参数值

@PostMapping
public User create(@RequestBody User user){
    System.out.println(user.getUsername());
    System.out.println(user.getPassword());
    System.out.println(user.getId());
    System.out.println(user.getBirthDay());
    user.setId("1");
    return user;
}
@Test
public void whenCreateSuccess() throws Exception{
    String content="{"username":"tom","password":123456}";
    String result=mockMvc.perform(MockMvcRequestBuilders.post("/user")
                         .contentType(MediaType.APPLICATION_JSON_UTF8)
                         .content(content))
                         .andExpect(MockMvcResultMatchers.status().isOk())
                         .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
                         .andReturn().getResponse().getContentAsString();
    System.out.println(result);
}

  注意:必须加@RequestBody注解,否则接收不到参数值,输出的属性值都是null。

  2)校验参数值

  后端使用hibernate-validator.jar校验,Controller中方法添加@Valid注解,实体类添加需要验证的注解。

  修改User类,做如下修改:

@NotBlank(message="密码不能为空")
private String password;
@Past(message="生日必须是过去的时间")
private Date birthDay;
@PostMapping("/a")
public User create2(@Valid @RequestBody User user){
    System.out.println(user.getUsername());
    System.out.println(user.getPassword());
    System.out.println(user.getId());
    System.out.println(user.getBirthDay());
    user.setId("1");
    return user;
}
@Test
public void create2() throws Exception{
  //当前时间加上一年 Date birthDay
=new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); String content="{"id":"1","username":"tom","password":null,"birthDay":"+birthDay.getTime()+"}"; String result=mockMvc.perform(MockMvcRequestBuilders.post("/user/a") .contentType(MediaType.APPLICATION_JSON_UTF8) .content(content)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1")) .andReturn().getResponse().getContentAsString(); System.out.println(result); }

  此时运行测试用例报400。不会进入方法体。

  说明:字段上的验证注解与方法中@Valid注解同时使用才有效。

  3)BindingResult类

  在Controller中的方法里加上这个类,可以获取到验证不通过的提示信息。

@PostMapping("/b")
public User create3(@Valid @RequestBody User user,BindingResult errors){
    if(errors.hasErrors()){
        List<ObjectError> list=errors.getAllErrors();
        for(ObjectError error:list){
            System.out.println(((FieldError)error).getField()+" "+ error.getDefaultMessage());
        }
    }
    System.out.println(user.getUsername());
    System.out.println(user.getPassword());
    System.out.println(user.getId());
    System.out.println(user.getBirthDay());
    user.setId("1");
    return user;
}
@Test
public void create3() throws Exception{
    Date birthDay=new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    String content="{"id":"1","username":"tom","password":null,"birthDay":"+birthDay.getTime()+"}";
    String result=mockMvc.perform(MockMvcRequestBuilders.post("/user/b")
                         .contentType(MediaType.APPLICATION_JSON_UTF8)
                         .content(content))
                         .andExpect(MockMvcResultMatchers.status().isOk())
                         .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
                         .andReturn().getResponse().getContentAsString();
    System.out.println(result);
}

  此时运行测试用例,会进入方法体。打印结果如下:

  技术图片

  说明:BindingResult与@Valid注解同时使用才有效。

  处理日期一般传递时间戳 new date().getTime(),因为js、app等都可以处理时间戳和日期的转换。

  3、修改操作

  使用@PutMapping注解,测试使用put方法。

@Test
public void update() throws Exception{
    Date birthDay=new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    String content="{"id":"1","username":"tom","password":null,"birthDay":"+birthDay.getTime()+"}";
    String result=mockMvc.perform(MockMvcRequestBuilders.put("/user/1")
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(content))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
            .andReturn().getResponse().getContentAsString();
    System.out.println(result);
}
@Test
public void update() throws Exception{
    Date birthDay=new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    String content="{"id":"1","username":"tom","password":null,"birthDay":"+birthDay.getTime()+"}";
    String result=mockMvc.perform(MockMvcRequestBuilders.put("/user/1")
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(content))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
            .andReturn().getResponse().getContentAsString();
    System.out.println(result);
}

  4、删除操作

  使用@DeleteMapping注解,测试使用delete方法。

@DeleteMapping("/{id:\\d+}")
public void delete(@PathVariable String id){
    System.out.println("id :"+id);
}
@Test
public void delete() throws Exception{
    mockMvc.perform(MockMvcRequestBuilders.delete("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8))
    .andExpect(MockMvcResultMatchers.status().isOk());
}

  三、自定义验证注解

  虽然hibernate-validator提供了大量的校验注解,但有时仍不能满足我们的需求,这时就要自定义注解。

package com.edu.sl.validator;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)//指定注解使用的类
public @interface MyConstraint {
    String message();
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}
package com.edu.sl.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;

public class MyConstraintValidator implements ConstraintValidator<MyConstraint,Object> {
    
    @Override
    public void initialize(MyConstraint arg0) {
        System.out.println("校验器初始化方法"); 
    }

    @Override
    public boolean isValid(Object arg0, ConstraintValidatorContext arg1) {
        System.out.println("arg0 :"+arg0);
     //写校验逻辑
        return false;//校验失败
    }
}

  自定义了一个注解和一个类,注解MyConstraint中三个属性是固定的。类MyConstraintValidator中的第二个泛型Object是可用类型,假如定义为String,只有在String类型的字段上可以使用该注解。自定义类中不需要加Component即可被Spring管理,可以注入其他bean用于校验逻辑。

  直接运行测试用例update,输出如下:

  技术图片

  四、其他

  1、返回验证码说明:

  400:请求格式错误,例如要求实参中必须有username,但实际没有。或者验证不通过
  405:后台不支持method,例如get请求post的方法

  2、JsonPath使用说明:

  在测试中使用jsonpath很方便,推荐使用。github中搜索 jsonpath,结果如下https://github.com/json-path/JsonPath

  3、Hibernate Validateor使用说明:

  Hibernate Validateor网上也有很多api,哪位网友知道比较全的地址可以告诉一下,下面截图奉上部分。

  技术图片

  技术图片

 

 

 

 

  

 

 

  

 

  

 

 

 

  

 

  

 

以上是关于(01)Restful风格的增删改查案例及其junit测试详解的主要内容,如果未能解决你的问题,请参考以下文章

IOS Sqlite用户界面增删改查案例

利用SpringBoot实现RestFul风格的增删改查操作

进入全屏 nodejs+express+mysql实现restful风格的增删改查示例

进入全屏 nodejs+express+mysql实现restful风格的增删改查示例

进入全屏 nodejs+express+mysql实现restful风格的增删改查示例

进入全屏 nodejs+express+mysql实现restful风格的增删改查示例