Play tips – Test with an embedded mongodb

Si il y a deux outils que j’affectionne en ce moment, c’est bien Play! (v1.2.5, la v2 ne m’a pas encore convaincu) et MongoDB. Pour ceux qui ne connaissent pas Mongo, c’est une base de données NoSQL orientée document qui écrase un peu la concurrence actuellement. Pour synthétiser les points forts de celle-ci en quelques lignes (car ce n’est pas le sujet de ce billet), on vous dira que :

– MongoDb n’a pas de schéma, vous n’avez qu’à envoyer un objet en JSON, et il stocké, point barre.
– MongoDB va stocker votre objet comme un document en BSON (un encodage binaire du JSON)
– MongoDB vous donne une API javascript pour faire des requêtes, mais des drivers sont existants dans la plupart des languages.
– MongoDB est très performant. Et qu’en plus si une instance ne suffit pas en prod, vous pouvez en mettre plusieurs, et le sharding (la répartition de vos données entre les différentes instances) se fait tout seul.
– Mongo gère la réplication entre instances, mais également la consistence de votre base (ce qui n’est pas le cas de toutes les bases NoSQL) à travers un fichier de log des opérations entre autres.
Et globalement tout ceci est assez vrai. Mongo n’est surement pas la base de données ultime mais elle rend de très bons services si l’on a des choses un peu volumineuses à stocker.

Si vous avez testé Play!, vous avez surement déjà remarqué le système de persistence assez bien foutu. On annote une entité, on lui fait étendre une classe Model et ça roule. Et bien, pour MongoDB c’est à peu près la même chose.

Vous devez commencer par ajouter Morphia dans les dépendances du projet. Morphia (hébergé sous google code, si je vous jure, ça existe encore) est un petit ORM Java/MongoDB qui marche pas mal, et justement un module Play existe. on édite donc le fichier dependencies.yml pour ajouter Morphia.

 - play -> morphia 1.2.9

Un petit coup de “play deps” en ligne de commande pour récupérer le module et nous sommes prêts. Dans votre projet, prenez (ou créez) une entité, par exemple App

@Entity
public class App extends Model {
public String name;
}
view raw App.java hosted with ❤ by GitHub

Si on ajoute un simple test unitaire

import models.App;
import org.junit.Test;
import play.test.UnitTest;
import java.util.List;
public class AppTest extends UnitTest {
@Test
public void saveNewApp() {
App app = new App();
app.name = "camel-ref";
app.save();
List<App> apps = App.findAll();
assertEquals(1, apps.size());
}
}
view raw AppTest.java hosted with ❤ by GitHub

et qu’on le lance, le test échoue lamentablement à se connecter à votre MongoDB. Normal! Et le but de ce billet et de vous montrer comment lancer ces tests d’intégration sans installer MongoDB sur une machine. Pour cela une petite lib très pratique existe : embedmongo. Vous pouvez l’ajouter dans votre fichier de dépendances :

- de.flapdoodle.embedmongo -> de.flapdoodle.embedmongo 1.16

Nous devons compléter notre test unitaire pour démarrer une instance Mongo avant le test. La plupart des articles illustrant l’utilisation de cette librairie montre des exemples de tests unitaires où l’instance est démarrée dans une méthode annotée @BeforeClass (qui, comme vous le savez sans doute, sera executée avant les tests). Mais Play! fonctionne un peu différement puisqu’un test unitaire Play! (qui étend la classe UnitTest) démarre en fait l’application avant de lancer la classe de tests et notamment les plugins utilisés comme Morphia. Si nous utilisons une méthode annotée @BeforeClass , le test va échouer car Morphia ne parviendra pas à se connecter à l’instance embarquée qui ne sera pas encore démarrée. Un moyen simple de contournement est de créer notre propre classe de base pour les tests unitaires qui utilisera notre Test Runner plutôt que celui par défaut de Play! En fait, notre Runner va tout simplement étendre le Runner de base mais démarrera l’instance Mongo avant de démarrer Play! Get it ?

Voila donc notre classe de base de test (qui remplace UnitTest et utilise donc notre propre runner de tests)

@RunWith(MongoPlayJUnitRunner.class)
public class MongoTest extends org.junit.Assert {
@Rule
public PlayJUnitRunner.StartPlay startPlayBeforeTests = PlayJUnitRunner.StartPlay.rule();
}
view raw MongoTest.java hosted with ❤ by GitHub

et notre Runner, qui démarre l’instance avant d’appeler le runner de Play!

public class MongoPlayJUnitRunner extends PlayJUnitRunner {
static int PORT = 27017;
static {
try {
MongodConfig config = new MongodConfig(Version.V2_0, PORT, Network.localhostIsIPv6());
MongodExecutable prepared = MongoDBRuntime.getDefaultInstance().prepare(config);
prepared.start();
new Mongo("localhost", PORT);
PlayJUnitRunner.StartPlay.rule();
} catch (IOException e) {
Logger.error("Could not start embedmongo instance");
}
}
public MongoPlayJUnitRunner(Class testClass) throws ClassNotFoundException, InitializationError, IOException {
super(testClass);
}
}

Notre test devient

import models.App;
import org.junit.Test;
import java.util.List;
public class AppMongoTest extends MongoTest {
@Test
public void saveNewApp() {
App app = new App();
app.name = "camel-ref";
app.save();
List<App> apps = App.findAll();
assertEquals(1, apps.size());
}
}

Tadam! Cette fois le test fonctionne!

Enjoy!

À propos de Cédric Exbrayat

Cédric Exbrayat, développeur et fondateur Ninja Squad, se réunit avec ses semblables régulièrement que ce soit au Lyon JUG ou à Mix-it, dont il est le fondateur. Java et JS for food and fun.

Publié le 30/07/2012, dans Tips, et tagué , , . Bookmarquez ce permalien. 1 Commentaire.

  1. Salut Cédric, je suis en plein dedans en ce moment. Et justement j’ai préféré opter pour Jongo à la place de Morphia. Jettes y un oeil, c’est très light, très simple d’utilisation et plus performant que morphia, la différence est flagrante en recherche.
    a+

Laisser un commentaire