J'ai créé un système Web à l'aide de Java Servlets et souhaite maintenant faire des tests sur JUnit. Mon dataManager
n'est qu'un élément de code de base qui le soumet à la base de données. Comment tester un servlet avec JUnit?
Mon exemple de code qui permet à un utilisateur de s'inscrire/de s'inscrire et qui est soumis à partir de ma page principale via 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);
}
}
Vous pouvez le faire en utilisant Mockito pour que la maquette retourne les paramètres corrects, vérifie qu'ils ont bien été appelés (spécifiez éventuellement le nombre de fois), écrivez le 'résultat' et vérifiez qu'il est correct.
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"));
}
}
Tout d'abord, dans une application réelle, vous n'obtiendrez jamais d'informations de connexion à la base de données dans un servlet; vous le configureriez sur votre serveur d'applications.
Il existe toutefois des moyens de tester les servlets sans faire exécuter un conteneur. L'une consiste à utiliser des objets fictifs. Spring fournit un ensemble de simulacres très utiles pour des choses comme HttpServletRequest, HttpServletResponse, HttpServletSession, etc.:
En utilisant ces simulacres, vous pouvez tester des choses comme
Que se passe-t-il si le nom d'utilisateur n'est pas dans la demande?
Que se passe-t-il si le nom d'utilisateur est dans la demande?
etc
Vous pouvez ensuite faire des choses comme:
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
}
}
Mis à jour en février 2018: OpenBrace Limited a fermé ses portes , et son produit ObMimic n'est plus pris en charge.
Voici une autre alternative, utilisant la bibliothèque ObMimic de l'API Servlet test-doubles (divulgation: je suis son développeur).
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());
}
}
Remarques:
Chaque "imitation" a un objet "mimicState" pour son état logique. Ceci établit une distinction claire entre les méthodes de l'API Servlet et la configuration et l'inspection de l'état interne du mimic.
Vous pourriez être surpris que la vérification de Content-Type inclut "charset = ISO-8859-1". Toutefois, pour le code "doPost" donné, il s'agit du javadoc de l'API Servlet, de la méthode getContentType de HttpServletResponse et de l'en-tête Content-Type généré, par exemple. Glassfish 3. Vous ne le réaliserez peut-être pas si vous utilisez des objets fantaisie normaux et vos propres attentes quant au comportement de l'API. Dans ce cas, cela n'a probablement pas d'importance, mais dans des cas plus complexes, c'est le genre de comportement imprévu de l'API qui peut sembler un peu ridicule!
J'ai utilisé response.getMimicState().getContentType()
comme moyen le plus simple de vérifier Content-Type et d'illustrer le point ci-dessus, mais vous pouvez en effet vérifier le "texte/html" lui-même si vous le souhaitez (en utilisant response.getMimicState().getContentTypeMimeType()
). Vérifier l'en-tête Content-Type de la même manière que pour l'en-tête Cache-Control fonctionne également.
Pour cet exemple, le contenu de la réponse est vérifié en tant que données de caractères (le codage de Writer étant utilisé). Nous pourrions également vérifier que l'enregistreur de la réponse a été utilisé plutôt que son OutputStream (en utilisant response.getMimicState().isWritingCharacterContent()
), mais je suppose que nous ne nous intéressons qu'au résultat obtenu, et ne nous soucions pas des appels d'API l'a produite (bien que cela puisse être vérifié aussi ...). Il est également possible de récupérer le contenu du corps de la réponse sous forme d'octets, d'examiner l'état détaillé de Writer/OutputStream, etc.
ObMimic contient des informations complètes et peut être téléchargé gratuitement sur le site Web OpenBrace . Ou vous pouvez me contacter si vous avez des questions (les coordonnées sont sur le site).
Je trouve les tests Selenium plus utiles avec des tests d’intégration ou des tests fonctionnels (de bout en bout). Je travaille avec essayer d'utiliser org.springframework.mock.web, mais je ne suis pas très loin. Je joins un exemple de contrôleur avec une suite de tests jMock.
Tout d'abord, le contrôleur:
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;
}
}
}
}
Ensuite, le test:
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));
}
}
J'espère que cela pourrait aider.
[~ # ~] éditer [~ # ~] : Cactus est maintenant un projet mort: http://attic.Apache.org /projects/jakarta-cactus.html
Vous voudrez peut-être regarder cactus.
http://jakarta.Apache.org/cactus/
Description du projet
Cactus est un framework de test simple pour les tests unitaires côté serveur Java (servlets, EJB, bibliothèques de balises, filtres, ...).
Cactus a pour objectif de réduire les coûts d’écriture des tests pour le code côté serveur. Il utilise JUnit et l'étend.
Cactus implémente une stratégie dans le conteneur, ce qui signifie que les tests sont exécutés à l'intérieur du conteneur.
Une autre approche consisterait à créer un serveur intégré pour "héberger" votre servlet, ce qui vous permettrait d'écrire des appels avec des bibliothèques destinées à appeler des serveurs réels (l'utilité de cette approche dépend dans une certaine mesure de la facilité avec laquelle vous pouvez effectuer une programmation "légitime". appels vers le serveur - je testais un point d’accès JMS (Java Messaging Service), pour lequel les clients abondent).
Vous pouvez emprunter deux itinéraires différents - les deux plus courants sont Tomcat et Jetty.
Avertissement: lors de la sélection du serveur à intégrer, il convient de garder à l'esprit la version de servlet-api que vous utilisez (la bibliothèque qui fournit des classes telles que HttpServletRequest). Si vous utilisez la version 2.5, Jetty 6.x fonctionne bien (c'est l'exemple que je vais donner ci-dessous). Si vous utilisez servlet-api 3.0, le composant intégré Tomcat-7 semble être une bonne option, mais je devais abandonner ma tentative d'utilisation, car l'application que je testais utilisait servlet-api 2.5. Si vous essayez de mélanger les deux, vous obtiendrez NoSuchMethod et d’autres exceptions similaires lors de la tentative de configuration ou de démarrage du serveur.
Vous pouvez configurer un tel serveur comme ceci (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();
}
Utilisez Selenium pour les tests unitaires basés sur le Web. Il existe un plugin Firefox appelé Selenium IDE qui peut enregistrer des actions sur la page Web et exporter vers des tests JUnit qui utilise Selenium RC pour exécuter le serveur de test.
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);
}
}
D'abord, vous devriez probablement refactoriser cela un peu pour que le DataManager ne soit pas créé dans le code doPost .. vous devriez essayer Dependency Injection pour obtenir une instance. (Voir la vidéo Guice pour une belle introduction à DI.). Si on vous dit de commencer à tester tout, alors le DI est un must.
Une fois vos dépendances injectées, vous pouvez tester votre classe de manière isolée.
Pour tester réellement le servlet, il existe d'autres threads plus anciens qui en ont discuté. Essayez ici et ici .