
org.zapodot.junit.db.EmbeddedDatabaseExtension Maven / Gradle / Ivy
The newest version!
package org.zapodot.junit.db;
import org.junit.jupiter.api.extension.*;
import org.junit.platform.commons.util.AnnotationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zapodot.junit.db.annotations.ConfigurationProperty;
import org.zapodot.junit.db.annotations.EmbeddedDatabase;
import org.zapodot.junit.db.annotations.EmbeddedDatabaseTest;
import org.zapodot.junit.db.common.CompatibilityMode;
import org.zapodot.junit.db.common.EmbeddedDatabaseCreator;
import org.zapodot.junit.db.common.Engine;
import org.zapodot.junit.db.internal.AbstractEmbeddedDatabaseCreatorBuilder;
import org.zapodot.junit.db.internal.InternalEmbeddedDatabaseCreator;
import javax.sql.DataSource;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.sql.Connection;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* A JUnit5 Extension that makes it easy to test JDBC integration code
*/
public class EmbeddedDatabaseExtension implements EmbeddedDatabaseCreator, BeforeEachCallback, AfterEachCallback, TestInstancePostProcessor, ParameterResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(EmbeddedDatabaseExtension.class);
private static final ExtensionContext.Namespace EMBEDDED_DB_EXT = ExtensionContext.Namespace
.create("org.zapodot.junit.db");
private static final String STORE_PROPERTY_DATABASE_CREATOR = "embeddedDatabaseCreator";
private static final String TEST_INSTANCE = "testInstance";
private final InternalEmbeddedDatabaseCreator embeddedDatabaseCreator;
public EmbeddedDatabaseExtension() {
this(null);
}
private EmbeddedDatabaseExtension(final InternalEmbeddedDatabaseCreator embeddedDatabaseCreator) {
this.embeddedDatabaseCreator = embeddedDatabaseCreator;
}
@Override
public void afterEach(final ExtensionContext context) throws Exception {
final InternalEmbeddedDatabaseCreator internalEmbeddedDatabaseCreator = context.getStore(EMBEDDED_DB_EXT).get(
STORE_PROPERTY_DATABASE_CREATOR,
InternalEmbeddedDatabaseCreator.class);
if (internalEmbeddedDatabaseCreator != null) {
internalEmbeddedDatabaseCreator.takeDownConnection();
}
context.getStore(EMBEDDED_DB_EXT).remove(TEST_INSTANCE);
context.getStore(EMBEDDED_DB_EXT).remove(STORE_PROPERTY_DATABASE_CREATOR);
}
@Override
public void beforeEach(final ExtensionContext context) throws Exception {
final InternalEmbeddedDatabaseCreator embeddedDatabaseCreatorFromConfiguration = Optional
.ofNullable(embeddedDatabaseCreator)
.orElseGet(() -> Optional.ofNullable(context.getStore(EMBEDDED_DB_EXT).get(
STORE_PROPERTY_DATABASE_CREATOR,
InternalEmbeddedDatabaseCreator.class))
.orElseGet(() -> tryToCreateFromExtensionContext(context).orElse(null)));
if (embeddedDatabaseCreatorFromConfiguration != null) {
embeddedDatabaseCreatorFromConfiguration
.setupConnection(embeddedDatabaseCreatorFromConfiguration
.getPredefinedName() != null ? embeddedDatabaseCreatorFromConfiguration
.getPredefinedName() : extractNameFromExtensionContext(context));
tryToInjectDataSourceOrConnection(context.getStore(EMBEDDED_DB_EXT).get(TEST_INSTANCE),
embeddedDatabaseCreatorFromConfiguration);
context.getStore(EMBEDDED_DB_EXT)
.put(STORE_PROPERTY_DATABASE_CREATOR, embeddedDatabaseCreatorFromConfiguration);
}
}
@Override
public void postProcessTestInstance(final Object testInstance, final ExtensionContext context) {
tryToCreateFromExtensionContext(context)
.ifPresent(dc -> context.getStore(EMBEDDED_DB_EXT).put(STORE_PROPERTY_DATABASE_CREATOR, dc));
context.getStore(EMBEDDED_DB_EXT).put(TEST_INSTANCE, testInstance);
}
@Override
public boolean supportsParameter(final ParameterContext parameterContext,
final ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.isAnnotated(EmbeddedDatabase.class);
}
@Override
public Object resolveParameter(final ParameterContext parameterContext,
final ExtensionContext extensionContext) throws ParameterResolutionException {
final Parameter parameter = parameterContext.getParameter();
final InternalEmbeddedDatabaseCreator databaseCreator = extensionContext.getStore(
EMBEDDED_DB_EXT).get(STORE_PROPERTY_DATABASE_CREATOR, InternalEmbeddedDatabaseCreator.class);
if (DataSource.class.isAssignableFrom(parameter.getType())) {
return databaseCreator.getDataSource();
} else if (Connection.class.isAssignableFrom(parameter.getType())) {
return databaseCreator.getConnection();
} else if (String.class.isAssignableFrom(parameter.getType())) {
return databaseCreator.getConnectionJdbcUrl();
}
return null;
}
@Override
public Connection getConnection() {
return embeddedDatabaseCreator.getConnection();
}
@Override
public DataSource getDataSource() {
return embeddedDatabaseCreator.getDataSource();
}
@Override
public boolean isAutoCommit() {
return embeddedDatabaseCreator.isAutoCommit();
}
@Override
public String getConnectionJdbcUrl() {
return embeddedDatabaseCreator.getConnectionJdbcUrl();
}
private void tryToInjectDataSourceOrConnection(final Object testInstance,
final EmbeddedDatabaseCreator embeddedDatabaseCreator) {
findInjectCandidateFields(testInstance.getClass()).stream()
.forEach(field -> injectDataSourceOrConnection(testInstance,
field,
embeddedDatabaseCreator));
}
private List findInjectCandidateFields(final Class type) {
return AnnotationUtils.findAnnotatedFields(type, EmbeddedDatabase.class, f -> DataSource.class
.isAssignableFrom(f.getType()) || Connection.class
.isAssignableFrom(f.getType()));
}
private void injectDataSourceOrConnection(final Object testInstance,
final Field field,
final EmbeddedDatabaseCreator embeddedDatabaseCreator) {
boolean accessibleOriginal = field.canAccess(testInstance);
field.setAccessible(true);
try {
if (DataSource.class.isAssignableFrom(field.getType())) {
LOGGER.debug("Will inject javax.sql.DataSource to field {}", field.getName());
field.set(testInstance, embeddedDatabaseCreator.getDataSource());
} else if (Connection.class.isAssignableFrom(field.getType())) {
LOGGER.debug("Will inject java.sql.Connection to field {}", field.getName());
field.set(testInstance, embeddedDatabaseCreator.getConnection());
}
} catch (IllegalAccessException | IllegalArgumentException e) {
throw new IllegalStateException("Could not inject embedded DataSource/Connection to field", e);
} finally {
field.setAccessible(accessibleOriginal);
}
}
private String extractNameFromExtensionContext(final ExtensionContext extensionContext) {
return extensionContext.getTestClass().map(Class::getSimpleName).orElse(extensionContext.getUniqueId());
}
private static Optional tryToCreateFromExtensionContext(final ExtensionContext extensionContext) {
LOGGER.debug("Constructing DataSource configuration using annotations");
final Optional dataSourceConfig = findAnnotation(extensionContext.getElement(),
EmbeddedDatabaseTest.class);
if (!dataSourceConfig.isPresent()) {
LOGGER.warn(
"No configuration found. There should be an @DataSourceConfig annotation on either the test class or the method");
return Optional.empty();
} else {
final EmbeddedDatabaseTest dataSourceConfigValue = dataSourceConfig.get();
final Builder builder;
if (Engine.HSQLDB.equals(dataSourceConfigValue.engine())) {
builder = Builder.hsqldb();
} else {
builder = Builder.h2();
}
if (dataSourceConfigValue.name() == null && !dataSourceConfigValue.name().equals("")) {
builder.withName(dataSourceConfigValue.name());
}
if (dataSourceConfigValue.compatibilityMode() == null) {
builder.withMode(CompatibilityMode.REGULAR);
} else {
builder.withMode(dataSourceConfigValue.compatibilityMode());
}
final ConfigurationProperty[] properties = dataSourceConfigValue.properties();
if (properties != null) {
Arrays.stream(properties).forEach(cp -> builder.withProperty(cp.name(), cp.value()));
}
if (!dataSourceConfigValue.autoCommit()) {
builder.withoutAutoCommit();
}
if (dataSourceConfigValue.initialSqls() != null) {
Arrays.asList(dataSourceConfigValue.initialSqls())
.forEach(builder::withInitialSql);
}
if (dataSourceConfigValue.initialSqlResources() != null) {
Arrays.asList(dataSourceConfigValue.initialSqlResources())
.forEach(builder::withInitialSqlFromResource);
}
return Optional.of(builder.buildInternalEmbeddedDatabaseCreator());
}
}
private static Optional findAnnotation(Optional extends AnnotatedElement> element,
Class annotationType) {
return element.flatMap(e -> findAnnotationForElement(annotationType, e));
}
private static Optional findAnnotationForElement(final Class annotationType,
final AnnotatedElement e) {
return AnnotationUtils.findAnnotation(e, annotationType);
}
public static class Builder extends AbstractEmbeddedDatabaseCreatorBuilder {
private Builder(final Engine engine) {
super(engine);
}
/**
* Creates a builder for the H2 engine
*
* @return a builder for creating an {@link EmbeddedDatabaseExtension} that will use the H2 engine
*/
public static Builder h2() {
return new Builder(Engine.H2);
}
/**
* Creates a builder for the H2 engine
*
* @return a builder for creating an {@link EmbeddedDatabaseExtension} that will use the HSQLDB engine
*/
public static Builder hsqldb() {
return new Builder(Engine.HSQLDB);
}
InternalEmbeddedDatabaseCreator buildInternalEmbeddedDatabaseCreator() {
return new InternalEmbeddedDatabaseCreator(autoCommit,
name,
propertiesMap(),
initializationPlugins,
createJdbcUrlFactory(),
compatibilityMode);
}
@Override
public EmbeddedDatabaseExtension build() {
return new EmbeddedDatabaseExtension(buildInternalEmbeddedDatabaseCreator());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy