通常我使用“集成测试”来测试Servlet和JSP,而不是纯粹的单元测试。JUnit/TestNG有很多可用的附加组件,包括:
这是一个针对简单订单处理Servlet的JWebUnit测试,该Servlet处理来自表单“orderEntry.html”的输入。 它期望顾客ID、顾客名称和一个或多个订单项:
public class OrdersPageTest {
private static final String WEBSITE_URL = "http://localhost:8080/demo1";
@Before
public void start() {
webTester = new WebTester();
webTester.setTestingEngineKey(TestingEngineRegistry.TESTING_ENGINE_HTMLUNIT);
webTester.getTestContext().setBaseUrl(WEBSITE_URL);
}
@Test
public void sanity() throws Exception {
webTester.beginAt("/orderEntry.html");
webTester.assertTitleEquals("Order Entry Form");
}
@Test
public void idIsRequired() throws Exception {
webTester.beginAt("/orderEntry.html");
webTester.submit();
webTester.assertTextPresent("ID Missing!");
}
@Test
public void nameIsRequired() throws Exception {
webTester.beginAt("/orderEntry.html");
webTester.setTextField("id","AB12");
webTester.submit();
webTester.assertTextPresent("Name Missing!");
}
@Test
public void validOrderSucceeds() throws Exception {
webTester.beginAt("/orderEntry.html");
webTester.setTextField("id","AB12");
webTester.setTextField("name","Joe Bloggs");
//fill in order line one
webTester.setTextField("lineOneItemNumber", "AA");
webTester.setTextField("lineOneQuantity", "12");
webTester.setTextField("lineOneUnitPrice", "3.4");
//fill in order line two
webTester.setTextField("lineTwoItemNumber", "BB");
webTester.setTextField("lineTwoQuantity", "14");
webTester.setTextField("lineTwoUnitPrice", "5.6");
webTester.submit();
webTester.assertTextPresent("Total: 119.20");
}
private WebTester webTester;
}
尝试使用HttpUnit,虽然您可能最终会编写更多针对模块的“集成测试”自动化测试,而不是单个类的“单元测试”。
我看了已发布的答案,想发表一个更完整的解决方案,该方案实际演示了如何使用嵌入式GlassFish及其Apache Maven插件进行测试。
我在我的博客上写了完整的过程Using GlassFish 3.1.1 Embedded with JUnit 4.x and HtmlUnit 2.x,并将完整项目放在Bitbucket上供下载:image-servlet
我在看到这个问题之前正在阅读有关JSP / JSF标记图像servlet的另一篇文章。因此,我将我从其他文章中使用的解决方案与本文的完整单元测试版本结合起来。
Apache Maven具有定义良好的生命周期,包括test
。我将使用它以及另一个称为integration-test
的生命周期来实现我的解决方案。
integration-test
添加为surefire-plugin执行的一部分。integration-test
生命周期期间执行。将此插件作为<build>
的一部分添加。
<plugin>
<groupId>org.glassfish</groupId>
<artifactId>maven-embedded-glassfish-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<!-- This sets the path to use the war file we have built in the target directory -->
<app>target/${project.build.finalName}</app>
<port>8080</port>
<!-- This sets the context root, e.g. http://localhost:8080/test/ -->
<contextRoot>test</contextRoot>
<!-- This deletes the temporary files during GlassFish shutdown. -->
<autoDelete>true</autoDelete>
</configuration>
<executions>
<execution>
<id>start</id>
<!-- We implement the integration testing by setting up our GlassFish instance to start and deploy our application. -->
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
<goal>deploy</goal>
</goals>
</execution>
<execution>
<id>stop</id>
<!-- After integration testing we undeploy the application and shutdown GlassFish gracefully. -->
<phase>post-integration-test</phase>
<goals>
<goal>undeploy</goal>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
将插件添加/修改为<build>
的一部分。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<!-- We are skipping the default test lifecycle and will test later during integration-test -->
<configuration>
<skip>true</skip>
</configuration>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<!-- During the integration test we will execute surefire:test -->
<goal>test</goal>
</goals>
<configuration>
<!-- This enables the tests which were disabled previously. -->
<skip>false</skip>
</configuration>
</execution>
</executions>
</plugin>
像下面的示例一样添加集成测试。
@Test
public void badRequest() throws IOException {
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
webClient.getOptions().setPrintContentOnFailingStatusCode(false);
final HtmlPage page = webClient.getPage("http://localhost:8080/test/images/");
final WebResponse response = page.getWebResponse();
assertEquals(400, response.getStatusCode());
assertEquals("An image name is required.", response.getStatusMessage());
webClient.getOptions().setThrowExceptionOnFailingStatusCode(true);
webClient.getOptions().setPrintContentOnFailingStatusCode(true);
webClient.closeAllWindows();
}
我在我的博客上写了完整的过程,您可以访问使用 GlassFish 3.1.1 嵌入 JUnit 4.x 和 HtmlUnit 2.x 进行测试。并将完整项目放在 Bitbucket 的image-servlet处供下载。myServlet.doGet(new HttpServletRequestWrapper() {
public HttpSession getSession() {
return mockSession;
}
...
}
HttpServletRequestWrapper 是一个方便的 Java 类。建议您在单元测试中创建一个实用方法来创建模拟 HTTP 请求:
public void testSomething() {
myServlet.doGet(createMockRequest(), createMockResponse());
}
protected HttpServletRequest createMockRequest() {
HttpServletRequest request = new HttpServletRequestWrapper() {
//overrided methods
}
}
最好将模拟创建方法放在一个基本的Servlet超类中,并使所有的Servlet单元测试都继承它。
这是一个关于servlet doPost()方法的JUnit测试实现,它只依赖Mockito库来模拟HttpRequest
、HttpResponse
、HttpSession
、ServletResponse
和RequestDispatcher
的实例。请将参数键和JavaBean实例替换为与从中调用doPost()的关联JSP文件中引用的值相对应的值。
Mockito Maven依赖项:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
</dependency>
JUnit测试:
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
/**
* Unit tests for the {@code StockSearchServlet} class.
* @author Bob Basmaji
*/
public class StockSearchServletTest extends HttpServlet {
// private fields of this class
private static HttpServletRequest request;
private static HttpServletResponse response;
private static StockSearchServlet servlet;
private static final String SYMBOL_PARAMETER_KEY = "symbol";
private static final String STARTRANGE_PARAMETER_KEY = "startRange";
private static final String ENDRANGE_PARAMETER_KEY = "endRange";
private static final String INTERVAL_PARAMETER_KEY = "interval";
private static final String SERVICETYPE_PARAMETER_KEY = "serviceType";
/**
* Sets up the logic common to each test in this class
*/
@Before
public final void setUp() {
request = mock(HttpServletRequest.class);
response = mock(HttpServletResponse.class);
when(request.getParameter("symbol"))
.thenReturn("AAPL");
when(request.getParameter("startRange"))
.thenReturn("2016-04-23 00:00:00");
when(request.getParameter("endRange"))
.thenReturn("2016-07-23 00:00:00");
when(request.getParameter("interval"))
.thenReturn("DAY");
when(request.getParameter("serviceType"))
.thenReturn("WEB");
String symbol = request.getParameter(SYMBOL_PARAMETER_KEY);
String startRange = request.getParameter(STARTRANGE_PARAMETER_KEY);
String endRange = request.getParameter(ENDRANGE_PARAMETER_KEY);
String interval = request.getParameter(INTERVAL_PARAMETER_KEY);
String serviceType = request.getParameter(SERVICETYPE_PARAMETER_KEY);
HttpSession session = mock(HttpSession.class);
when(request.getSession()).thenReturn(session);
final ServletContext servletContext = mock(ServletContext.class);
RequestDispatcher dispatcher = mock(RequestDispatcher.class);
when(servletContext.getRequestDispatcher("/stocksearchResults.jsp")).thenReturn(dispatcher);
servlet = new StockSearchServlet() {
public ServletContext getServletContext() {
return servletContext; // return the mock
}
};
StockSearchBean search = new StockSearchBean(symbol, startRange, endRange, interval);
try {
switch (serviceType) {
case ("BASIC"):
search.processData(ServiceType.BASIC);
break;
case ("DATABASE"):
search.processData(ServiceType.DATABASE);
break;
case ("WEB"):
search.processData(ServiceType.WEB);
break;
default:
search.processData(ServiceType.WEB);
}
} catch (StockServiceException e) {
throw new RuntimeException(e.getMessage());
}
session.setAttribute("search", search);
}
/**
* Verifies that the doPost method throws an exception when passed null arguments
* @throws ServletException
* @throws IOException
*/
@Test(expected = NullPointerException.class)
public final void testDoPostPositive() throws ServletException, IOException {
servlet.doPost(null, null);
}
/**
* Verifies that the doPost method runs without exception
* @throws ServletException
* @throws IOException
*/
@Test
public final void testDoPostNegative() throws ServletException, IOException {
boolean throwsException = false;
try {
servlet.doPost(request, response);
} catch (Exception e) {
throwsException = true;
}
assertFalse("doPost throws an exception", throwsException);
}
}
verify(session).setAttribute("field", "value")
也可能是一个很好的断言。 - Mahdi2018年2月更新:OpenBrace Limited已关闭,其ObMimic产品不再受支持。
另一个解决方案是使用我的ObMimic库,该库专门设计用于servlet的单元测试。它提供了所有Servlet API类的完整的纯Java实现,并且您可以根据需要配置和检查这些类以进行测试。
您确实可以使用它直接从JUnit或TestNG测试中调用doGet/doPost方法,并测试任何内部方法,即使它们涉及ServletContext或使用会话参数(或任何其他Servlet API功能)。
这不需要外部或嵌入式容器,也不限制您进行更广泛的基于HTTP的“集成”测试,与通用目的的模拟不同,它具有完整的Servlet API行为“内置”,因此您的测试可以是“状态”为基础而不是“交互”为基础(例如,您的测试不必依赖于代码所做的Servlet API调用的精确顺序,也不必依赖于您对Servlet API如何响应每个调用的期望)。
在我的如何使用JUnit测试我的Servlet答案中有一个简单的示例。有关完整详细信息和免费下载,请访问ObMimic网站。
这个问题有一个解决方案,提出了使用Mockito 如何使用JUnit测试我的Servlet的建议。这将限制任务只进行简单的单元测试,而不需要设置任何类似服务器的环境。