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

io.dropwizard.testing.DropwizardTestSupport Maven / Gradle / Ivy

There is a newer version: 5.0.0-alpha.4
Show newest version
package io.dropwizard.testing;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.dropwizard.configuration.ConfigurationSourceProvider;
import io.dropwizard.configuration.YamlConfigurationFactory;
import io.dropwizard.core.Application;
import io.dropwizard.core.Configuration;
import io.dropwizard.core.cli.Command;
import io.dropwizard.core.cli.ConfiguredCommand;
import io.dropwizard.core.cli.EnvironmentCommand;
import io.dropwizard.core.cli.ServerCommand;
import io.dropwizard.core.setup.Bootstrap;
import io.dropwizard.core.setup.Environment;
import io.dropwizard.lifecycle.Managed;
import io.dropwizard.logging.common.LoggingUtil;
import net.sourceforge.argparse4j.inf.Namespace;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.slf4j.Logger;

import javax.validation.constraints.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

import static java.util.Objects.requireNonNull;

/**
 * A test support class for starting and stopping your application at the start and end of a test class.
 * 

* By default, the {@link Application} will be constructed using reflection to invoke the nullary * constructor. If your application does not provide a public nullary constructor, you will need to * override the {@link #newApplication()} method to provide your application instance(s). *

* * @param the configuration type */ public class DropwizardTestSupport { protected final Class> applicationClass; @Nullable protected final String configPath; @Nullable protected final ConfigurationSourceProvider configSourceProvider; protected final Set configOverrides; @Nullable protected final String customPropertyPrefix; protected final Function, Command> commandInstantiator; /** * Flag that indicates whether instance was constructed with an explicit * Configuration object or not; handling of the two cases differ. * Needed because state of {@link #configuration} changes during lifecycle. */ protected final boolean explicitConfig; @Nullable protected C configuration; @Nullable protected Application application; @Nullable protected Environment environment; @Nullable protected Server jettyServer; protected List> listeners = new ArrayList<>(); public DropwizardTestSupport(Class> applicationClass, @Nullable String configPath, ConfigOverride... configOverrides) { this(applicationClass, configPath, (String) null, configOverrides); } /** * @since 2.0 */ public DropwizardTestSupport(Class> applicationClass, @Nullable String configPath, @Nullable ConfigurationSourceProvider configSourceProvider, ConfigOverride... configOverrides) { this(applicationClass, configPath, configSourceProvider, null, configOverrides); } /** * @since 2.0 */ public DropwizardTestSupport(Class> applicationClass, @Nullable String configPath, @Nullable ConfigurationSourceProvider configSourceProvider, @Nullable String customPropertyPrefix, ConfigOverride... configOverrides) { this(applicationClass, configPath, configSourceProvider, customPropertyPrefix, ServerCommand::new, configOverrides); } /** * @since 2.0 */ public DropwizardTestSupport(Class> applicationClass, @Nullable String configPath, @Nullable String customPropertyPrefix, ConfigOverride... configOverrides) { this(applicationClass, configPath, customPropertyPrefix, ServerCommand::new, configOverrides); } /** * @since 2.0 */ public DropwizardTestSupport(Class> applicationClass, @Nullable String configPath, @Nullable String customPropertyPrefix, Function, Command> commandInstantiator, ConfigOverride... configOverrides) { this(applicationClass, configPath, null, customPropertyPrefix, commandInstantiator, configOverrides); } /** * @since 2.0 */ public DropwizardTestSupport(Class> applicationClass, @Nullable String configPath, @Nullable ConfigurationSourceProvider configSourceProvider, @Nullable String customPropertyPrefix, Function, Command> commandInstantiator, ConfigOverride... configOverrides) { this.applicationClass = applicationClass; this.configPath = configPath; this.configSourceProvider = configSourceProvider; this.configOverrides = Optional.ofNullable(configOverrides) .map(Set::of) .orElse(Set.of()); this.customPropertyPrefix = customPropertyPrefix; this.explicitConfig = false; this.commandInstantiator = commandInstantiator; } /** * Alternative constructor that may be used to directly provide Configuration * to use, instead of specifying resource path for locating data to create * Configuration. * * @since 0.9 * * @param applicationClass Type of Application to create * @param configuration Pre-constructed configuration object caller provides; will not * be manipulated in any way, no overriding */ public DropwizardTestSupport(Class> applicationClass, C configuration) { this(applicationClass, configuration, ServerCommand::new); } /** * Alternate constructor that allows specifying the command the Dropwizard application is started with. * @since 1.1.0 * @param applicationClass Type of Application to create * @param configuration Pre-constructed configuration object caller provides; will not * be manipulated in any way, no overriding * @param commandInstantiator The {@link Function} used to instantiate the {@link Command} used to * start the Application */ public DropwizardTestSupport(Class> applicationClass, @Nullable C configuration, Function, Command> commandInstantiator) { if (configuration == null) { throw new IllegalArgumentException("Can not pass null configuration for explicitly configured instance"); } this.applicationClass = applicationClass; this.configPath = ""; this.configSourceProvider = null; this.configOverrides = Collections.emptySet(); this.customPropertyPrefix = null; this.configuration = configuration; this.explicitConfig = true; this.commandInstantiator = commandInstantiator; } public DropwizardTestSupport addListener(ServiceListener listener) { this.listeners.add(listener); return this; } public DropwizardTestSupport manage(final Managed managed) { return addListener(new ServiceListener() { @Override public void onRun(C configuration, Environment environment, DropwizardTestSupport rule) throws Exception { environment.lifecycle().manage(managed); } }); } public void before() throws Exception { applyConfigOverrides(); try { startIfRequired(); } catch (Exception e) { // If there's an exception when setting up the server / application, // manually call after as junit does not call the after method if // the `before` method throws. after(); throw e; } } public void after() { try { stopIfRequired(); } finally { resetConfigOverrides(); } } private void stopIfRequired() { if (jettyServer != null) { for (ServiceListener listener : listeners) { try { listener.onStop(this); } catch (Exception ignored) { } } try { jettyServer.stop(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } finally { jettyServer = null; } } // Don't leak logging appenders into other test cases if (configuration != null) { configuration.getLoggingFactory().reset(); } else { LoggingUtil.getLoggerContext().getLogger(Logger.ROOT_LOGGER_NAME).detachAndStopAllAppenders(); } } private void applyConfigOverrides() { configOverrides.forEach(ConfigOverride::addToSystemProperties); } private void resetConfigOverrides() { configOverrides.forEach(ConfigOverride::removeFromSystemProperties); } private void startIfRequired() throws Exception { if (jettyServer != null) { return; } application = newApplication(); final Bootstrap bootstrap = new Bootstrap(getApplication()) { @Override public void run(C configuration, Environment environment) throws Exception { environment.lifecycle().addServerLifecycleListener(server -> jettyServer = server); super.run(configuration, environment); for (ServiceListener listener : listeners) { try { listener.onRun(configuration, environment, DropwizardTestSupport.this); } catch (Exception ex) { throw new RuntimeException("Error running app rule start listener", ex); } } } }; getApplication().initialize(bootstrap); if (configSourceProvider != null) { bootstrap.setConfigurationSourceProvider(configSourceProvider); } if (explicitConfig) { bootstrap.setConfigurationFactoryFactory((klass, validator, objectMapper, propertyPrefix) -> new POJOConfigurationFactory<>(getConfiguration())); } else if (customPropertyPrefix != null) { @NotNull final String prefix = customPropertyPrefix; bootstrap.setConfigurationFactoryFactory((klass, validator, objectMapper, propertyPrefix) -> new YamlConfigurationFactory<>(klass, validator, objectMapper, prefix)); } final Map namespaceAttributes = Optional.ofNullable(configPath) .filter(path -> !path.isEmpty()) .map(path -> Collections.singletonMap("file", (Object)path)) .orElse(Collections.emptyMap()); final Namespace namespace = new Namespace(namespaceAttributes); final Command command = commandInstantiator.apply(application); command.run(bootstrap, namespace); if (command instanceof EnvironmentCommand) { @SuppressWarnings("unchecked") EnvironmentCommand environmentCommand = (EnvironmentCommand) command; this.configuration = environmentCommand.getConfiguration(); this.environment = environmentCommand.getEnvironment(); } else if (command instanceof ConfiguredCommand) { @SuppressWarnings("unchecked") ConfiguredCommand configuredCommand = (ConfiguredCommand) command; this.configuration = configuredCommand.getConfiguration(); } } public C getConfiguration() { return requireNonNull(configuration, "configuration"); } public int getLocalPort() { return getPort(0); } public int getAdminPort() { final Connector[] connectors = requireNonNull(jettyServer, "jettyServer").getConnectors(); return ((ServerConnector) connectors[connectors.length - 1]).getLocalPort(); } public int getPort(int connectorIndex) { return ((ServerConnector) requireNonNull(jettyServer, "jettyServer").getConnectors()[connectorIndex]).getLocalPort(); } public Application newApplication() { try { return applicationClass.getConstructor().newInstance(); } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) public > A getApplication() { return (A) requireNonNull(application, "application"); } public Environment getEnvironment() { return requireNonNull(environment, "environment"); } public ObjectMapper getObjectMapper() { return getEnvironment().getObjectMapper(); } public abstract static class ServiceListener { public void onRun(T configuration, Environment environment, DropwizardTestSupport rule) throws Exception { // Default NOP } public void onStop(DropwizardTestSupport rule) throws Exception { // Default NOP } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy