Servlet yksikkötestausta Spring mockeilla Maven + EE 6 ympäristössä

Jahas, tulipa taas käytettyä aika detaileihin ja tapeltua hassujen pikku yksityiskohtien parissa. Tavoite oli tehdä servlet komponentille yksikkötestausta TDD hengessä, ja sitä varten se haluttiin eristää kaikesta muusta, mm. EJB ja JPA kerroksista, jopa niin että servereitä ei käynnistetä. Teoriassahan homma menee näin:

– Tee JUnit tai TestNG tms testirunko haluamallasi tavalla

– Instansio Servlet ja injektoi sille kaikki mitä se tarvitsee toimiakseen – käytännössä esim. kutsut setXX metodeita joilla asetat esim. mahdollisen tilattoman ejb viittauksen jäsenmuuttujaan. Periaatteessa kaikki oliot mitä servlet hakee/tarvitsee toimiakseen tulisi asettaa sille mockeina. Voit tehdä mockit itse anonyymillä luokalla joka toteuttaa rajapinnan, tai voit käyttää esim. easymock tai mockito kehyksiä joista kirjoittelin jo aiemmin. Jos olet kovakoodannut viittauksia JNDI:hin, tough luck. Ne pitää sitten purkaa ulos, jos servlettisi aktiivisesti hakee resurssiviittauksia sitä ei voi käytännössä yksikkötestata.

– Varsinaiseen kutsuun tarvitset myös mockit Request ja Response olioista – olen tässä huomannut Spring test-moduulista löytyvien mockien olevan korvaamaton apu. Luodaan ne, konfiguroidaan ne, ja sitten annetaan ne doGet kutsun parametrina

– Lopuksi tutkitaan request ja response olioista onnistuiko kutsu. Mistä sen tietää? Riippuu mitä servlet tekee. Tulostettiinko sivulle jotain? Asetettiinko requestiin muuttujia? Tuliko poikkeuksia?

Näin siis periaatteessa homma menee. Tässä esimerkkikoodia:

 @Test
     public void testDoGet() throws ServletException, IOException {

         // set up SUT
         HelloServlet hs = new HelloServlet();

         // set up mocks SUT needs, add some request (form) parameters
         MockHttpServletRequest request = new MockHttpServletRequest("GET","/HelloServlet");
         request.addParameter("name", "Johnny");

         MockHttpServletResponse response = new MockHttpServletResponse();

         // test SUT
         hs.doGet(request,response);

         // validate results
         String result = response.getContentAsString();
         assertTrue(result.contains("Johnny"));
         assertTrue(result.contains("Hello"));

     }

Tässä ei ole siis EJB viittauksia tai muita resursseja, vaan mittaillaan ihan perinteisen helloworld servletin rakennetta. Ongelma tuli tätä testatessa mavenissä. Sain aika metkan virheilmoituksen:

java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/servlet/ServletException

Mistäpä moinen johtuu? Näemmä Maven riippuvuudesta jolla noudetaan sinänsä kätevästi EE 6 kirjastot kaikki kerralla, tämmöinen:

        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>

Kävikin ilmi että tämä on paholaisesta. Se hakee näennäisen hyvät servlet.jar paketit mukaan polkuun joilla käännös onnistuu. Mutta joku suuren sarvipään itsensä kehittämä oikku on aiheuttanut sen että niistä on poistettu kaikki bytecode metodirungoista. Toisin sanoen, testaus ei niillä onnistu. Ja mikä hauskointa, jos tuot mukaan uusia kirjastoja jotka korjaavat ongelman, niin tämä riippuvuus siltikin aiheuttaa saman virheen.

Eli ratkaisu. Poistetaan tämä riippuvuus. Korvataan yksittäisillä riippuvuuksilla joilla tuodaan halutut ee piirteet. Tässä on oma ongelmansa: yritäppä löytää geneeristä jpa 2.0 rajapintakirjastoa jostain yleiseltä suurelta palvelimelta.. Mutta käytännössä nappaat mukaan servlet, ejb 3.1, ja jpa 2.0 kirjastot, ja ympäristö on taas ok. Clean and build vielä päälle. Nappasin itse yhden servlet kirjaston joka toimii, muitakin voi toki käytellä. Joskus voisin ihmetellä voiko jollain mekanismilla ylikirjoittaa vain tuon servlet osan ja tuoda muut yleisellä importilla mutta ainakaan vielä en keksinyt keinoa netistä.

Anyway, täältä siis toimiva servlet jar, ja Spring mockit:

   <repositories>
        <repository>
            <id>glassfish-repository</id>
            <url>http://download.java.net/maven/glassfish</url>
        </repository>

    </repositories>  

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>3.0.6.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>3.0.6.RELEASE</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.servlet</artifactId>
            <version>3.0</version>
            <scope>provided</scope>
        </dependency>

Edit: Ooooo Oraclella oli näemmä aika hyvä artikkeli EE 6 yksikkötestauksesta, jossa mm. käyty läpi tämä sama ongelma ja sen ratkaisu, hieman eri lailla. Tässä linkki: http://www.oracle.com/technetwork/articles/java/unittesting-455385.html

 

Pistän tähän varuiksi muistiin pääasian: Jos haluat Glassfish serverin kirjastot ympäristöön, muutkin kuin vain web-luokat, lisää tämä Maven riippuvuuksiin:

 
<dependency>
    <groupId>org.glassfish.extras</groupId>
    <artifactId>glassfish-embedded-all</artifactId>
    <version>3.1.1</version>
    <scope>provided</scope>
 </dependency>

Testattu ja toimii hienosti.

Advertisements

One thought on “Servlet yksikkötestausta Spring mockeilla Maven + EE 6 ympäristössä

Vastaa

Täytä tietosi alle tai klikkaa kuvaketta kirjautuaksesi sisään:

WordPress.com-logo

Olet kommentoimassa WordPress.com -tilin nimissä. Log Out / Muuta )

Twitter-kuva

Olet kommentoimassa Twitter -tilin nimissä. Log Out / Muuta )

Facebook-kuva

Olet kommentoimassa Facebook -tilin nimissä. Log Out / Muuta )

Google+ photo

Olet kommentoimassa Google+ -tilin nimissä. Log Out / Muuta )

Muodostetaan yhteyttä palveluun %s