- 内容提要
- 作者简介
- 译者简介
- 前言
- HTTP
- Servlet 和 JSP
- 下载 Spring 或使用 STS 与 Maven/Gradle
- 手动下载 Spring
- 使用 STS 和 Maven/Gradle
- 下载 Spring 源码
- 本书内容简介
- 下载示例应用
- 第 1 章Spring 框架
- 第 2 章模型 2 和 MVC 模式
- 第 3 章Spring MVC 介绍
- 第 4 章基于注解的控制器
- 第 5 章数据绑定和表单标签库
- 第 6 章转换器和格式化
- 第 7 章验证器
- 第 8 章表达式语言
- 第 9 章JSTL
- 第 10 章国际化
- 第 11 章上传文件
- 第 12 章下载文件
- 第 13 章应用测试
- 附录 A Tomcat
- 附录 B Spring Tool Suite 和 Maven
- 附录 C Servlet
- 附录 D JavaServer Pages
- 附录 E 部署描述符
13.5 对 Spring MVC Controller 单元测试
你已经学习了如何在 Spring MVC 应用程序中测试各个类。但 Controller 有点不同,因为它们通常与 Servlet API 对象(如 HttpServletRequest、HttpServletResponse、HttpSession 等)交互。在许多情况下,你将需要模拟这些对象以正确测试控制器。
像 Mockito 或 EasyMock 这样的框架是可以模拟任何 Java 对象的通用模拟框架。你必须自己配置生成的对象(使用一系列 when 语句)。Spring-Test 模拟对象是专门为使用 Spring 而构建的,并且与真实对象更接近,更容易使用。
以下小节讨论了一些更重要的单元测试控制器类型。
13.5.1 MockHttpServletRequest 和 MockHttpServletResponse
当调用控制器时,你可能需要传递 HttpServletRequest 和 HttpServletResponse。在生产环境中,两个对象都由 servlet 容器本身提供。在测试环境中,你可以使用 org.springframework.mock.web 包中的 Spring MockHttpServletRequest 和 MockHttpServletResponse 类。
这两个类很容易使用。你可以通过调用其无参构造函数来创建实例:
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();MockHttpServletRequest 类实现了 javax.servlet.http.HttpServletRequest,并允许你将实例配置将看起来像一个真正的 HttpServletRequest。它提供了方法来设置 HttpServletRequest 中的所有属性以及获取其属性的值。表 13.1 显示了它的一些方法。
表 13.1 MockHttpServletRequest 的主要方法
方法 | 描述 |
|---|---|
addHeader | 添加一个 HTTP 请求头 |
addParameter | 添加一个请求参数 |
getAttribute | 返回一个属性 |
getAttributeNames | 返回包含了全部属性名的一个 Enumeration 对象 |
getContextPath | 返回上下文路径 |
getCookies | 返回全部的 cookies |
setMethod | 设置 HTTP 方法 |
setParameter | 设置一个参数值 |
setQueryString | 设置查询语句 |
setRequestURI | 设置请求 URI |
MockHttpServletResponse 实现了 javax.servlet.http.HttpServletResponse,并提供了配置实例的其他方法。表 13.2 显示了其一些主要的方法。
表 13.2 MockHttpServletResponse 的主要方法
方法 | 描述 |
|---|---|
addCookie | 添加一个 cookie |
addHeader | 添加一个 HTTP 响应头 |
getContentLength | 返回内容长度 |
getWriter | 返回 Writer |
getOutputStream | 返回 ServletOutputStream |
考虑清单 13.21 的 VideoController 类。
清单 13.21 VideoController 类
package com.example.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class VideoController {
@RequestMapping(value = "/mostViewed")
public String getMostViewed(HttpServletRequest request,
HttpServletResponse response) {
Integer id = (Integer) request.getAttribute("id");
if (id == null) {
response.setStatus(500);
} else if (id == 1) {
request.setAttribute("viewed", 100);
} else if (id == 2) {
request.setAttribute("viewed", 200);
}
return "mostViewed";
}
}VideoController 类的 getMostViewed 方法中,若请求属性 id 存在且值为 1 或 2,则添加请求属性“viewed”。否则,不添加请求属性。
清单 13.22 中的 VideoControllerTest 类使用两个测试方法验证这一点。
清单 13.22 VideoControllerTest 类
package com.example.controller;
import org.junit.Test;
import static org.junit.Assert.*;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
public class VideoControllerTest {
@Test
public void testGetMostViewed() {
VideoController videoController = new VideoController();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/mostViewed");
request.setAttribute("id", 1);
MockHttpServletResponse response = new MockHttpServletResponse();
videoController.getMostViewed(request, response);
assertEquals(200, response.getStatus());
assertEquals(100L, (int) request.getAttribute("viewed"));
}
@Test
public void testGetMostViewedWithNoId() {
VideoController videoController = new VideoController();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/mostViewed");
MockHttpServletResponse response = new MockHttpServletResponse();
videoController.getMostViewed(request, response);
assertEquals(500, response.getStatus());
assertNull(request.getAttribute("viewed"));
}
}testGetMostViewed 方法实例化 VideoController 类并创建两个 mock 对象,一个 MockHttp ServletRequest 和一个 MockHttpServletResponse。它还设置请求 URI,并向 MockHttpServletRequest 添加属性“id”。
VideoController videoController = new VideoController();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/mostViewed");
request.setAttribute("id", 1);
MockHttpServletResponse response = new MockHttpServletResponse();然后调用 VideoController 的 getMostView 方法,传递 mock 对象,然后验证响应的状态码为 200,请求包含一个值为 100 的“viewed”属性。
videoController.getMostViewed(request, response);
assertEquals(200, response.getStatus());
assertEquals(100L, (int) request.getAttribute("viewed"));VideoControllerTest 中的第二个方法类似方法一,但不会向 MockHttpServletRequest 对象添加“id”属性。因此,在调用控制器的方法时,它接收 HTTP 响应状态代码 500,并且在 MockHttpServletRequest 对象中没有“viewed”属性。
13.5.2 ModelAndViewAssert
ModelAndViewAssert 类是 org.springframework.web.servlet 包的一部分,是另一个有用的 Spring 类,用于测试从控制器的请求处理方法返回的 ModelAndView。回想一下第 4 章中介绍过,ModelAndView 是请求处理方法可以返回的类型之一,是包含有关请求处理方法的模型和视图的信息的一个 bean。
表 13.3 列举了 ModelAndViewAssert 的一些主要方法。
表 13.3 ModelAndViewAssert 的主要方法
方法 | 描述 |
|---|---|
assertViewName | 检查 ModelAndView 的视图名称是否与预期名称匹配 |
assertModelAttributeAvailable | 检查 ModelAndView 的模型是否包含具有预期模型名称的属性 |
assertModelAttributeValue | 检查 ModelAndView 模型是否包含具有指定名称和值的属性 |
assertSortAndCompareList-ModelAttribute | 对 ModelAndView 的列表排序,然后将其与预期列表进行比较 |
例如,考虑清单 13.23 中的 Book 类。这是一个简单的 bean 类,有 4 个属性:isbn、title、author 和 pubDate
清单 13.23 Book 类
package com.example.model;
import java.time.LocalDate;
public class Book {
private String isbn;
private String title;
private String author;
private LocalDate pubDate;
public Book(String isbn, LocalDate pubDate) {
this.isbn = isbn;
this.pubDate = pubDate;
}
public Book(String isbn, String title, String author,
LocalDate pubDate) {
this.isbn = isbn;
this.title = title;
this.author = author;
this.pubDate = pubDate;
}
// getters and setters not shown to save space
@Override
public boolean equals(Object otherBook) {
return isbn.equals(((Book)otherBook).getIsbn());
}
}清单 13.24 中的 BookController 类是一个 Spring MVC 控制器,它包含一个请求处理方法 getLatestTitles。该方法接受 pubYear 路径变量,并返回一个 ModelAndView,如果 pubYear 的值为“2016”,它将包含书籍列表。
清单 13.24 BookController 类
package com.example.controller;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.example.model.Book;
@Controller
public class BookController {
@RequestMapping(value = "/latest/{pubYear}")
public ModelAndView getLatestTitles(
@PathVariable String pubYear) {
ModelAndView mav = new ModelAndView("Latest Titles");
if ("2016".equals(pubYear)) {
List<Book> list = Arrays.asList(
new Book("0001", "Spring MVC: A Tutorial",
"Paul Deck",
LocalDate.of(2016, 6, 1)),
new Book("0002", "Java Tutorial",
"Budi Kurniawan", LocalDate.of(2016, 11, 1)),
new Book("0003", "SQL", "Will Biteman",
LocalDate.of(2016, 12, 12)));
mav.getModel().put("latest", list);
}
return mav;
}
}测试 BookController 的一种简单方式是使用 ModelAndViewAssert 中的静态方法,如清单 13.25 中的 BookControllerTest 类所示。
清单 13.25 BookControllerTest 类
package com.example.controller;
import static org.springframework.test.web.ModelAndViewAssert.*;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.junit.Test;
import org.springframework.web.servlet.ModelAndView;
import com.example.model.Book;
public class BookControllerTest {
@Test
public void test() {
BookController bookController = new BookController();
ModelAndView mav = bookController.getLatestTitles("2016");
assertViewName(mav, "Latest Titles");
assertModelAttributeAvailable(mav, "latest");
List<Book> expectedList = Arrays.asList(
new Book("0002", LocalDate.of(2016, 11, 1)),
new Book("0001", LocalDate.of(2016, 6, 1)),
new Book("0003", LocalDate.of(2016, 12, 12)));
assertAndReturnModelAttributeOfType(mav, "latest",
expectedList.getClass());
Comparator<Book> pubDateComparator =
(a, b) -> a.getPubDate().compareTo(b.getPubDate());
assertSortAndCompareListModelAttribute(mav, "latest",
expectedList, pubDateComparator);
}
}绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论