SpringBoot之SpringMVC的使用,并通过RestTemplate和MockMvc调用接口实现单元测试

474 阅读8分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

一、springMVC快速使用

通过使用SpringBoot添加web启动器来实现SpringMVC应用

1-1、创建springboot项目

首先创建一个springboot项目,并添加web的启动器,这步操作比较简单,就不做演示了

1-1-1、创建User实体

package com.jony.entity;

import java.util.Date;

public class User {

    private Integer id;

    private String username;

    private String address;

    private Date birthday;

    public Date getBirthday() {
        return birthday;
    }

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

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + ''' +
                ", address='" + address + ''' +
                '}';
    }

    public User(Integer id, String username, String address) {
        this.id = id;
        this.username = username;
        this.address = address;
    }

    public User(String username, String address) {
        this.username = username;
        this.address = address;
    }

    public User(Integer id, String username, String address, Date birthday) {
        this.id = id;
        this.username = username;
        this.address = address;
        this.birthday = birthday;
    }

    public User() {
    }
}

1-1-2、创建用于返回请求状态、信息、数据的Result类

package com.jony.entity;

public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public Result(Integer code, String message) {
        this.code = code;
        this.message = message;
    }


    public Result() {
    }

    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", message='" + message + ''' +
                ", data=" + data +
                '}';
    }
}

1-1-3、创建service模拟数据库操作

package com.jony.service;

import com.jony.entity.User;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
public class UserService {
    private static Map<Integer, User> users = new HashMap<>();

    static {
        users.put(1, new User(1, "zhangsan", "北京", new Date()));
        users.put(2, new User(2, "lisi", "上海"));
        users.put(3, new User(3, "wangwu", "深圳"));
        users.put(4, new User(4, "zhaoliu", "杭州"));
        users.put(5, new User(5, "sunqi", "广州"));
    }

    /**
     * 根据id查询用户
     *
     * @param id
     * @return
     */
    public User getUserById(Integer id) {
        return users.get(id);
    }


    /**
     * 查询所有用户
     *
     * @return
     */
    public List<User> getAllUser() {
        return new ArrayList(users.values());
    }

    /**
     * 更新
     *
     * @param user
     * @return
     */
    public void update(User user) {
        users.replace(user.getId(), user);
    }


    /**
     * 新增
     *
     * @param user
     * @return
     */
    public void add(User user) {
        Integer newId = users.size() + 1;
        user.setId(newId);
        users.put(newId, user);
    }

    /**
     * 删除
     *
     * @return
     */
    public void delete(Integer id) {
        users.keySet().removeIf(key -> key == id);
    }
}

1-1-4、创建控制器

package com.jony.controller;

import com.jony.entity.Result;
import com.jony.entity.User;
import com.jony.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

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

    @Autowired
    UserService userService;

    //Rest  /user/1
    @GetMapping("/{id}")
    public Result getUser(@PathVariable Integer id) {
        User user = userService.getUserById(id);
        return new Result<>(200, "messge", user);
    }

    // 新增 /user/add
    @PostMapping("/add")
    public Result addUser(@RequestBody User user) {
        System.out.println(user);
        userService.add(user);
        return new Result<>(200, "添加成功", userService.getAllUser());
    }

    // 修改 /user1
    @PutMapping("/{id}")
    public Result editUser(@RequestBody User user) {
        userService.update(user);
        return new Result<>(200, "修改成功", userService.getAllUser());
    }

    // 删除 /user1
    @DeleteMapping("/{id}")
    public Result deleteUser(@PathVariable Integer id) {
        userService.delete(id);
        return new Result<>(200, "删除成功", userService.getAllUser());
    }

}

1-1-5、最终项目创建的代码结构

image.png

二、服务接口的调用

上面通过springboot创建了一个简单的服务,那么我们改如何调用这个服务呢?下面来介绍下面这几种方式

2-1、使用通过RestTemplate调用服务

RestTemplate是Spring提供的用于访问Rest服务的,RestTemplate提供了多种便捷访问远程Http服务的方法,传统情况下在java代码里访问restful服务,一般使用Apache的HttpClient。不过此种方法使用起来太过繁琐。spring提供了一种简单便捷的模板类来进行操作,这就是RestTemplate。

适用于微服务架构下 服务之间的远程调用 ps: 以后使用微服务架构, spring cloud feign

2-1-1、RestTemplate的相关方法

对应操作对应方法
DELETEdelete
GETgetForObject按照指定Class返回对象
getForEntity返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
HEADheadForHeaders
OPTIONSoptionsForAllow
POSTpostForLocation/postForObject
PUTput
any支持任何请求方法类型exchange/execute

2-1-1-1、get操作

如下图,使用get操作,有getForObject及getForEntity,并且每个都有三个方法,参数分别为url/返回参数类型/传入的参数。

image.png

如果使用非限定参数的方法,传入的方法就可以以","分隔,比如需要调用的接口url为:baseUrl/${uid}/${username}
调用的时候就可以使用:restTemplate.getForObject(url,Result.class,123,zhangsan)

image.png

2-1-1-2、使用RestTemplate的注意事项

在使用RestTemplate在调用其他接口的时候,如果通过非URL传递参数,会被默认转换为Json的格式进行传递,因此被调用的方法,接收参数必须以Json的格式接收,否则无法接收参数

2-1-2、创建一个项目用于调用刚刚的服务

上面已经通过springboot创建了一个服务,为了更真实的演示服务之间的调用,我们再创建一个springboot服务

2-1-2-1、调用get请求查询

image.png 上图中,可以看到通过构造函数初始化了RestTemplate,然后就可以通过 restTemplate.getForObject进行调用了。

2-1-2-2-1、测试调用服务

下图中,通过调用刚刚RestTemplate的服务,然后再通过RestTemplate调用8080服务的接口,这样就可以完成两个服务之间的调用了。 image.png

2-1-2-2、调用post新增

image.png 上图调用了postForEntity的方法,目前可以看到传入了三个参数、分别为url/post对象/返回类型,postForEntity同样也支持通过URL传参,如下:

image.png

${a}/${b}为url传参,后面对应的1,2为对应参数

2-1-2-2-1、测试调用

可以看到新增的用户已经添加进去。 image.png

上面提到使用RestTemplate通过非url传参,被调用的接口必须使用Json格式进行接收参数,现在我们把接口改为非Json,看一下效果
原来方法:

// 新增 /user/add
@PostMapping("/add")
public Result addUser(@RequestBody User user) {
    System.out.println(user);
    userService.add(user);
    return new Result<>(200, "添加成功", userService.getAllUser());
}

改后方法:

// 新增 /user/add
@PostMapping("/add")
public Result addUser(User user) {
    System.out.println(user);
    userService.add(user);
    return new Result<>(200, "添加成功", userService.getAllUser());
}

测试结果:
可以看到我们传递的参数未被接收到。 image.png

2-1-2-3、调用put请求

我们被调用的方法,在更新用户的同时又将所有用户返回,因此我们更新之后,就可以再次获得所有用户了,被调用方法如下:

image.png

但是restTemplate中的put是没有返回值的,这就代表虽然我们可以更新数据,但是无法获得返回数据,如下图: image.png

针对以上情况其实我们可以使用restTemplate的any的操作来进行处理,any可以处理上面table中的任意方法,如get/post/head等,

最终我们使用any的exchange调用put方法(感兴趣的同学,可以自己试下execute),如下:

image.png

2-1-2-3-1、测试调用

image.png 如上图,我们通过使用any的方式使用exchange方法,就实现了调用put请求,并获得放回值的处理。

2-1-2-4、调用delete

调用delete,使用的就是delete方法,delete同样没有返回值,如果想取返回值,同样可以使用any中的exchange/execute方法。

image.png 删除只需要通过URL传删除的ID即可,因此可以把HttpEntity设为null,然后再把HttpMethod设为Delete即可。

2-1-2-4-1、测试调用

如下图,可以看到已经没有ID为1的数据。 image.png

2-1-3、在单元测试中使用RestTemplate

需要注意的是在单元测试中需要使用TestRestTemplate,其他过程和上面远程调用一致,如下图:

image.png

2-2、通过MockMvc调用

MockMvc是由spring-test包提供,实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,使得测试速度快、不依赖网络环境。同时提供了一套验证的工具,结果的验证十分方便。

比如我们当前环境需要依赖一些外部的环境,这个时候如果启动程序还得启动其他外部程序,那就太繁琐了,这个时候就可以使用MockMvc来解决这个问题,下面来进行使用吧。

2-2-1、添加相关注解

使用MockMvc需要给测试添加两个注解分为为:@SpringBootTest@AutoConfigureMockMvc 如下:

image.png 一般情况下使用一些功能需要添加依赖,MockMvc的依赖其实在

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

可以看下关系图

image.png

2-2-2、注入MockMvc并使用

通过AutoWrite注入MockMvc,同时添加junit测试注解,需要注意的是需要引用junit5的Test

image.png

最终代码如下:

image.png

2-2-2-1、控制台请求响应相关信息

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /user/1
       Parameters = {}
          Headers = []
             Body = null
    Session Attrs = {}

Handler:
             Type = com.jony.controller.UserController
           Method = com.jony.controller.UserController#getUser(Integer)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"code":200,"message":"messge","data":{"id":1,"username":"zhangsan","address":"北京","birthday":"2022-05-24T12:19:22.066+00:00"}}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

2-2-2-2、设置响应的文本类型

通过accept设置响应的文本类型,如下

image.png

通过设置accept(MediaType.APPLICATION_JSON_UTF8),就可以设置Header信息了,如下:

image.png

2-2-2-3、设置请求参数

通过param设置请求参数,如下: image.png

2-2-2-4、设置断言

通过使用andExpect设置响应断言,

可以设置响应状态的断言 image.png

设置响应数据断言 其中为响应的数据,通过 为响应的数据,通过.data.username就可以获得username的值,然后再进行判断值是否为zhangsan image.png

设置不匹配的断言数据,控制台就会告诉我们断言失败,如下图:

image.png

2-2-2-5、添加结果处理器

通过andDo可以添加结果处理器,我们就可以对结果进行处理,比如我们输出结果如下:

image.png

2-2-2-6、设置请求的类型

通过设置contentType就可以谁知请求的Conten-Type了。 image.png

2-2-3、使用MockMvc发送post请求

通过post可以发送相关数据

image.png

2-2-4、MockMvc的使用方法

* 1、mockMvc.perform执行一个请求。
* 2、MockMvcRequestBuilders.get("XXX")构造一个请求。
* 3、ResultActions.param添加请求传值
* 4、ResultActions.accept(MediaType.TEXT_HTML_VALUE)设置返回类型
* 5、ResultActions.contentType(MediaType.TEXT_HTML_VALUE)设置请求数据类型
* 6、ResultActions.andExpect添加执行完成后的断言。
* 7、ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情
*   比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。
* 8、ResultActions.andReturn表示执行完成后返回相应的结果。