如何使用 JUnit 测试 servlet

我已经使用 JavaServlet 创建了一个 Web 系统,现在想进行 JUnit 测试。我的 dataManager只是一段提交到数据库的基本代码。如何用 JUnit 测试 Servlet?

我的代码示例,允许用户注册/注册,这是从我的主页通过 AJAX 提交:

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{


// Get parameters
String userName = request.getParameter("username");
String password = request.getParameter("password");
String name = request.getParameter("name");


try {


// Load the database driver
Class.forName("com.mysql.jdbc.Driver");


//pass reg details to datamanager
dataManager = new DataManager();
//store result as string
String result = dataManager.register(userName, password, name);


//set response to html + no cache
response.setContentType("text/html");
response.setHeader("Cache-Control", "no-cache");
//send response with register result
response.getWriter().write(result);


} catch(Exception e){
System.out.println("Exception is :" + e);
}
}
198363 次浏览

首先,您可能需要对此进行一些重构,以便不在 doPost 代码中创建 DataManager。.你应该试试依赖注入。(请参阅 吉斯视频以获得 DI 的精彩介绍。).如果您被告知要开始对所有内容进行单元测试,那么 DI 就是必不可少的。

一旦注入了依赖项,就可以隔离地测试类。

要实际测试 servlet,还有其他一些较老的线程讨论过这个问题. . 请尝试 给你给你

使用 进行基于 Web 的单元测试。有一个名为 硒化物的 Firefox 插件,它可以记录网页上的操作并导出到 JUnit 测试用例,JUnit 测试用例使用 硒 RC来运行测试服务器。

编辑 : 仙人掌现在是一个死亡的项目: < a href = “ http://attic.apache.org/Projects/jaketta-Cactus.html”rel = “ nofollow norefrer”> http://attic.apache.org/projects/jakarta-Cactus.html


你可能想看看仙人掌。

Http://jakarta.apache.org/cactus/

工程项目简介

Cactus 是用于单元测试服务器端 java 代码(Servlet、 EJB、 Tag Libs、 Filters、 ...)的简单测试框架。

Cactus 的目的是降低为服务器端代码编写测试的成本。它使用 JUnit 并扩展它。

Cactus 实现了容器内策略,这意味着测试在容器内执行。

首先,在实际的应用程序中,您永远不会在 servlet 中获得数据库连接信息; 您将在应用程序服务器中配置它。

但是,有一些方法可以在不运行容器的情况下测试 Servlet。一种是使用模拟对象。Spring 为 HttpServletRequest、 HttpServletResponse、 HttpServletSession 等提供了一组非常有用的模拟:

Http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/mock/web/package-summary.html

使用这些模拟,您可以测试

如果用户名不在请求中会发生什么?

如果用户名在请求中会发生什么?

等等

你可以这样做:

import static org.junit.Assert.assertEquals;


import java.io.IOException;


import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;


public class MyServletTest {
private MyServlet servlet;
private MockHttpServletRequest request;
private MockHttpServletResponse response;


@Before
public void setUp() {
servlet = new MyServlet();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}


@Test
public void correctUsernameInRequest() throws ServletException, IOException {
request.addParameter("username", "scott");
request.addParameter("password", "tiger");


servlet.doPost(request, response);


assertEquals("text/html", response.getContentType());


// ... etc
}
}

您可以使用 莫基托来做到这一点,使 mock 返回正确的参数,验证它们确实被调用(可选地指定次数) ,写入“结果”并验证它是正确的。

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.*;
import javax.servlet.http.*;
import org.apache.commons.io.FileUtils;
import org.junit.Test;


public class TestMyServlet extends Mockito{


@Test
public void testServlet() throws Exception {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);


when(request.getParameter("username")).thenReturn("me");
when(request.getParameter("password")).thenReturn("secret");


StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
when(response.getWriter()).thenReturn(writer);


new MyServlet().doPost(request, response);


verify(request, atLeast(1)).getParameter("username"); // only if you want to verify username was called...
writer.flush(); // it may not have been flushed yet...
assertTrue(stringWriter.toString().contains("My expected string"));
}
}

我发现 Selenium 测试对于集成或功能(端到端)测试更有用。我正在尝试使用 Org.springframework.moc.web,但是我还没有走得很远。我附加一个样本控制器与 JMock测试套件。

首先,财务总监:

package com.company.admin.web;


import javax.validation.Valid;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;


import com.company.admin.domain.PaymentDetail;
import com.company.admin.service.PaymentSearchService;
import com.company.admin.service.UserRequestAuditTrail;
import com.company.admin.web.form.SearchCriteria;


/**
* Controls the interactions regarding to the refunds.
*
* @author slgelma
*
*/
@Controller
@SessionAttributes({"user", "authorization"})
public class SearchTransactionController {


public static final String SEARCH_TRANSACTION_PAGE = "searchtransaction";


private PaymentSearchService searchService;
//private Validator searchCriteriaValidator;
private UserRequestAuditTrail notifications;


@Autowired
public void setSearchService(PaymentSearchService searchService) {
this.searchService = searchService;
}


@Autowired
public void setNotifications(UserRequestAuditTrail notifications) {
this.notifications = notifications;
}


@RequestMapping(value="/" + SEARCH_TRANSACTION_PAGE)
public String setUpTransactionSearch(Model model) {
SearchCriteria searchCriteria = new SearchCriteria();
model.addAttribute("searchCriteria", searchCriteria);
notifications.transferTo(SEARCH_TRANSACTION_PAGE);
return SEARCH_TRANSACTION_PAGE;
}


@RequestMapping(value="/" + SEARCH_TRANSACTION_PAGE, method=RequestMethod.POST, params="cancel")
public String cancelSearch() {
notifications.redirectTo(HomeController.HOME_PAGE);
return "redirect:/" + HomeController.HOME_PAGE;
}


@RequestMapping(value="/" + SEARCH_TRANSACTION_PAGE, method=RequestMethod.POST, params="execute")
public String executeSearch(
@ModelAttribute("searchCriteria") @Valid SearchCriteria searchCriteria,
BindingResult result, Model model,
SessionStatus status) {
//searchCriteriaValidator.validate(criteria, result);
if (result.hasErrors()) {
notifications.transferTo(SEARCH_TRANSACTION_PAGE);
return SEARCH_TRANSACTION_PAGE;
} else {
PaymentDetail payment =
searchService.getAuthorizationFor(searchCriteria.geteWiseTransactionId());
if (payment == null) {
ObjectError error = new ObjectError(
"eWiseTransactionId", "Transaction not found");
result.addError(error);
model.addAttribute("searchCriteria", searchCriteria);
notifications.transferTo(SEARCH_TRANSACTION_PAGE);
return SEARCH_TRANSACTION_PAGE;
} else {
model.addAttribute("authorization", payment);
notifications.redirectTo(PaymentDetailController.PAYMENT_DETAIL_PAGE);
return "redirect:/" + PaymentDetailController.PAYMENT_DETAIL_PAGE;
}
}
}


}

接下来,测试:

    package test.unit.com.company.admin.web;


import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;


import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.support.SessionStatus;


import com.company.admin.domain.PaymentDetail;
import com.company.admin.service.PaymentSearchService;
import com.company.admin.service.UserRequestAuditTrail;
import com.company.admin.web.HomeController;
import com.company.admin.web.PaymentDetailController;
import com.company.admin.web.SearchTransactionController;
import com.company.admin.web.form.SearchCriteria;


/**
* Tests the behavior of the SearchTransactionController.
* @author slgelma
*
*/
@RunWith(JMock.class)
public class SearchTransactionControllerTest {


private final Mockery context = new JUnit4Mockery();
private final SearchTransactionController controller = new SearchTransactionController();
private final PaymentSearchService searchService = context.mock(PaymentSearchService.class);
private final UserRequestAuditTrail notifications = context.mock(UserRequestAuditTrail.class);
private final Model model = context.mock(Model.class);




/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
controller.setSearchService(searchService);
controller.setNotifications(notifications);
}


@Test
public void setUpTheSearchForm() {


final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;


context.checking(new Expectations() \{\{
oneOf(model).addAttribute(
with(any(String.class)), with(any(Object.class)));
oneOf(notifications).transferTo(with(any(String.class)));
}});


String nextPage = controller.setUpTransactionSearch(model);
assertThat("Controller is not requesting the correct form",
target, equalTo(nextPage));
}


@Test
public void cancelSearchTest() {


final String target = HomeController.HOME_PAGE;


context.checking(new Expectations()\{\{
never(model).addAttribute(with(any(String.class)), with(any(Object.class)));
oneOf(notifications).redirectTo(with(any(String.class)));
}});


String nextPage = controller.cancelSearch();
assertThat("Controller is not requesting the correct form",
nextPage, containsString(target));
}


@Test
public void executeSearchWithNullTransaction() {


final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;


final SearchCriteria searchCriteria = new SearchCriteria();
searchCriteria.seteWiseTransactionId(null);


final BindingResult result = context.mock(BindingResult.class);
final SessionStatus status = context.mock(SessionStatus.class);


context.checking(new Expectations() \{\{
allowing(result).hasErrors(); will(returnValue(true));
never(model).addAttribute(with(any(String.class)), with(any(Object.class)));
never(searchService).getAuthorizationFor(searchCriteria.geteWiseTransactionId());
oneOf(notifications).transferTo(with(any(String.class)));
}});


String nextPage = controller.executeSearch(searchCriteria, result, model, status);
assertThat("Controller is not requesting the correct form",
target, equalTo(nextPage));
}


@Test
public void executeSearchWithEmptyTransaction() {


final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;


final SearchCriteria searchCriteria = new SearchCriteria();
searchCriteria.seteWiseTransactionId("");


final BindingResult result = context.mock(BindingResult.class);
final SessionStatus status = context.mock(SessionStatus.class);


context.checking(new Expectations() \{\{
allowing(result).hasErrors(); will(returnValue(true));
never(model).addAttribute(with(any(String.class)), with(any(Object.class)));
never(searchService).getAuthorizationFor(searchCriteria.geteWiseTransactionId());
oneOf(notifications).transferTo(with(any(String.class)));
}});


String nextPage = controller.executeSearch(searchCriteria, result, model, status);
assertThat("Controller is not requesting the correct form",
target, equalTo(nextPage));
}


@Test
public void executeSearchWithTransactionNotFound() {


final String target = SearchTransactionController.SEARCH_TRANSACTION_PAGE;
final String badTransactionId = "badboy";
final PaymentDetail transactionNotFound = null;


final SearchCriteria searchCriteria = new SearchCriteria();
searchCriteria.seteWiseTransactionId(badTransactionId);


final BindingResult result = context.mock(BindingResult.class);
final SessionStatus status = context.mock(SessionStatus.class);


context.checking(new Expectations() \{\{
allowing(result).hasErrors(); will(returnValue(false));
atLeast(1).of(model).addAttribute(with(any(String.class)), with(any(Object.class)));
oneOf(searchService).getAuthorizationFor(with(any(String.class)));
will(returnValue(transactionNotFound));
oneOf(result).addError(with(any(ObjectError.class)));
oneOf(notifications).transferTo(with(any(String.class)));
}});


String nextPage = controller.executeSearch(searchCriteria, result, model, status);
assertThat("Controller is not requesting the correct form",
target, equalTo(nextPage));
}


@Test
public void executeSearchWithTransactionFound() {


final String target = PaymentDetailController.PAYMENT_DETAIL_PAGE;
final String goodTransactionId = "100000010";
final PaymentDetail transactionFound = context.mock(PaymentDetail.class);


final SearchCriteria searchCriteria = new SearchCriteria();
searchCriteria.seteWiseTransactionId(goodTransactionId);


final BindingResult result = context.mock(BindingResult.class);
final SessionStatus status = context.mock(SessionStatus.class);


context.checking(new Expectations() \{\{
allowing(result).hasErrors(); will(returnValue(false));
atLeast(1).of(model).addAttribute(with(any(String.class)), with(any(Object.class)));
oneOf(searchService).getAuthorizationFor(with(any(String.class)));
will(returnValue(transactionFound));
oneOf(notifications).redirectTo(with(any(String.class)));
}});


String nextPage = controller.executeSearch(searchCriteria, result, model, status);
assertThat("Controller is not requesting the correct form",
nextPage, containsString(target));
}


}

希望这个能帮上忙。

 public class WishServletTest {
WishServlet wishServlet;
HttpServletRequest mockhttpServletRequest;
HttpServletResponse mockhttpServletResponse;


@Before
public void setUp(){
wishServlet=new WishServlet();
mockhttpServletRequest=createNiceMock(HttpServletRequest.class);
mockhttpServletResponse=createNiceMock(HttpServletResponse.class);
}


@Test
public void testService()throws Exception{
File file= new File("Sample.txt");
File.createTempFile("ashok","txt");
expect(mockhttpServletRequest.getParameter("username")).andReturn("ashok");
expect(mockhttpServletResponse.getWriter()).andReturn(new PrintWriter(file));
replay(mockhttpServletRequest);
replay(mockhttpServletResponse);
wishServlet.doGet(mockhttpServletRequest, mockhttpServletResponse);
FileReader fileReader=new FileReader(file);
int count = 0;
String str = "";
while ( (count=fileReader.read())!=-1){
str=str+(char)count;
}


Assert.assertTrue(str.trim().equals("Helloashok"));
verify(mockhttpServletRequest);
verify(mockhttpServletResponse);


}


}

更新于2018年2月: OpenBrace Limited has closed down,其 ObMimic 产品不再受支持。

这里有另一个替代方案,使用 OpenBrace 的 ServletAPI 的 奥米米克库 test-double (公开: 我是它的开发人员)。

package com.openbrace.experiments.examplecode.stackoverflow5434419;


import static org.junit.Assert.*;
import com.openbrace.experiments.examplecode.stackoverflow5434419.YourServlet;
import com.openbrace.obmimic.mimic.servlet.ServletConfigMimic;
import com.openbrace.obmimic.mimic.servlet.http.HttpServletRequestMimic;
import com.openbrace.obmimic.mimic.servlet.http.HttpServletResponseMimic;
import com.openbrace.obmimic.substate.servlet.RequestParameters;
import org.junit.Before;
import org.junit.Test;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/**
* Example tests for {@link YourServlet#doPost(HttpServletRequest,
* HttpServletResponse)}.
*
* @author Mike Kaufman, OpenBrace Limited
*/
public class YourServletTest {


/** The servlet to be tested by this instance's test. */
private YourServlet servlet;


/** The "mimic" request to be used in this instance's test. */
private HttpServletRequestMimic request;


/** The "mimic" response to be used in this instance's test. */
private HttpServletResponseMimic response;


/**
* Create an initialized servlet and a request and response for this
* instance's test.
*
* @throws ServletException if the servlet's init method throws such an
*     exception.
*/
@Before
public void setUp() throws ServletException {
/*
* Note that for the simple servlet and tests involved:
* - We don't need anything particular in the servlet's ServletConfig.
* - The ServletContext isn't relevant, so ObMimic can be left to use
*   its default ServletContext for everything.
*/
servlet = new YourServlet();
servlet.init(new ServletConfigMimic());
request = new HttpServletRequestMimic();
response = new HttpServletResponseMimic();
}


/**
* Test the doPost method with example argument values.
*
* @throws ServletException if the servlet throws such an exception.
* @throws IOException if the servlet throws such an exception.
*/
@Test
public void testYourServletDoPostWithExampleArguments()
throws ServletException, IOException {


// Configure the request. In this case, all we need are the three
// request parameters.
RequestParameters parameters
= request.getMimicState().getRequestParameters();
parameters.set("username", "mike");
parameters.set("password", "xyz#zyx");
parameters.set("name", "Mike");


// Run the "doPost".
servlet.doPost(request, response);


// Check the response's Content-Type, Cache-Control header and
// body content.
assertEquals("text/html; charset=ISO-8859-1",
response.getMimicState().getContentType());
assertArrayEquals(new String[] { "no-cache" },
response.getMimicState().getHeaders().getValues("Cache-Control"));
assertEquals("...expected result from dataManager.register...",
response.getMimicState().getBodyContentAsString());


}


}

备注:

  • Each "mimic" has a "mimicState" object for its logical state. This provides a clear distinction between the Servlet API methods and the configuration and inspection of the mimic's internal state.

  • 您可能会惊讶于 Content-Type 的检查包括“ charset = ISO-8859-1”。然而,对于给定的“ doPost”代码,这是根据 Servlet API Javadoc,HttpServletResponse 自己的 getContentType 方法,以及在 Glassfish 3上生成的实际 Content-Type 头。如果使用普通的模拟对象和您自己对 API 行为的预期,您可能不会意识到这一点。在这种情况下,这可能无关紧要,但是在更复杂的情况下,这是一种意想不到的 API 行为,可能会让模拟变得有点可笑!

  • 我已经使用 response.getMimicState().getContentType()作为检查 Content-Type 的最简单方法,并举例说明了上面的观点,但是如果需要的话,您确实可以单独检查“ text/html”(使用 response.getMimicState().getContentTypeMimeType())。检查 Content-Type 标头的方式与检查 Cache-Control 标头的方式相同。

  • For this example the response content is checked as character data (with this using the Writer's encoding). We could also check that the response's Writer was used rather than its OutputStream (using response.getMimicState().isWritingCharacterContent()), but I've taken it that we're only concerned with the resulting output, and don't care what API calls produced it (though that could be checked too...). It's also possible to retrieve the response's body content as bytes, examine the detailed state of the Writer/OutputStream etc.

有完整的细节 ObMimic 和免费下载在 OpenBrace网站。或者你可以联系我,如果你有任何问题(联系方式在网站上)。

另一种方法是创建一个嵌入式服务器来“托管”你的 servlet,允许你使用库来编写对它的调用,这些库意在对实际的服务器进行调用(这种方法的有用性在某种程度上取决于你对服务器进行“合法的”编程调用的容易程度——我正在测试一个 JMS (Java Messaging Service)访问点,客户端比比皆是)。

你可以走几条不同的路线——通常是公猫和码头。

警告: 选择要嵌入的服务器时需要注意的是您正在使用的 servlet-api 版本(提供类如 HttpServletRequest 的库)。如果您使用的是2.5,我发现 Jetty 6.x 可以很好地工作(这是我将在下面给出的示例)。如果您使用的是 servlet-api 3.0,那么 tomcat-7嵌入式内容似乎是一个不错的选择,但是我不得不放弃使用它的尝试,因为我正在测试的应用程序使用的是 servlet-api 2.5。在尝试配置或启动服务器时,混合使用这两种方法将导致 NoSuchMethod 和其他此类异常。

您可以像下面这样设置这样一个服务器(Jetty 6.1.26,servlet-api 2.5) :

public void startServer(int port, Servlet yourServletInstance){
Server server = new Server(port);
Context root = new Context(server, "/", Context.SESSIONS);


root.addServlet(new ServletHolder(yourServletInstance), "/servlet/context/path");


//If you need the servlet context for anything, such as spring wiring, you coudl get it like this
//ServletContext servletContext = root.getServletContext();


server.start();
}