如何测试Spring MVC应用

 更新时间:2020年10月12日 17:07  点击:1933

Spring的依赖注入使得我们的代码非常容易进行单元测试——@Controller, @Service@Entity等注解标注的类基本都是POJO(plain old Java object),也就是说很少依赖于Spring容器本身的API。我们可以非常容易地使用JUnit或TestNG编写测试代码。另一方面,对于三层架构的Spring Web应用(Controller, Service, DAO),使用Mock活Stub方法也能够更好的来测试我们的代码逻辑。例如Service层代码的单元测试中,依赖的DAO(或Repository)对象都是根据应用测试需求Mock出来的,而不需要真正去访问数据库。

Spring Web测试

在对Spring Web应用中的@Controller代码进行单元测试的过程中,一般的方法是创建@Controller对象,同时将它依赖的一些Mock对象——例如MockHttpServletRequest, MockHttpServletResponse(都由spring-test模块提供,无需自己编写)作为@Controller方法的参数。但是对于处理Web请求的@Controller代码来说,仅仅测试Handler方法里的代码是远远不够的,对于一个处理HTTP请求的@Controller`,我们还需要测试:

  • @RequestMapping路由是否正确
  • 数据绑定、类型转换、校验逻辑是否正确——数据包括URL参数、表单、@PathVariable
  • @InitBinder, @ModelAttribute, @ExceptionHandler等注解的方法或属性计算过程

上述过程贯穿于HTTP请求处理的生命周期中,所以对于Spring Web应用中@Controller代码单元测试的概念,应该做一些扩充——不仅仅局限于代码本身,也要结合MVC框架中的各个处理过程。

本文接下来的内容代码,都以Spring Boot为例,首先假设我们通过Spring Boot创建了一个最简单的Web Mvc应用——包含了一个最简单的Conroller,处理/users/{id}对应的HTTP请求,返回值是id={id}(通过String.format()方法),那么可以为它创建如下测试代码:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringMvcTestDemoApplication.class)
@WebAppConfiguration
public class SpringMvcTestDemoApplicationTests {

  private MockMvc mockMvc;

  @Before
  public void init() {
    this.mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
  }

  @Test
  public void getUserById() throws Exception {
    long id = 1;
    this.mockMvc.perform(get("/users/" + id))
        .andExpect(status().isOk())
        .andExpect(content().string("id=" + id));
  }

}

运行上述测试时,很容易从控制台中的日志发现,SpringJUnit4ClassRunner创建了一个Spring Web应用上下文,并且在其中进行了Web Mvc框架的配置——这里是注册@RequestMapping方法。接下来mockMvc.perform()方法实际上向该Spring Web应用发起了一个HTTP请求:

  • 请求的url为/users/{id}
  • andExpect()方法也就是测试中常用的Assert
  • status()用于检查返回状态吗,这里是200
  • content()用于检查内容

如果我们不小心将@RequestMapping的路由路径写错,那么这里运行的结果一定不会是status().isOk(),这也就完成了对HTTP请求路由的测试。接下来我们将继续探索MVC框架中的其他方面。

Mock Service

在Spring Web应用三层结构里,Controller层代码通常会调用Service层代码,例如:

@RestController
public class UserController {

  @Autowired
  private UserService userService;

  @RequestMapping(value = "/users/{id}", method = GET)
  public String get(@PathVariable("id") long id) {
    String username = userService.getUsername(id);
    return String.format("username=%s", username);
  }
}

UserController进行单元测试需要排除Service代码的影响,所以需要对Service进行Mock,这里我们使用Mockito框架,在Spring上下文中Mock一个UserService对象:

@Configuration
public class TestContext {

  @Bean
  public UserService userServiceMock() {
    return Mockito.mock(UserService.class);
  }
}

同时通过Mockito的API来MockUserService.getUsername(long id)方法,@Controller的测试代码如下:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {
    SpringMvcTestDemoApplication.class,
    TestContext.class
})
@WebAppConfiguration
public class SpringMvcTestDemoApplicationTests {

  @Autowired
  UserService userService;

  @Autowired
  UserController controller;

  MockMvc mockMvc;

  @Before
  public void init() {
    this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
  }

  @Test
  public void getUserById() throws Exception {
    long id = 1L;
    String ricky = "Ricky";
    Mockito.when(userService.getUsername(id)).thenReturn(ricky);
    this.mockMvc.perform(get("/users/" + id))
        .andExpect(status().isOk())
        .andExpect(content().string("username=" + ricky));
  }

}

由于需要进行依赖注入,所以UserServiceUserController都使用@Autowired注解。Mockito.when(userService.getUsername(id)).thenReturn(ricky);表明userService.getUsername()方法的参数为1L时,返回值为"Ricky",Mockito提供能很多强大的Mock API,更多用法请参考官方文档。

测试REST API

当我们构建REST服务时,大多数情况会使用JSON作为数据交换格式,Spring MVC测试框架同样提供了一种简洁的方式对JSON结果进行断言,假设现在有@Controller如下:

@RequestMapping(value = "/users/{id}/json", method = GET)
public User getUser(@PathVariable("id") long id) {
  String username = userService.getUsername(id);
  return new User(id, username);
}

static class User {
  public long id;
  public String username;
  //构造方法,Getter/Setter略
}

实际应用返回的JSON数据是:

{
 "id": 1,
 "username": "Ricky"
}

测试代码可以这样断言:

@Test
public void getUser() throws Exception {
  this.mockMvc.perform(get("/users/{id}/json", id).accept(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$.id").value(id.intValue()))
      .andExpect(jsonPath("$.username").value(ricky));
}

$.id, $.username都是JsonPath提供的JSON表达式,可以通过jsonPathvalue()等方法来轻松对JSON数据进行断言而不需要自己编写JSON文本处理。

以上就是如何测试Spring MVC应用的详细内容,更多关于测试Spring MVC应用的资料请关注猪先飞其它相关文章!

[!--infotagslink--]

相关文章

  • Spring AOP 对象内部方法间的嵌套调用方式

    这篇文章主要介绍了Spring AOP 对象内部方法间的嵌套调用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-08-29
  • Spring Cloud 中@FeignClient注解中的contextId属性详解

    这篇文章主要介绍了Spring Cloud 中@FeignClient注解中的contextId属性详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-25
  • Springboot如何实现Web系统License授权认证

    这篇文章主要介绍了Springboot如何实现Web系统License授权认证,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-05-28
  • 解决@SpringBootTest 单元测试遇到的坑

    这篇文章主要介绍了解决@SpringBootTest 单元测试遇到的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-14
  • 如何在Spring WebFlux的任何地方获取Request对象

    这篇文章主要介绍了如何在Spring WebFlux的任何地方获取Request对象,帮助大家更好的理解和使用springboot框架,感兴趣的朋友可以了解下...2021-01-26
  • 详解SpringCloudGateway内存泄漏问题

    这篇文章主要介绍了详解SpringCloudGateway内存泄漏问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-07-16
  • DWVA上传漏洞挖掘的测试例子

    DVWA (Dam Vulnerable Web Application)DVWA是用PHP+Mysql编写的一套用于常规WEB漏洞教学和检测的WEB脆弱性测试程序。包含了SQL注入、XSS、盲注等常见的一些安全漏洞...2016-11-25
  • PHP测试成功的邮件发送案例

    mail()函数的作用:连接到邮件服务器,利用smtp协议,与该服务器交互并投邮件。注意:1、mail函数不支持esmtp协议,---即,只能直投,不能登陆2、由上条,我们只能直投至最终的收件服务器地址.而该地址,又是在PHP.ini中指定的,所...2015-10-30
  • Spring为什么不推荐使用@Autowired注解详析

    @Autowired 注解的主要功能就是完成自动注入,使用也非常简单,但这篇文章主要给大家介绍了关于Spring为什么不推荐使用@Autowired注解的相关资料,需要的朋友可以参考下...2021-11-03
  • 用VirtualBox构建MySQL测试环境

    宿主机使用网线的时候,客户机在Bridged Adapter模式下,使用Atheros AR8131 PCI-E Gigabit Ethernet Controller上网没问题。 宿主机使用无线的时候,客户机在Bridged Adapter模式下,使用可选项里唯一一个WIFI选项,Microsoft Virtual Wifi Miniport Adapter也无法上网,故弃之。...2013-09-19
  • Springboot如何使用mybatis实现拦截SQL分页

    这篇文章主要介绍了Springboot使用mybatis实现拦截SQL分页,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-06-19
  • SpringMVC文件上传原理及实现过程解析

    这篇文章主要介绍了SpringMVC文件上传原理及实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-07-15
  • C# MVC模式中应该怎样区分应用程序逻辑(Controller层)和业务逻辑(Model层)?

    这篇文章主要介绍了C# MVC模式中应该怎样区分应用程序逻辑(Controller层)和业务逻辑(Model层)?,这也小编做.NET项目时经常思考和让人混乱的一个问题,这篇文章写的挺好,一下清晰了许多,需要的朋友可以参考下...2020-06-25
  • Spring Data JPA 关键字Exists的用法说明

    这篇文章主要介绍了Spring Data JPA 关键字Exists的用法说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-10
  • tomcat启动完成执行 某个方法 定时任务(Spring)操作

    这篇文章主要介绍了tomcat启动完成执行 某个方法 定时任务(Spring)操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-25
  • 使用Maven 搭建 Spring MVC 本地部署Tomcat的详细教程

    这篇文章主要介绍了使用Maven 搭建 Spring MVC 本地部署Tomcat,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-08-16
  • Java Spring Cloud 负载均衡详解

    这篇文章主要介绍了Spring Cloud负载均衡及远程调用实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2021-09-18
  • SpringMvc自动装箱及GET请求参数原理解析

    这篇文章主要介绍了SpringMvc自动装箱及GET请求参数原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-09-19
  • Springboot使用thymeleaf动态模板实现刷新

    这篇文章主要介绍了Springboot使用thymeleaf动态模板实现刷新,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-08-31
  • PHP测试成功的邮件发送案例

    mail()函数的作用:连接到邮件服务器,利用smtp协议,与该服务器交互并投邮件。注意:1、mail函数不支持esmtp协议,---即,只能直投,不能登陆2、由上条,我们只能直投至最终的收件服务器地址.而该地址,又是在PHP.ini中指定的,所...2015-10-30