117 votes

Comment tester mon servlet en utilisant JUnit

J'ai créé un système web en utilisant Java Servlets et je veux maintenant faire des tests JUnit. Mon dataManager est juste un morceau de code de base qui le soumet à la base de données. Comment testeriez-vous un Servlet avec JUnit?

Mon exemple de code qui permet à un utilisateur de s'inscrire, qui est soumis depuis 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);
    }  
}

173voto

aaronvargas Points 1881

Vous pouvez le faire en utilisant Mockito pour que le mock renvoie les bons paramètres, vérifiez s'ils ont effectivement é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"); // seulement si vous voulez vérifier que le nom d'utilisateur a été appelé...
        writer.flush(); // il peut ne pas avoir été vidé encore...
        assertTrue(stringWriter.toString().contains("Ma chaîne attendue"));
    }
}

1 votes

De cette façon, comment assurez-vous que "Cache-Control" est défini dans la réponse ?

35 votes

Au lieu d'imprimer dans un fichier sur le disque, vous pourriez utiliser un StringWriter (comme paramètre du constructeur de PrintWriter). Vous devriez ensuite assertTrue(stringWriter.toString().contains("Ma chaîne attendue")); De cette façon, le test lira/écrira en mémoire plutôt que sur le disque.

0 votes

@aaronvargas: Merci pour votre réponse! Mais lorsque j'exécute votre code, j'obtiens l'erreur suivante: java.util.MissingResourceException: Impossible de trouver le bundle pour le nom de base javax.servlet.LocalStrings, locale de_DE - Cela se produit pendant l'exécution de new MyServlet().doPost(...). Avez-vous une idée de ce qui pourrait être cassé?

52voto

Paul Croarkin Points 5845

Tout d'abord, dans une application réelle, vous ne récupéreriez jamais les informations de connexion à la base de données dans un servlet; vous le configureriez dans votre serveur d'application.

Cependant, il existe des moyens de tester les Servlets sans avoir de conteneur en cours d'exécution. L'un consiste à utiliser des objets fictifs. Spring fournit un ensemble de simulacres très utiles pour des choses comme HttpServletRequest, HttpServletResponse, HttpServletSession, etc :

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

En utilisant ces simulacres, vous pourriez 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 pourriez 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
    }
}

0 votes

Cela me donne java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletMapping

3voto

Stephen Gelman Points 123

Je trouve les tests Selenium plus utiles pour les tests d'intégration ou fonctionnels (de bout en bout). J'essaie d'utiliser org.springframework.mock.web mais je ne suis pas très avancé. Je joins un exemple de contrôleur avec un jMock suite de tests.

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 pourra vous aider.

3voto

Mike Kaufman Points 21

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, en utilisant la bibliothèque ObMimic de test-doubles de l'API Servlet d'OpenBrace (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;

/**
 * Tests d'exemple pour {@link YourServlet#doPost(HttpServletRequest, HttpServletResponse)}.
 *
 * @author Mike Kaufman, OpenBrace Limited
 */
public class YourServletTest {

    /** Le servlet à tester par ce test. */
    private YourServlet servlet;

    /** La requête "mimic" à utiliser dans ce test. */
    private HttpServletRequestMimic request;

    /** La réponse "mimic" à utiliser dans ce test. */
    private HttpServletResponseMimic response;

    /**
     * Créer un servlet initialisé et une requête et une réponse pour ce
     * test.
     *
     * @throws ServletException si la méthode d'initialisation du servlet lance une
     *     exception.
     */
    @Before
    public void setUp() throws ServletException {
        /*
         * Notez que pour le servlet simple et les tests impliqués :
         * - Nous n'avons pas besoin de quelque chose de particulier dans ServletConfig du servlet.
         * - Le ServletContext n'est pas pertinent, donc ObMimic peut utiliser
         *   son ServletContext par défaut pour tout.
         */
        servlet = new YourServlet();
        servlet.init(new ServletConfigMimic());
        request = new HttpServletRequestMimic();
        response = new HttpServletResponseMimic();
    }

    /**
     * Tester la méthode doPost avec des valeurs d'argument d'exemple.
     *
     * @throws ServletException si le servlet lance une telle exception.
     * @throws IOException si le servlet lance une telle exception.
     */
    @Test
    public void testYourServletDoPostWithExampleArguments()
            throws ServletException, IOException {

        // Configurer la requête. Dans ce cas, tout ce dont nous avons besoin sont les trois
        // paramètres de la requête.
        RequestParameters parameters
            = request.getMimicState().getRequestParameters();
        parameters.set("username", "mike");
        parameters.set("password", "xyz#zyx");
        parameters.set("name", "Mike");

        // Exécuter le "doPost".
        servlet.doPost(request, response);

        // Vérifier le Content-Type de la réponse, l'en-tête Cache-Control et
        // le contenu du corps.
        assertEquals("text/html; charset=ISO-8859-1",
            response.getMimicState().getContentType());
        assertArrayEquals(new String[] { "no-cache" },
            response.getMimicState().getHeaders().getValues("Cache-Control"));
        assertEquals("...résultat attendu de dataManager.register...",
            response.getMimicState().getBodyContentAsString());

    }

}

Remarques :

  • Chaque "mimic" a un objet "mimicState" pour son état logique. Cela permet 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 du Content-Type inclue "charset=ISO-8859-1". Cependant, pour le code "doPost" donné, c'est conforme à la Javadoc de l'API Servlet, et à la méthode getContentType de HttpServletResponse, et à l'en-tête Content-Type réel produit sur par exemple Glassfish 3. Vous pourriez ne pas vous en rendre compte si vous utilisez des objets mock normaux et vos propres attentes du 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 inattendu de l'API qui peut tourner en dérision les mocks !

  • J'ai utilisé response.getMimicState().getContentType() comme moyen le plus simple de vérifier le Content-Type et d'illustrer le point ci-dessus, mais vous pourriez en effet vérifier simplement "text/html" (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ère (en utilisant l'encodage du Writer). Nous pourrions également vérifier que le Writer de la réponse a été utilisé plutôt que son OutputStream (en utilisant response.getMimicState().isWritingCharacterContent()), mais j'ai supposé que nous nous intéressons uniquement à la sortie résultante, et que nous nous fichons des appels à l'API qui l'ont produite (bien que cela puisse également être vérifié...). Il est également possible de récupérer le contenu du corps de la réponse sous forme de bytes, d'examiner l'état détaillé du Writer/OutputStream, etc.

Vous trouverez des détails complets sur ObMimic et un téléchargement gratuit sur le site OpenBrace. Ou vous pouvez me contacter si vous avez des questions (les coordonnées sont sur le site web).

1voto

ashok Points 21
 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);

}

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X