Spring & JUnit

January 5th, 2013 | 10 min read | JUnit, Maven, Spring, Testing

Ondertussen hebben we al enkele Spring web applicaties gemaakt met frameworks zoals Dozer, Apache Velocity, Apache Lucene, Hibernate, RESTEasy, … . Wat we echter nog niet hebben gedaan is het testen van de applicatie.

Deze, en waarschijnlijk ook de volgende, tutorials zullen in het teken staan van het testen van een applicatie.

Heads up!

In deze tutorial wordt met regelmaat verwezen naar mijn vorige Java tutorials. Het is dus handig dat je deze (zeker de eerste) eens doorneemt.

In deze eerste tutorial ga ik enkele kleine testjes schrijven die kleine stukken code testen. Tests die maar een bepaald deel (unit) van de code testen noemen we unit tests. Het meest voor de hand liggende framework daarvoor is JUnit.

Project opzetten

Om niet een uur kwijt te zijn voor we een project hebben dat we kunnen gaan testen, ga ik gebruik maken van een eerder geschreven voorbeeld, namelijk de Spring webapp met form. Gebruik dus de code (onderaan het artikel te downloaden) en indien gewenst kan je het project hernoemen naar

spring-junit

.

Maak nu de mappen

src/test/java

en

src/test/resources

aan en klik daarna met je rechtermuisknop op het project en kies voor Maven en dan Update project configuration. Je zal merken dat beide mappen nu als source folder beschikbaar zullen zijn.

Maven configuratie

Zoals eerder gezegd ga ik werken met het JUnit framework, maar net zoals met alle andere frameworks moet ik daarvoor eerst de nodige dependencies hebben. De dependencies die we nodig hebben zijn:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.10</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
    <scope>test</scope>
</dependency>

De eerste dependency ligt voor de hand en met de tweede dependency zorgen we ervoor dat we gebruik kunnen maken van test Spring beans en andere Spring gerelateerde zaken.

Merk ook op dat we een scope meegeven die de waarde test krijgt. Dit wilt zeggen dat deze dependencies enkel nodig zijn in de test fase. Bij het packagen van het project (er een WAR van maken) zullen de dependencies niet toegevoegd worden.

Maak onder het element

<properties>

een nieuwe property aan met als code:

<skipTests>true</skipTests>

Onder

<plugins>

maak je de volgende plugin aan:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.12</version>
    <configuration>
        <skipTests>${skipTests}</skipTests>
    </configuration>
</plugin>

In grote projecten kunnen tests enkele minuten innemen, daarom dat velen ervoor kiezen om tijdens het builden van je project tests uit te schakelen. Je kan ze dan aanzetten door de parameter

skipTests

te overriden. Hoe we dat doen zien we op het einde.

Spring configuratie

Maak nu in de map

src/test/resources

een Spring bean configuratiebestand aan met de naam

spring-test.xml

. Het voordeel van deze test-resources is dat we eigenlijk bepaalde beans kunnen manipuleren.

In de praktijk is dit vooral handig omdat je dan in plaats van werkelijke implementaties mocks kan gaan gebruiken. Stel je maar eens voor dat je een controller gaat testen die allerlei services aanspreekt. Als we deze services kunnen mocken, dan hoeven we bijvoorbeeld niet de hele tijd een webservice of een database aan te spreken.

De configuratie die we gaan gebruiken is de volgende:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="be.g00glen00b.controller.FormController" />
    <bean class="be.g00glen00b.domain.Text" />
</beans>

Zoals je kan zien lijkt dit enorm veel op de eerder geziene configuraties, behalve dat ik hier nergens een component-scan gebruik. De reden daarvoor is dat het op deze manier makkelijker controleerbaar is welke beans er voorzien worden en welke niet.

Test case

Maak nu in

src/test/java

een packages aan met de naam

be.g00glen00b.controller

. Misschien valt het je al op dat dit dezelfde package is als diegene die we in

src/main/java

terug vinden. Dat komt omdat we normaal gezien voor elke class een test class gaan schrijven en voor elke methode in die klasse één of meerdere test methodes.

De enige uitzondering daarin zijn meestal domain classes. De reden daarvoor is dat domain classes meestal enkel uit enkele getters/setters bestaan zonder echt veel business logica (indien er wel business logica bestaat kan je hier wel beter een test class voor schrijven).
Normaal gezien is het zelfs zo dat als je genoeg tests schrijft in alle andere layers (in dit geval de controller), dat je dan ook de functionaliteit van je domain classes test. Je gaat immers in je controller gebruik maken van je domain class, dus eigenlijk test je deze onrechtstreeks ook.

Je kan dus al raden wat we gaan doen, in de package

be.g00glen00b.controller

mag je nu een test case maken met de naam

FormControllerTest

.

Hiervoor klikken we met de rechtermuisknop op de package en kies je voor NewOther… . Hier kies je voor JUnit Test Case zoals in onderstaande afbeelding.

new-test-case

Als eerste stap voer je een naam in en vink je de methode

setUp()

aan. Deze helper methodes zorgen ervoor dat je bepaalde code kunt uitvoeren voor of na de test case aangeroepen wordt (

setUpBeforeClass()

en

tearDownAfterClass()

) of  dat je code kan uitvoeren voor of na elke test case met

setUp()

en

tearDown()

.

new-test-case-4

Onder Class under test kies je nu voor de class die je wenst te testen in deze test case, namelijk

FormController

.

new-test-case-2

Ten slotte vink je in het volgende scherm alle methodes aan onder

FormController

.

new-test-case-3

FormControllerTest

Het enige wat ons nu nog rest is dat we de tests nog moeten schrijven. De code die we hiervoor gaan gebruiken is:

package be.g00glen00b.controller;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.ui.ModelMap;
import org.springframework.web.servlet.ModelAndView;

import be.g00glen00b.domain.Text;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-test.xml")
public class FormControllerTest {

    @Autowired
    private FormController controller;

    private ModelMap model;

    @Before
    public void setUp() throws Exception {
        model = new ModelMap();
    }

    @Test
    public void testPostFormBean() {
        Text text = new Text();
        text.setTekst("test");

        assertEquals("result", controller.postFormBean(text, null, model));
        assertEquals("test", model.get("tekst"));
    }

    @Test
    public void testGetFormBean() {
        ModelAndView result = controller.getFormBean();

        assertEquals("form", result.getViewName());
        assertTrue(result.getModel().containsKey("command"));
        assertTrue(result.getModel().get("command") instanceof Text);
    }

    @Test
    public void testPostFormEasy() {
        assertEquals("result", controller.postFormEasy("test", model));
        assertEquals("test", model.get("tekst"));
    }

    @Test
    public void testGetFormEasy() {
        assertEquals("form2", controller.getFormEasy());
    }

}

Zoals je kan zien is dit al een redelijk uitgebreide class. Het eerste wat je zou moeten opvallen zijn de annotaties bovenaan de class, namelijk

@RunWith(SpringJUnit4ClassRunner.class)

en

@ContextConfiguration("classpath:spring-test.xml")

. Deze annotaties maken het mogelijk om de Spring container te kunnen gebruiken voor onze tests.

Iets verder kan je bijvoorbeeld al zien dat we autowiring gaan gebruiken bij de

FormController

. Eerlijk gezegd zou ik het in de praktijk hier niet voor gebruiken omdat je misschien de staat van het object tijdens de testen kan wijzigen (elke test zou met een nieuw object moeten vertrekken), maar dit is louter om aan te tonen dat het mogelijk is. We werken uiteindelijk toch maar met een controller die eigenlijk (bijna) geen object state heeft.

In de

setUp()

methode kan je zien dat we een nieuwe instantie van een ModelMap aanmaken, dit wilt zeggen dat voor elke test uitgevoerd wordt we beginnen met een nieuwe ModelMap.
Dit kan handig zijn voor gevallen die wel een redelijk grote object state hebben om zo te garanderen dat elke test uitgevoerd wordt op een “basis”-object en niet op een object waar door andere tests al vanalles aan veranderd is.

Daaronder staan dan alle tests, voorafgegaan door de annotatie

@Test

. Voor elke methode is er één test methode die de logica in die methode gaat testen. In tests voor een controller zal het er vooral om gaan om te controleren of het model en de view correct zijn.

Controleren of iets gelijk is aan de verwachtte waarde doe je met assertEquals(). Zo kan je bijvoorbeeld zien in

assertEquals("result", controller.postFormBean(text, null, model));

dat we controleren of de view inderdaad wel

result

is. Mocht dat niet zo zijn zou onze test failen.

Controleren of iets

true

of

false

is doe je met

assertTrue()

of

assertFalse()

. Zo zie je in 

assertTrue(result.getModel().containsKey("command"));

bijvoorbeeld dat we gaan controleren of het model een key bevat met de naam

command

, mocht dat niet zo zijn zou het resultaat

false

zijn en zou de test failen.

De test case is af en nu zijn we klaar om onze test te testen. De uiteindelijke structuur van je project zou er ongeveer zo moeten uitzien als in onderstaande afbeelding.

final-project-structure

Builden

Wat we nu gaan doen is zoals altijd eens een keertje alles builden met het commando:

mvn clean install

Zoals je al zal merken ziet dit er heel gewoon uit, we hadden dan ook tests uitgeschakeld. Als we nu  

skipTests

overriden door de parameter

-DskipTests=false

achteraan toe te voegen zodat we onderstaand commando krijgen:

mvn clean install -DskipTests=false

en we voeren dat uit krijgen we echter extra output te zien, namelijk:

results

Zoals je kan zien worden alle tests uitgevoerd en onderaan krijgen we als resultaat te zien hoeveel tests er al dan niet geslaagd zijn. Mocht er een test niet slagen zal het builden gestaakt worden en wordt er geen WAR gemaakt.

Je kan ook je tests runnen zonder dat je daar Maven voor nodig hebt, als je met je rechtermuisknop op je test class klikt en dan kiest voor Run as, JUnit test dan krijg je de resultaten te zien in Eclipse zelf.

junit-eclipse

Daarmee hebben we dan ook deze eerste tutorial rond testing afgerond. De code kan je zoals gewoonlijk hieronder downloaden.

Download project

Download – spring-junit.rar (5,8 kB)

Back to tutorialsContact me on TwitterDiscuss on Twitter

Profile picture

Dimitri "g00glen00b" Mestdagh is a consultant at Cronos and tech lead at Aquafin. Usually you can find him trying out new libraries and technologies. Loves both Java and JavaScript.