java unit-testing mongodb junit morphia

java - Pruebas unitarias con MongoDB



unit-testing junit (4)

Mi base de datos de elección es MongoDB. Estoy escribiendo una API de capa de datos para abstraer los detalles de implementación de las aplicaciones cliente, es decir, básicamente estoy brindando una sola interfaz pública (un objeto que actúa como un IDL).

Estoy probando mi lógica a medida que avanzo en TDD. Antes de cada prueba unitaria, se llama a un método @Before para crear una base de datos única, después de lo cual, cuando finaliza la prueba, se llama a un método @After para eliminar la base de datos. Esto ayuda a promover la independencia entre las pruebas unitarias.

Casi todas las pruebas unitarias, es decir, realizar una consulta contextual , requieren algún tipo de lógica de inserción antes de la mano. Mi interfaz pública proporciona un método de inserción; sin embargo, parece incorrecto utilizar este método como lógica precursora para cada prueba unitaria.

Realmente necesito algún tipo de mecanismo de burla, sin embargo, no he tenido mucha experiencia con marcos de burla, y parece que Google no devuelve nada como un marco de burla que uno podría usar con MongoDB.

¿Qué hacen los demás en estas situaciones? Es decir, ¿cómo funciona el código de prueba unitaria de personas que interactúa con una base de datos?

Además, mi interfaz pública se conecta a una base de datos definida en un archivo de configuración externo (parece incorrecto usar esta conexión para las pruebas de mi unidad), una situación que se beneficiaría de algún tipo de burla.


Como escribió sbridges en esta publicación, es una mala idea no tener un servicio dedicado (a veces también conocido como repositorio o DAO) que abstrae el acceso a los datos de la lógica. Entonces podría probar la lógica proporcionando un simulacro de DAO.

Otro enfoque que hago es crear un objeto Mock of the Mongo (por ejemplo, PowerMockito) y luego devolver los resultados apropiados. Esto porque no es necesario probar si la base de datos funciona en pruebas unitarias, pero más debería probar si la consulta correcta se envió al databse.

Mongo mongo = PowerMockito.mock(Mongo.class); DB db = PowerMockito.mock(DB.class); DBCollection dbCollection = PowerMockito.mock(DBCollection.class); PowerMockito.when(mongo.getDB("foo")).thenReturn(db); PowerMockito.when(db.getCollection("bar")).thenReturn(dbCollection); MyService svc = new MyService(mongo); // Use some kind of dependency injection svc.getObjectById(1); PowerMockito.verify(dbCollection).findOne(new BasicDBObject("_id", 1));

Esa también sería una opción. Por supuesto, la creación de los simulacros y el retorno de los objetos apropiados se codifica como un ejemplo anterior.


Escribí una implementación de stub MongoDB en Java: mongo-java-server

El valor predeterminado es un back-end en memoria, que se puede usar fácilmente en las pruebas de Unidad e Integración.

Ejemplo

MongoServer server = new MongoServer(new MemoryBackend()); // bind on a random local port InetSocketAddress serverAddress = server.bind(); MongoClient client = new MongoClient(new ServerAddress(serverAddress)); DBCollection coll = client.getDB("testdb").getCollection("testcoll"); // creates the database and collection in memory and inserts the object coll.insert(new BasicDBObject("key", "value")); assertEquals(1, collection.count()); assertEquals("value", collection.findOne().get("key")); client.close(); server.shutdownNow();


Me sorprende que nadie aconsejó usar fakemongo hasta el momento. Emula al cliente mongo bastante bien, y todo se ejecuta en la misma JVM con pruebas, por lo que las pruebas de integración se vuelven robustas, y técnicamente mucho más cercanas a las verdaderas "pruebas unitarias", ya que no tiene lugar ninguna interacción con un sistema extraño. Es como usar H2 incrustado para probar tu código SQL por unidades. Estuve muy contento de usar fakemongo en pruebas unitarias que prueban el código de integración de la base de datos de una manera integral. Considere esta configuración en el contexto del resorte de prueba:

@Configuration @Slf4j public class FongoConfig extends AbstractMongoConfiguration { @Override public String getDatabaseName() { return "mongo-test"; } @Override @Bean public Mongo mongo() throws Exception { log.info("Creating Fake Mongo instance"); return new Fongo("mongo-test").getMongo(); } @Bean @Override public MongoTemplate mongoTemplate() throws Exception { return new MongoTemplate(mongo(), getDatabaseName()); } }

Con esto puedes probar tu código que usa MongoTemplate desde el contexto de primavera, y en combinación con github.com/lordofthejars/nosql-unit , jsonunit , etc. obtienes pruebas unitarias robustas que cubren el código de consulta de mongo.

@Test @UsingDataSet(locations = {"/TSDR1326-data/TSDR1326-subject.json"}, loadStrategy = LoadStrategyEnum.CLEAN_INSERT) @DatabaseSetup({"/TSDR1326-data/dbunit-TSDR1326.xml"}) public void shouldCleanUploadSubjectCollection() throws Exception { //given JobParameters jobParameters = new JobParametersBuilder() .addString("studyId", "TSDR1326") .addString("execId", UUID.randomUUID().toString()) .toJobParameters(); //when //next line runs a Spring Batch ETL process loading data from SQL DB(H2) into Mongo final JobExecution res = jobLauncherTestUtils.launchJob(jobParameters); //then assertThat(res.getExitStatus()).isEqualTo(ExitStatus.COMPLETED); final String resultJson = mongoTemplate.find(new Query().with(new Sort(Sort.Direction.ASC, "topLevel.subjectId.value")), DBObject.class, "subject").toString(); assertThatJson(resultJson).isArray().ofLength(3); assertThatDateNode(resultJson, "[0].topLevel.timestamp.value").isEqualTo(res.getStartTime()); assertThatNode(resultJson, "[0].topLevel.subjectECode.value").isStringEqualTo("E01"); assertThatDateNode(resultJson, "[0].topLevel.subjectECode.timestamp").isEqualTo(res.getStartTime()); ... etc }

Utilicé fakemongo sin problemas con el controlador mongo 3.4, y la comunidad está muy cerca de lanzar una versión que admite el controlador 3.6 ( https://github.com/fakemongo/fongo/issues/316 ).


Técnicamente, las pruebas de que hablar con una base de datos (nosql u otras) no son pruebas unitarias , ya que las pruebas están probando interacciones con un sistema externo, y no solo probando una unidad aislada de código. Sin embargo, las pruebas que hablan con una base de datos a menudo son extremadamente útiles y, a menudo, son lo suficientemente rápidas para ejecutarse con las otras pruebas unitarias.

Normalmente tengo una interfaz de servicio (por ejemplo, UserService) que encapsula toda la lógica para tratar con la base de datos. El código que se basa en UserService puede usar una versión simulada de UserService y se prueba fácilmente.

Al probar la implementación del Servicio que habla con Mongo, (por ejemplo, MongoUserService) es más fácil escribir un código Java que iniciará / detendrá un proceso mongo en la máquina local, y tendrá su MongoUserService conectado a eso, consulte esta pregunta para algunos notas .

Podría intentar burlarse de la funcionalidad de la base de datos mientras prueba MongoUserService, pero generalmente es demasiado propenso a errores y no prueba lo que realmente desea probar, que es la interacción con una base de datos real. Por lo tanto, al escribir pruebas para MongoUserService, configura un estado de base de datos para cada prueba. Mire en DbUnit un ejemplo de un marco para hacerlo con una base de datos.