mardi 27 juillet 2010

Wicket Test et Spring

Wicket fournit en standard un outil très pratique pour tester ses pages web sans avoir à démarrer son serveur : wicket-test. Les composants Wicket étant de simples objets Java, il est possible de les manipuler et de les tester depuis un test JUnit.


On peut alors écrire ce genre de test :

@Test
public void testPage(){
WicketTester tester =new WicketTester(new MyWicketApplication());
tester.startPage(new MyFirstPage());
tester.assertRenderedPage(MyFirstPage.class);
//test du label
tester.assertLabel("myLabel","my value");
//simule un click
tester.clickLink("toPage2");
tester.assertRenderedPage(MySecondPage.class);
tester.assertNoErrorMessage();
//soumet le formulaire
tester.submitForm("myForm");
// etc. etc.
}

Ceci est bien pratique, et bien plus simple que générer des clicks avec un outil comme Selenium... C'est un des points forts de l'approche composants de Wicket.
Cependant lorsque l'application utilise des services Spring, il faut que les tests aient accès au contexte Spring pour pouvoir afficher correctement les pages. On peut avoir par exemple une page qui affiche une liste d'objets provenants de la base de données, à travers un service Spring.

2 solutions s'offrent à nous, créer un contexte Spring factice avec des mocks des services, ou alors injecter le vrai contexte Spring pour faire des tests d'intégration.

L'approche Mock


On considère que la classe MyWicketApplication contient les méthodes suivantes :

@Override
    protected void init() {
         configureSpringInjection();
         //...
     }

    @Override
    protected void configureSpringInjection() {
        addComponentInstantiationListener(new SpringComponentInjector(this));
    }

On crée une classe MockApplication qui hérite de MyWicketApplication :
public class MockApplication extends MyWicketApplication {

    private AnnotApplicationContextMock context;

    @Override
    protected void init() {
         super.init();
         configureSpringInjection();
     }

    @Override
    protected void configureSpringInjection() {
        context = new AnnotApplicationContextMock();
        addComponentInstantiationListener(new SpringComponentInjector(this, context, true));
    }

    public AnnotApplicationContextMock getContext() {
        return context;
    }
}

On crée aussi une classe MockWicketTester qu'on va réutiliser dans nos tests :

public class MockWicketTester extends WicketTester {

   private AnnotApplicationContextMock context;

    public MockWicketTester() {
        super(new MockApplication());
        context = ((MockApplication) getApplication()).getContext();    
    }

    public AnnotApplicationContextMock getContext() {
        return context;
    }
    
}

En utilisant Mockito pour créer des mocks de nos services, on peut écrire le test ainsi :

import static org.mockito.Mockito.*;

@Test
public void testPage(){
MockWicketTester tester = new MockWicketTester();
MySpringService service = mock(MySpringService.class);
//configuration des valeurs de retour du mock avec Mockito si nécessaire
tester.getContext().putBean(service);
tester.startPage(new MyFirstPage());
//...
}

Utilisation du contexte réel

Le type du contexte n'est plus AnnotApplicationContextMock mais un simple ApplicationContext.
Dans la méthode configureSpringInjection de la classe principale de l'application, on passe en paramètre le fichier de configuration Spring de l'application :

public class IntegrationTestApplication extends MyWicketApplication{

    @Override
    protected void configureSpringInjection() {
        ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"applicationContext.xml"});
        addComponentInstantiationListener(new SpringComponentInjector(this, context, true));
    }
}

Le code du WicketTester est alors le suivant :
public class IntegrationWicketTester extends WicketTester {

    public MockWicketTester() {
        super(new IntegrationTestApplication());
    }


Ensuite dans les tests, il n'est plus nécessaire d'injecter les mocks, on dispose du contexte Spring complet avec tous les services correctement initialisés :

@Test
public void testPage(){
IntegrationWicketTester tester = new IntegrationWicketTester();
tester.startPage(new MyFirstPage());
//...
}

En espérant que ça vous soit utile!