概述

在这篇快速教程中,我们将探讨几种模拟HttpServletRequest对象的方法。

首先,我们从完全功能的mock类型开始——来自Spring测试库的MockHttpServletRequesthttps://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/mock/web/MockHttpServletRequest.html`_)。然后,我们将了解如何使用两个流行的mocking库——Mockito和JMockit进行测试。最后,我们将介绍如何通过匿名子类进行测试。

测试HttpServletRequest

当我们想要模拟客户端请求信息,如HttpServletRequesthttps://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/http/HttpServletRequest.html`_)时,测试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()方法,我们需要mockrequestresponse参数,以模拟实际运行时行为。

使用Spring的MockHttpServletRequest

Spring-Test库提供了实现HttpServletRequest接口的完整功能类MockHttpServletRequesthttps://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/mock/web/MockHttpServletRequest.html_)。尽管这个库主要针对测试Spring应用,但我们可以在不使用任何特定于Spring的功能的情况下使用它的MockHttpServletRequest类。换句话说,即使应用程序不使用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");
}

在这里,我们注意到并没有真正的模拟。我们使用了完整的请求和响应对象,仅用几行代码就测试了目标类。因此,测试代码既清晰又易于维护。

使用Mocking框架

另一种选择是,mocking框架提供了一种简洁的API来测试模仿原始对象运行时行为的mock对象

它们的优点包括表达性好,能够轻松地模拟静态和私有方法。此外,与自定义实现相比,我们可以避免大部分样板代码,专注于测试本身。

4.1 使用Mockito

Mockito是一个流行的开源自动化测试框架,它内部使用Java反射API创建mock对象。

首先,我们通过添加mockito-core的依赖pom.xml开始:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.4.0</version>
    <scope>test</scope>
</dependency>

接下来,让我们看看如何使用Mockito模拟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是一个提供了有用录制和验证语法的mocking 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>

然后,在测试类中进行mock实现:

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

如上所述,通过几行设置,我们就成功地使用mock HttpServletRequest对象测试了目标类。

因此,mocking框架可以大大减少工作量,使编写单元测试更快。然而,使用mock对象需要理解mock API,通常需要单独的框架。

使用匿名子类

有些项目可能有依赖约束或更喜欢对测试类实现的直接控制。特别是对于大型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];
        }

        // More methods to implement
    }
};

然后,将这个请求传递给要测试的类:

@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可能需要特定的实现

总结

在这篇文章中,我们讨论了在为Servlet编写单元测试时模拟HttpServletRequest对象的几种选项。除了使用mocking框架,我们还看到使用MockHttpServletRequest类进行测试比自定义实现更为干净和高效。

如往常一样,这些示例的代码可在GitHub上找到。