如何 Mock(模拟)HttpServletRequest
1、概览
本文将带你了解几种模拟 HttpServletRequest
对象的方法。
首先,从 Spring Test 中的 MockHttpServletRequest
开始,这是一个功能齐全的模拟类型。然后,了解如何使用 Mockito 和 JMockit 这两个流行的模拟库进行测试。最后,了解如何使用匿名子类进行测试。
2、测试 HttpServletRequest
当我们要模拟客户端 request
信息(如 HttpServletRequest
)来测试 Servlet
可能会有点麻烦,该接口定义了各种方法,需要进行实现。
要测试的目标 UserServlet
类,如下:
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
String firstName = request.getParameter("firstName");
String lastName = request.getParameter("lastName");
response.getWriter().append("Full Name: " + firstName + " " + lastName);
}
}
要对 doGet()
方法进行单元测试,需要模拟 request
和 response
参数,以模拟实际的运行时行为。
3、使用 Spring 的 MockHttpServletRequest
Spring-Test 提供了一个功能齐全的类 MockHttpServletRequest
,它实现了 HttpServletRequest
接口。
虽然该库主要用于测试 Spring 应用,但我们可以使用它的 MockHttpServletRequest
类,而无需实现任何 Spring 特有的功能。换句话说,即使应用不使用 Spring,我们也可以使用该依赖来模拟 HttpServletRequest
对象。
在 pom.xml
中这个添加 依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.20</version>
<scope>test</scope>
</dependency>
现在,让我们看看如何使用该类来测试 UserServlet
:
@Test
void givenHttpServletRequest_whenUsingMockHttpServletRequest_thenReturnsParameterValues() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("firstName", "Spring");
request.setParameter("lastName", "Test");
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.doGet(request, response);
assertThat(response.getContentAsString()).isEqualTo("Full Name: Spring Test");
}
如上,你可以注意到没有涉及实际的模拟。我们使用了功能完备的 request
和 response
对象,只用了几行代码就测试了目标类。因此,测试代码简洁、可读性强且易于维护。
4、使用模拟(Mock)框架
模拟框架提供了一个干净简单的API,用于测试模拟对象,这些对象模拟了原始对象的运行时行为。
它们的一些优点是表达能力强,可以直接模拟 static
和 private
方法。此外,与自定义实现相比,可以避免大部分用于模拟的样板代码,而将重点放在测试上。
4.1、Mockito
Mockito 是一个流行的开源测试自动化框架,内部使用 Java Reflection(反射) API 创建 mock 对象。
在 pom.xml
中添加 依赖:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.4.0</version>
<scope>test</scope>
</dependency>
接下来,让我们看看如何模拟 HttpServletRequest
对象的 getParameter()
方法:
@Test
void givenHttpServletRequest_whenMockedWithMockito_thenReturnsParameterValues() throws IOException {
// mock HttpServletRequest & HttpServletResponse
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
// mock the returned value of request.getParameterMap()
when(request.getParameter("firstName")).thenReturn("Mockito");
when(request.getParameter("lastName")).thenReturn("Test");
when(response.getWriter()).thenReturn(new PrintWriter(writer));
servlet.doGet(request, response);
assertThat(writer.toString()).isEqualTo("Full Name: Mockito Test");
}
4.2、JMockit
JMockit 是一个 Mock API,它提供了有用的记录和验证语法(可以将其用于 JUnit 和 TestNG)。它是一个容器外集成测试库,适用于 Java EE 和基于 Spring 的应用。
让我们看看如何使用 JMockit 来模拟 HttpServletRequest
。
添加 jmockit
依赖:
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>1.49</version>
<scope>test</scope>
</dependency>
接下来,继续在测试类中实现模拟:
@Mocked
HttpServletRequest mockRequest;
@Mocked
HttpServletResponse mockResponse;
@Test
void givenHttpServletRequest_whenMockedWithJMockit_thenReturnsParameterValues() throws IOException {
new Expectations() {{
mockRequest.getParameter("firstName"); result = "JMockit";
mockRequest.getParameter("lastName"); result = "Test";
mockResponse.getWriter(); result = new PrintWriter(writer);
}};
servlet.doGet(mockRequest, mockResponse);
assertThat(writer.toString()).isEqualTo("Full Name: JMockit Test");
}
如上所示,只需几行设置,就成功地用模拟的 HttpServletRequest
对象测试了目标类。
Mock 框架可以节省大量的工作量,并且能够更快地编写单元测试。但是,要使用模拟对象,需要了解 Mock API,并且通常需要一个单独的框架。
5、使用匿名子类
在某些项目中,可能依赖存在限制或希望直接控制自己的测试类实现。特别是在 Servlet 代码库较大的情况下,自定义实现的可重用性非常重要。在这些情况下,匿名类非常方便。
匿名类是没有名称的内部类。它们很容易实现,并且可以直接控制实际对象。如果我们不想为测试添加额外的依赖,可以考虑使用这种方法。
现在,创建一个实现 HttpServletRequest
接口的匿名子类,并用它来测试 doGet()
方法:
public static HttpServletRequest getRequest(Map<String, String[]> params) {
return new HttpServletRequest() {
public Map<String, String[]> getParameterMap() {
return params;
}
public String getParameter(String name) {
String[] values = params.get(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}
// 其他的实现方法
}
};
接下来,把这个 request
传递给被测试的类:
@Test
void givenHttpServletRequest_whenUsingAnonymousClass_thenReturnsParameterValues() throws IOException {
final Map<String, String[]> params = new HashMap<>();
params.put("firstName", new String[] { "Anonymous Class" });
params.put("lastName", new String[] { "Test" });
servlet.doGet(getRequest(params), getResponse(writer));
assertThat(writer.toString()).isEqualTo("Full Name: Anonymous Class Test");
}
这种解决方案的缺点是需要创建一个匿名类,并对所有抽象方法进行虚拟实现。此外,嵌套对象(如 HttpSession
)有可能需要特定的实现。
6、总结
本文介绍了在编写 servlet 单元测试时模拟 HttpServletRequest
对象的几种方式。除了使用模拟框架外,还可以使用 MockHttpServletRequest
类进行测试,这更简洁高效。
参考:https://www.baeldung.com/java-httpservletrequest-mock