如何Mock HttpServletRequest

2023/05/09

1. 概述

在这个快速教程中,我们将研究几种mock HttpServletRequest对象的方法

首先,我们将从功能齐全的mock类型开始-来自Spring Test库的MockHttpServletRequest。然后,我们将了解如何使用两个流行的mock库-Mockito和JMockit进行测试。最后,我们将看到如何使用匿名子类进行测试。

2. 测试HttpServletRequest

当我们想要mock HttpServletRequest等客户端请求信息时,测试Servlet可能会很棘手。此外,此接口定义了各种方法,并且有不同的方法可用于mock这些方法。

让我们看一下我们要测试的目标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()方法进行单元测试,我们需要mock request和response参数以模拟实际的运行时行为。

3. 使用Spring的MockHttpServletRequest

Spring-Test库提供了一个功能齐全的类MockHttpServletRequest,它实现了HttpServletRequest接口。

虽然这个库主要用于测试Spring应用程序,但我们可以使用它的MockHttpServletRequest类而无需实现任何Spring特定的功能。换句话说,即使应用程序不使用Spring,我们仍然可以包含这个依赖项来mock 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");
}

在这里,我们可以注意到没有涉及实际的mock。我们使用了功能齐全的请求和响应对象,并仅用几行代码就测试了目标类。因此,测试代码是干净的、可读的和可维护的

4. 使用Mock框架

或者,mock框架提供了一个干净简单的API来测试mock对象,这些对象模仿原始对象的运行时行为

它们的一些优点是它们的可表达性和开箱即用的mock静态和私有方法的能力。此外,我们可以避免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>

接下来,让我们看看如何从HttpServletRequest对象mock 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,它提供了有用的记录和验证语法(我们可以将它用于JUnitTestNG)。它是一个容器外集成测试库,用于Java EE和基于Spring的应用程序。让我们看看如何使用JMockit mock 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() ;

    servlet.doGet(mockRequest, mockResponse);

    assertThat(writer.toString()).isEqualTo("Full Name: JMockit Test");
}

正如我们在上面看到的,只需几行设置,我们就成功地使用mock HttpServletRequest对象测试了目标类。

因此,mock框架可以节省我们大量的复杂工作,并使编写单元测试的速度更快。相反,要使用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];
        }

        // 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(TestUtil.getRequest(params), TestUtil.getResponse(writer));
	
	assertThat(writer.toString()).isEqualTo("Full Name: Anonymous Class Test");
}

该解决方案的缺点是需要为所有抽象方法创建一个具有虚拟实现的匿名类。此外,像HttpSession这样的嵌套对象可能需要特定的实现

6. 总结

在本文中,我们讨论了在为Servlet编写单元测试时mock HttpServletRequest对象的几个选项。除了使用mock框架之外,我们还看到使用MockHttpServletRequest类进行测试似乎比自定义实现更干净、更高效。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章