Spring security实战(3)-----使用Spring MVC编写RestFul API

1.传统服务与RestFul API风格服务的区别

    传统服务风格:
         - 用url描述行为
         - 不管结果是否成功,状态码都返回200,具体参数获取报文信息

    RestFul API服务风格:
         - 用url描述资源 (传统的是使用url描述行为)
         - 使用Http方法描述行为,使用HTTP状态码来表示不同的结果(而不是通过报文来判断成功还是失败)
         - 使用json交互数据(请求和响应)

2.使用到的注解和相关技术

@RestController 标明此Controller提供RestFul
@RequestMappering及其变体 映射http请求到java方法
@RequestParam 映射请求参数到Java方法的参数
@PageableDefault 指定分页参数的默认值 –Pageable spring data中的内部对象
@PathVariable 映射url片段到Java方法的参数 –在url声明中使用正则表达式
@JsonView控制json的输出内容
@RequestBody 映射请求体到Java方法参数
日期类型参数的处理 –使用时间戳来交互数据
@Valid注解和BindigResult验证请求参数的合法性并处理效验结果

3.编写第一个RestFul API测试用例

3.1编写一个带有普通参数的RestFul API测试用例

    首先在rz-security-demo中的pom.xml中填写依赖:

 <!--spring的测试框架-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

     然后在src/test/java目录下新建立一个UserControllerTest.java文件

package com.rz.web.controller;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)  //表示如何来运行测试用例,使用SpringRunner来执行测试用例
@SpringBootTest //说明整个类都是一个测试用例的类
public class UserControllerTest {

        @Autowired
        private WebApplicationContext wac;

        //伪造的mvc环境
        private MockMvc mockMvc;

        @Before
        public  void init(){
            mockMvc=MockMvcBuilders.webAppContextSetup(wac).build();
        }

        @Test
        public void whenQuerySuccess() throws Exception {
            //perform 发送一个模拟请求
            //由于是restful风格,需要写contentType
            //andExpect 对请求结果的期望
            //jsonPath  解析返回来的json的内容
            mockMvc.perform(MockMvcRequestBuilders.get("/user")
                    .param("name","joon")  //当Java方法有@RequestParam注解时候需要加上该参数
                   .contentType(MediaType.APPLICATION_JSON_UTF8))
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    //认为返回的是一个长度为三的集合
                    .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
        }
}

     现在执行下结果是404,这是因为这个RestFul API我们还没有编写,现在我们在src/main/java下新建一个包com.rz.controller,文件名为UserController.java

package com.rz.com.rz.controller;

import com.rz.com.rz.entity.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
public class UserController {

    @GetMapping("/user")
    public List<User> getAllUser(@RequestParam(required = false,defaultValue = "jojo") String name){

        System.out.print(name);
        List<User> users=new ArrayList<User>();
        users.add(new User());
        users.add(new User());
        users.add(new User());
        return users;

    }
}

3.2编写一个带有普通参数的RestFul API测试用例

当业务复杂的情况下,查询条件会有很多,可以构造对象进行查询
在/src/main/java/com/rz/entity下建立一个类

package com.rz.com.rz.entity;

public class UserQueryCondition {

    private String name;

    private int age;

    private int ageTo;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getAgeTo() {
        return ageTo;
    }

    public void setAgeTo(int ageTo) {
        this.ageTo = ageTo;
    }
}

修改UserController类的方法

@GetMapping("/user")
    public List<User> getAllUser(UserQueryCondition userQueryCondition){

        //反射的toString工具,用于打印对象参数的值
        System.out.println(ReflectionToStringBuilder.toString(userQueryCondition, ToStringStyle.MULTI_LINE_STYLE));

        List<User> users=new ArrayList<User>();
        users.add(new User());
        users.add(new User());
        users.add(new User());
        return users;

    }

修改UserControllerTest类的方法

 @Test
        public void whenQuerySuccess() throws Exception {
            //perform 发送一个模拟请求
            //由于是restful风格,需要写contentType
            //andExpect 对请求结果的期望
            //jsonPath  解析返回来的json的内容
            mockMvc.perform(MockMvcRequestBuilders.get("/user")
                    .param("name","joon")  //当Java方法有@RequestParam注解时候需要加上该参数
                    .param("age","12")
                   .contentType(MediaType.APPLICATION_JSON_UTF8))
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    //认为返回的是一个长度为三的集合
                    .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
        }

3.3 编写一个带有普通参数的RestFul API测试用例

修改UserController类的方法

//Pageable  pageable 分页对象
//@PageableDefault(page = 3,size = 1,sort = "age,desc")默认分页注解
@GetMapping("/user")
  public List<User> getAllUser(UserQueryCondition userQueryCondition,@PageableDefault(page = 3,size = 1,sort = "age,desc") Pageable pageable){


        //反射的toString工具,用于打印对象参数的值
        System.out.println(ReflectionToStringBuilder.toString(userQueryCondition, ToStringStyle.MULTI_LINE_STYLE));

        System.out.println(pageable.getPageNumber());
        System.out.println(pageable.getPageSize());
        System.out.println(pageable.getSort());

        List<User> users=new ArrayList<>();
        users.add(new User());
        users.add(new User());
        users.add(new User());
        return users;

    }

修改UserControllerTest类的方法

        @Test
        public void whenQuerySuccess() throws Exception {
            //perform 发送一个模拟请求
            //由于是restful风格,需要写contentType
            //andExpect 对请求结果的期望
            //jsonPath  解析返回来的json的内容
            mockMvc.perform(MockMvcRequestBuilders.get("/user")
                    .param("name","joon")  //当Java方法有@RequestParam注解时候需要加上该参数
                    .param("age","12")
                    //**********分页参数
                    .param("size","1")
                    .param("page","3")
                    .param("sort","age,desc")
                    //**********分页参数
                   .contentType(MediaType.APPLICATION_JSON_UTF8))
                    .andExpect(MockMvcResultMatchers.status().isOk())
                    //认为返回的是一个长度为三的集合
                    //jsonPath:可以在github上搜索相关资料
                    .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
        }

4.编写第二个RestFul API测试用例

4.1 编写一个带有映射片段的RestFul API测试用例

继续在UserControllerTest类中添加测试方法

        /***
         * 查询单个用户
         */
        @Test
        public void whenGetSignInfoSuccess() throws Exception {
                mockMvc.perform(MockMvcRequestBuilders.get("/user/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                        .andExpect(MockMvcResultMatchers.status().isOk())
                        .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("tom"));
        } 

在相对应的UserController类中新增方法

@GetMapping("/user/{id}")
    public User getUserInfo(@PathVariable(name = "id") String idxxx){
        User user=new User();
        user.setName("tom");
        return user;
    }

4.2 编写一个url声明中使用正则表达式的RestFul API测试用例

继续在UserControllerTest类中添加测试方法

         /***
         * 查询单个用户(url出现限制)
         */
        @Test
        public void whenGetInfoSuccess() throws Exception {
                mockMvc.perform(MockMvcRequestBuilders.get("/user/a")
                        .contentType(MediaType.APPLICATION_JSON_UTF8))
                        .andExpect(MockMvcResultMatchers.status().is4xxClientError());

        }

在相对应的UserController类中新增方法

    /***
     * 返回用户具体信息(url有正则的限制,只能是数字)
     * @param idxxx
     * @return
     */
 @GetMapping("/user/{id:\\d+}")  //
    public User getUserInfo(@PathVariable(name = "id") String idxxx){
        User user=new User();
        user.setName("tom");
        return user;
    }

4.3 编写一个使用jsonview注解的RestFul API测试用例

     @jsonview:可以对controller每个方法对于同一个对象有不同的输出结果,可以指定不返回哪些字段

4.3.1 使用接口来声明多个视图

4.3.2 在值对象的get方法上指定视图

          在rz-security-demo项目下的/src/main/java/com/rz/entity/下的User类添加接口声明,并在在值对象的get方法上指定视图

package com.rz.com.rz.entity;

import com.fasterxml.jackson.annotation.JsonView;

public class User {

    //简单视图
    public interface  UserSimpleView{};

    //详细视图
    public interface  UserDetailView  extends UserSimpleView {};

    private String name;

    private String pwd;

    @JsonView(UserSimpleView.class)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


    @JsonView(UserDetailView.class)
    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

}

4.3.3 在Controller方法上指定视图

          在rz-security-demo项目下的/src/main/java/com/rz/controller下的UserController类的指定方法中添加@jsonview注解

 /***
     * 返回所有用户信息
     * @param userQueryCondition
     * @param pageable
     * @return
     */
    @JsonView(User.UserSimpleView.class)
    @GetMapping("/user")
    public List<User> getAllUser(UserQueryCondition userQueryCondition,@PageableDefault(page = 3,size = 1,sort = "age,desc") Pageable pageable){

        //反射的toString工具,用于打印对象参数的值
        System.out.println(ReflectionToStringBuilder.toString(userQueryCondition, ToStringStyle.MULTI_LINE_STYLE));

        System.out.println(pageable.getPageNumber());
        System.out.println(pageable.getPageSize());
        System.out.println(pageable.getSort());

        List<User> users=new ArrayList<>();
        users.add(new User());
        users.add(new User());
        users.add(new User());
        return users;

    }


    /***
     * 返回用户具体信息(url有正则的限制,只能是数字)
     * @param idxxx
     * @return
     */
    @JsonView(User.UserDetailView.class)
    @GetMapping("/user/{id:\\d+}")
    public User getUserInfo(@PathVariable(name = "id") String idxxx){
        User user=new User();
        user.setName("tom");
        return user;
    }

我们可以对结果在控制台查看,修改UserControllerTest类的方法:

         /***
         * 查询单个用户
         */
        @Test
        public void whenGetSignInfoSuccess() throws Exception {
                String result=     mockMvc.perform(MockMvcRequestBuilders.get("/user/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8))
                        .andExpect(MockMvcResultMatchers.status().isOk())                        .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("tom"))
                        .andReturn().getResponse().getContentAsString();

                System.out.println(result);
        }

5.编写第三个RestFul API测试用例

5.1 编写一个RequestBody注解的RestFul API测试用例

首先在User实体类中添加id字段

    private String id;

   @JsonView(UserSimpleView.class)
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

然后在UserController类中添加新增用户的方法:

  /***
     * 创建用户
     * @param user
     * @return
     */
    @PostMapping
    public User createUser(@RequestBody  User user){

        //反射的toString工具,用于打印对象参数的值
        System.out.println(ReflectionToStringBuilder.toString(user,ToStringStyle.MULTI_LINE_STYLE));

        user.setId("1");
        return user;
    }

在UserControllerTest类中新增测试方法:

         /***
         * 创建用户
         * 如果返回405 ,表示有该服务,但是不支持此种http访问方式
         * @throws Exception
         */
        @Test
        public void whenCreateSuccess() throws Exception {
                String content="{\"name\":\"tom\",\"pwd\":null}";

                mockMvc.perform(MockMvcRequestBuilders.post("/user")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(content))
                        .andExpect(MockMvcResultMatchers.status().isOk())
                        .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"));
        }

5.2 编写一个带有日期类型参数的处理的RestFul API测试用例

首先在User实体类中添加birthday字段

  @JsonView(UserSimpleView.class)
    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

然后在UserControllerTest类中修改新增用户的测试方法:

        /***
         * 创建用户
         * 如果返回405 ,表示有该服务,但是不支持此种http访问方式
         * @throws Exception
         */
        @Test
        public void whenCreateSuccess() throws Exception {

                Date data=new Date();
                System.out.println("客户端发送的数据:"+data.getTime());


                String content="{\"id\":null,\"name\":\"tom\",\"pwd\":null,\"birthday\":"+data.getTime()+"}";

                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);
        }

6.编写第四个RestFul API测试用例

6.1 编写一个@Valid 注解和BindigResult验证的RestFul API测试用例

    目的是为了:为了提取重复的效验逻辑
首先修改User实体类,加上 @NotBlank注解
 @NotBlank
    private String pwd;

然后在UserControllerTest类中修改新增用户的测试方法:

     /***
     * 创建用户
     * @param user
     * @return
     */
    @PostMapping
    public User createUser(@Valid @RequestBody  User user, BindingResult errors){

        if(errors.hasErrors()){
            errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage()));
        }

        //反射的toString工具,用于打印对象参数的值
        System.out.println(ReflectionToStringBuilder.toString(user,ToStringStyle.MULTI_LINE_STYLE));

        user.setId("1");
        return user;
    }

注意事项:

  • @Valid 用于验证字段,其中有一个错误就打回,进入不了方法体
  • BindigResult 可以将带着错误信息进入到方法体中,做业务记录或处理
  • 两个需要配合使用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章