All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fr.ght1pc9kc.testy.mongo.WithMongoData Maven / Gradle / Ivy

package fr.ght1pc9kc.testy.mongo;

import com.fasterxml.jackson.databind.ObjectMapper;
import fr.ght1pc9kc.testy.core.extensions.WithObjectMapper;
import org.bson.Document;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import reactor.core.publisher.Mono;

import javax.inject.Named;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Extension allowing to initialize a mongo database with data.
 * 

* Example of use with model: *

 *
 * @Builder
 * @Value
 * public class User {
 *
 *     private final String id;
 *     private final String login;
 *     private final String firstName;
 *     private final String lastName;
 *     private final String password;
 *
 * }
 * 
*

* Implementation of the data set: *

 *
 * public class UserDataSet implements MongoDataSet<User> {
 *     @Override
 *     public List<User> documents() {
 *         final User user = User.builder()
 *                 .id("generated-id")
 *                 .firstName("Obiwan")
 *                 .lastName("Kenobi")
 *                 .login("okenobi")
 *                 .password("110812f67fa1e1f0117f6f3d70241c1a42a7b07711a93c2477cc516d9042f9db")
 *                 .build();
 *         return ImmutableList.of(user);
 *     }
 * }
 * 
*

* Use of the extension. Before each test, the mongo test database will contain the documents of the data set: *

 * public class UserMongoRepositoryTest {
 *
 *     private static final String USER_COLLECTION = "user";
 *
 *     private static final WithEmbeddedMongo WITH_EMBEDDED_MONGO = WithEmbeddedMongo.builder()
 *             .build();
 *     private static final WithObjectMapper WITH_OBJECT_MAPPER = WithObjectMapper.builder()
 *             .addMixin(User.class, UserMongoMixin.class)
 *             .build();
 *     private static final WithMongoData WITH_MONGO_DATA = WithMongoData.builder(WITH_EMBEDDED_MONGO)
 *             .withObjectMapper(WITH_OBJECT_MAPPER)
 *             .addDataset(USER_COLLECTION, new UserDataSet())
 *             .build();
 *
 *     @RegisterExtension
 *     static final ChainedExtension CHAIN = ChainedExtension.outer(WITH_EMBEDDED_MONGO)
 *             .append(WITH_OBJECT_MAPPER)
 *             .append(WITH_MONGO_DATA)
 *             .register();
 *
 *     // (...)
 * }
 * 
* *

Database Tracker

*

For performance reasons, the lib provides a {@link Tracker} that lets the extension know that the database has * not been modified, and that it is therefore not necessary to recreate all the collections.

* *

 * {@literal @}Test
 *  void should_read_data(WithMongoData.Tracker tracker) {
 *      tracker.skipNextSampleLoad();
 *      ...
 *  }
 * 
*/ public final class WithMongoData implements BeforeEachCallback, BeforeAllCallback, ParameterResolver { private static final String MONGO_ID_FIELD = "_id"; private static final String P_TRACKER = "sampleTracker_"; private final WithEmbeddedMongo wEmbeddedMongo; private final @Nullable WithObjectMapper wObjectMapper; private final Map> dataSets; private WithMongoData(WithEmbeddedMongo wEmbeddedMongo, Map> dataSets) { this.wEmbeddedMongo = wEmbeddedMongo; this.wObjectMapper = null; this.dataSets = dataSets; } private WithMongoData(WithEmbeddedMongo wEmbeddedMongo, @NotNull WithObjectMapper wObjectMapper, Map> dataSets) { this.wEmbeddedMongo = wEmbeddedMongo; this.wObjectMapper = wObjectMapper; this.dataSets = dataSets; } @Override public void beforeAll(ExtensionContext context) { String dbName = wEmbeddedMongo.getDatabaseName(); getStore(context).put(P_TRACKER + dbName, new Tracker()); } @Override public void beforeEach(ExtensionContext context) { final ObjectMapper objectMapper = Optional.ofNullable(this.wObjectMapper) .map(wom -> wom.getObjectMapper(context)) .orElseGet(ObjectMapper::new); final ReactiveMongoTemplate mongoTemplate = this.wEmbeddedMongo.getMongoTemplate(context); final Tracker tracker = getStore(context).get(P_TRACKER + this.wEmbeddedMongo.getDatabaseName(), Tracker.class); if (tracker == null) { throw new IllegalStateException(getClass().getName() + " must be static and package-protected !"); } else if (tracker.skipNext.getAndSet(false)) { return; } dataSets.forEach((collection, dataSet) -> { mongoTemplate.dropCollection(collection).block(); fillCollection(mongoTemplate, objectMapper, collection, dataSet); }); } @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext context) { Class type = parameterContext.getParameter().getType(); final String dbName = wEmbeddedMongo.getDatabaseName(); return Tracker.class.equals(type) && dbName.equals(getCatalogForParameter(parameterContext, dbName)); } @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { Class type = parameterContext.getParameter().getType(); final String dbName = wEmbeddedMongo.getDatabaseName(); if (Tracker.class.equals(type)) { return getStore(extensionContext).get(P_TRACKER + dbName); } throw new NoSuchElementException(P_TRACKER + dbName); } private String getCatalogForParameter(ParameterContext parameterContext, String dbName) { return parameterContext.findAnnotation(Named.class) .map(Named::value) .orElse(dbName); } private ExtensionContext.Store getStore(ExtensionContext context) { String dbName = wEmbeddedMongo.getDatabaseName(); return context.getStore(ExtensionContext.Namespace.create(getClass().getName(), dbName)); } private void fillCollection(ReactiveMongoTemplate mongoDb, ObjectMapper objectMapper, String collectionName, MongoDataSet dataSet) { final List toInsert = dataSet.documents().stream() .map(o -> { if (o instanceof Document document) { return document; } Document doc = objectMapper.convertValue(o, Document.class); Object identifier = doc.get(dataSet.identifier()); if (identifier != null) { doc.remove(dataSet.identifier()); doc.put(MONGO_ID_FIELD, identifier); } return doc; }).toList(); mongoDb.insertAll(Mono.just(toInsert), collectionName).blockLast(); } /** * Create a {@link WithMongoDataBuilder} for the extension. * * @param wEmbeddedMongo Embedded mongo database. * @return {@link WithMongoDataBuilder}. */ public static WithMongoDataBuilder builder(WithEmbeddedMongo wEmbeddedMongo) { return new WithMongoDataBuilder(wEmbeddedMongo); } /** * Builder for the extension class {@link WithMongoData}. */ public static final class WithMongoDataBuilder { private final WithEmbeddedMongo wEmbeddedMongo; @Nullable private WithObjectMapper wObjectMapper; private final Map> dataSetsBuilder = new HashMap<>(); private WithMongoDataBuilder(WithEmbeddedMongo wEmbeddedMongo) { this.wEmbeddedMongo = wEmbeddedMongo; } /** * Add a customized object mapper to convert the objects to documents. * * @param wObjectMapper The {@link WithObjectMapper} extension to map the documents. * @return Builder instance. */ public WithMongoDataBuilder withObjectMapper(WithObjectMapper wObjectMapper) { this.wObjectMapper = wObjectMapper; return this; } /** * Add a data set. * * @param collectionName Name of the collection the data-set will fill. * @param dataSet Set of data to initialize the collection with. * @return Builder instance. */ public WithMongoDataBuilder addDataset(String collectionName, MongoDataSet dataSet) { this.dataSetsBuilder.put(collectionName, dataSet); return this; } /** * Build the extension. * * @return The built {@link WithMongoData} extension. */ public WithMongoData build() { return Optional.ofNullable(wObjectMapper) .map(wom -> new WithMongoData(wEmbeddedMongo, wom, Map.copyOf(dataSetsBuilder))) .orElseGet(() -> new WithMongoData(wEmbeddedMongo, Map.copyOf(dataSetsBuilder))); } } public static class Tracker { private final AtomicBoolean skipNext = new AtomicBoolean(false); public void skipNextSampleLoad() { skipNext.set(true); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy