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

org.kiwiproject.test.dropwizard.app.DropwizardAppTests Maven / Gradle / Ivy

package org.kiwiproject.test.dropwizard.app;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;

import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import io.dropwizard.Configuration;
import io.dropwizard.jersey.DropwizardResourceConfig;
import io.dropwizard.jersey.setup.JerseyEnvironment;
import io.dropwizard.lifecycle.JettyManaged;
import io.dropwizard.lifecycle.Managed;
import io.dropwizard.lifecycle.ServerLifecycleListener;
import io.dropwizard.lifecycle.setup.LifecycleEnvironment;
import io.dropwizard.testing.junit5.DropwizardAppExtension;
import lombok.experimental.UtilityClass;
import org.eclipse.jetty.util.component.LifeCycle;
import org.glassfish.jersey.internal.inject.InstanceBinding;
import org.kiwiproject.reflect.KiwiReflection;

import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * Test utility for testing Dropwizard apps when using {@link DropwizardAppExtension}.
 */
@UtilityClass
public class DropwizardAppTests {

    /**
     * Dropwizard does not expose this as a public class, so we have to resort to reflection and hackery to get at
     * this class. It is, unfortunately, the class used when you register a {@link ServerLifecycleListener} in
     * a Dropwizard application, so we need to access it somehow.
     */
    @VisibleForTesting
    static final String DROPWIZARD_PRIVATE_SERVER_LISTENER_CLASS_NAME =
            "io.dropwizard.lifecycle.setup.LifecycleEnvironment$ServerListener";

    // Resources

    /**
     * Find the resource classes registered in the given Dropwizard app.
     *
     * @param app the DropwizardAppExtension containing the Dropwizard app being tested
     * @param  the configuration type
     * @return list containing registered resource types
     */
    public static  List> registeredResourceClassesOf(DropwizardAppExtension app) {
        return registeredResourceClassesOf(app.getEnvironment().jersey());
    }

    /**
     * Find the resource classes registered in the given {@link JerseyEnvironment}.
     *
     * @param jersey the {@link JerseyEnvironment} associated with the Dropwizard app being tested
     * @return list containing registered resource types
     */
    public static List> registeredResourceClassesOf(JerseyEnvironment jersey) {
        return registeredResourceObjectsOf(jersey)
                .stream()
                .map(Object::getClass)
                .collect(toList());
    }

    /**
     * Find the resource objects registered in the given Dropwizard app.
     *
     * @param app the DropwizardAppExtension containing the Dropwizard app being tested
     * @param  the configuration type
     * @return list containing registered resource objects
     */
    public static  Set registeredResourceObjectsOf(DropwizardAppExtension app) {
        return registeredResourceObjectsOf(app.getEnvironment().jersey());
    }

    /**
     * Find the resource objects registered in the given {@link JerseyEnvironment}.
     *
     * @param jersey the {@link JerseyEnvironment} associated with the Dropwizard app being tested
     * @return list containing registered resource objects
     * @implNote Dropwizard 2.0 added one more layer of indirection for resource endpoint classes; they are now
     * wrapper inside a {@link DropwizardResourceConfig.SpecificBinder} so this method needs to unwrap those and
     * add the wrapped objects to the returned set. To accomplish that, it has to perform some nastiness with the
     * {@link org.glassfish.jersey.internal.inject.Binding} returned by
     * {@link DropwizardResourceConfig.SpecificBinder#getBindings()}, specifically it must cast to
     * {@link InstanceBinding} to be able to get the "service" which is the actual object we want. This is
     * extremely brittle and we're only leaving it like this because this is a test helper class. Note also
     * that the set returned by this class contains more objects that is returned by
     * {@link DropwizardResourceConfig#getInstances()} since we do not remove the wrapped classes.
     */
    public static Set registeredResourceObjectsOf(JerseyEnvironment jersey) {
        var instances = jersey.getResourceConfig().getInstances();

        var wrappedResourceObjects = instances.stream()
                .filter(o -> o instanceof DropwizardResourceConfig.SpecificBinder)
                .map(DropwizardResourceConfig.SpecificBinder.class::cast)
                .flatMap(specificBinder -> specificBinder.getBindings().stream())
                .filter(InstanceBinding.class::isInstance)
                .map(InstanceBinding.class::cast)
                .map(InstanceBinding::getService)
                .collect(toSet());

        return Sets.union(instances, wrappedResourceObjects);
    }

    // Health checks

    /**
     * Find the health check names registered in the given Dropwizard app.
     *
     * @param app the DropwizardAppExtension containing the Dropwizard app being tested
     * @param  the configuration type
     * @return a set containing the health check names
     */
    public static  SortedSet healthCheckNamesOf(DropwizardAppExtension app) {
        return app.getEnvironment().healthChecks().getNames();
    }

    // Lifecycle & Managed

    /**
     * Finds the {@link Managed} objects registered in the given Dropwizard app.
     *
     * @param app the DropwizardAppExtension containing the Dropwizard app being tested
     * @param  the configuration type
     * @return list of Managed objects
     */
    public static  List managedObjectsOf(DropwizardAppExtension app) {
        return managedObjectsOf(app.getEnvironment().lifecycle());
    }

    /**
     * Finds the {@link Managed} objects registered in the given {@link LifecycleEnvironment}.
     *
     * @param lifecycle the {@link LifecycleEnvironment} associated with the Dropwizard app being tested
     * @return list of Managed objects
     */
    public static List managedObjectsOf(LifecycleEnvironment lifecycle) {
        return managedObjectStreamOf(lifecycle).collect(toList());
    }

    /**
     * Finds {@link Managed} objects registered in the given Dropwizard app and having the given type.
     *
     * @param app  the DropwizardAppExtension containing the Dropwizard app being tested
     * @param type the type of object to find
     * @param   the configuration type
     * @param   the object type
     * @return list of managed objects
     */
    public static  List managedObjectsOfType(DropwizardAppExtension app,
                                                                            Class type) {
        return managedObjectsOfType(app.getEnvironment().lifecycle(), type);
    }

    /**
     * Finds {@link Managed} objects registered in the given {@link LifecycleEnvironment} and having the given type.
     *
     * @param lifecycle the {@link LifecycleEnvironment} associated with the Dropwizard app being tested
     * @param type      the type of object to find
     * @param        the object type
     * @return list of managed objects of type T
     */
    @SuppressWarnings("unchecked")
    public static  List managedObjectsOfType(LifecycleEnvironment lifecycle, Class type) {
        return (List) managedObjectsOf(lifecycle, managed -> type.isAssignableFrom(managed.getClass()));
    }

    /**
     * Finds {@link Managed} objects registered in the given Dropwizard app that match the given predicate.
     *
     * @param app       the DropwizardAppExtension containing the Dropwizard app being tested
     * @param predicate the predicate to match
     * @param        the configuration type
     * @return list of Managed objects
     */
    public static  List managedObjectsOf(DropwizardAppExtension app,
                                                                           Predicate predicate) {
        return managedObjectsOf(app.getEnvironment().lifecycle(), predicate);
    }

    /**
     * Finds {@link Managed} objects registered in the given {@link LifecycleEnvironment} that match the
     * given predicate.
     *
     * @param lifecycle the {@link LifecycleEnvironment} associated with the Dropwizard app being tested
     * @param predicate the predicate to match
     * @return list of Managed objects
     */
    public static List managedObjectsOf(LifecycleEnvironment lifecycle, Predicate predicate) {
        return managedObjectStreamOf(lifecycle)
                .filter(predicate)
                .collect(toList());
    }

    /**
     * Finds the first {@link Managed} object registered in the given Dropwizard app having the given type.
     *
     * @param app  the DropwizardAppExtension containing the Dropwizard app being tested
     * @param type the type of object to find
     * @param   the configuration type
     * @param   the object type
     * @return optional that might contain the first Managed that is found
     */
    public static  Optional firstManagedObjectOfType(DropwizardAppExtension app,
                                                                                    Class type) {
        return firstManagedObjectOfType(app.getEnvironment().lifecycle(), type);
    }

    /**
     * Finds the first {@link Managed} object registered in the given {@link LifecycleEnvironment} having the
     * given type.
     *
     * @param lifecycle the {@link LifecycleEnvironment} associated with the Dropwizard app being tested
     * @param type      the type of object to find
     * @param        the object type
     * @return optional that might contain the first Managed that is found
     */
    @SuppressWarnings("unchecked")
    public static  Optional firstManagedObjectOfType(LifecycleEnvironment lifecycle, Class type) {
        return (Optional) firstManagedObject(lifecycle, managed -> type.isAssignableFrom(managed.getClass()));
    }

    /**
     * Finds the first {@link Managed} object registered in the given Dropwizard app matching the given predicate.
     *
     * @param app       the DropwizardAppExtension containing the Dropwizard app being tested
     * @param predicate the predicate to match
     * @param        the configuration type
     * @return optional that might contain the first Managed that is found
     */
    public static  Optional firstManagedObject(DropwizardAppExtension app,
                                                                                 Predicate predicate) {
        return firstManagedObject(app.getEnvironment().lifecycle(), predicate);
    }

    /**
     * Finds the first {@link Managed} object registered in the given {@link LifecycleEnvironment} matching the
     * given predicate.
     *
     * @param lifecycle the {@link LifecycleEnvironment} associated with the Dropwizard app being tested
     * @param predicate the predicate to match
     * @return optional that might contain the first Managed that is found
     */
    public static Optional firstManagedObject(LifecycleEnvironment lifecycle,
                                                       Predicate predicate) {
        return managedObjectStreamOf(lifecycle)
                .filter(predicate)
                .findFirst();
    }

    /**
     * Streams {@link Managed} objects registered in the given Dropwizard app.
     *
     * @param app the DropwizardAppExtension containing the Dropwizard app being tested
     * @param  the configuration type
     * @return stream of Managed objects
     */
    public static  Stream managedObjectStreamOf(DropwizardAppExtension app) {
        return managedObjectStreamOf(app.getEnvironment().lifecycle());
    }

    /**
     * Streams {@link Managed} objects registered in the given {@link LifecycleEnvironment}.
     *
     * @param lifecycle the {@link LifecycleEnvironment} associated with the Dropwizard app being tested
     * @return stream of Managed objects
     */
    public static Stream managedObjectStreamOf(LifecycleEnvironment lifecycle) {
        return lifeCycleStreamOf(lifecycle)
                .map(JettyManaged.class::cast)
                .map(JettyManaged::getManaged);
    }

    /**
     * Finds {@link LifeCycle} objects registered in the given Dropwizard app.
     *
     * @param app the DropwizardAppExtension containing the Dropwizard app being tested
     * @param  the configuration type
     * @return list of LifeCycle objects
     */
    public static  List lifeCycleObjectsOf(DropwizardAppExtension app) {
        return lifeCycleObjectsOf(app.getEnvironment().lifecycle());
    }

    /**
     * Finds {@link LifeCycle} objects registered in the given {@link LifecycleEnvironment}.
     *
     * @param lifecycle the {@link LifecycleEnvironment} associated with the Dropwizard app being tested
     * @return list of LifeCycle objects
     */
    public static List lifeCycleObjectsOf(LifecycleEnvironment lifecycle) {
        return lifecycle.getManagedObjects();
    }

    /**
     * Streams {@link LifeCycle} objects registered in the given Dropwizard app.
     *
     * @param app the DropwizardAppExtension containing the Dropwizard app being tested
     * @param  the configuration type
     * @return stream of LifeCycle objects
     */
    public static  Stream lifeCycleStreamOf(DropwizardAppExtension app) {
        return lifeCycleStreamOf(app.getEnvironment().lifecycle());
    }

    /**
     * Streams {@link LifeCycle} objects registered in the given {@link LifecycleEnvironment}.
     *
     * @param lifecycle the {@link LifecycleEnvironment} associated with the Dropwizard app being tested
     * @return stream of LifeCycle objects
     */
    public static Stream lifeCycleStreamOf(LifecycleEnvironment lifecycle) {
        return lifecycle.getManagedObjects().stream();
    }

    /**
     * Finds {@link LifeCycle.Listener} classes registered with the given Dropwizard app.
     *
     * @param app the DropwizardAppExtension containing the Dropwizard app being tested
     * @param  the configuration type
     * @return list of lifecycle listener classes
     */
    @Beta
    public static  List> lifeCycleListenerClassesOf(
            DropwizardAppExtension app) {

        return lifeCycleListenerClassesOf(app.getEnvironment().lifecycle());
    }

    /**
     * Finds {@link LifeCycle.Listener} classes registered with the given {@link LifecycleEnvironment}.
     *
     * @param lifecycle the {@link LifecycleEnvironment} associated with the Dropwizard app being tested
     * @return list of lifecycle listener classes
     */
    @Beta
    public static List> lifeCycleListenerClassesOf(LifecycleEnvironment lifecycle) {
        return lifeCycleListenersOf(lifecycle).stream()
                .map(Object::getClass)
                .collect(toList());
    }

    /**
     * Finds {@link LifeCycle.Listener} objects registered with the given Dropwizard app.
     *
     * @param app the DropwizardAppExtension containing the Dropwizard app being tested
     * @param  the configuration type
     * @return list of lifecycle listener objects
     */
    @Beta
    public static  List lifeCycleListenersOf(
            DropwizardAppExtension app) {

        return lifeCycleListenersOf(app.getEnvironment().lifecycle());
    }

    /**
     * Finds {@link LifeCycle.Listener} objects registered with the given {@link LifecycleEnvironment}.
     *
     * @param lifecycle the {@link LifecycleEnvironment} associated with the Dropwizard app being tested
     * @return list of lifecycle listener objects
     */
    @SuppressWarnings("unchecked")
    @Beta
    public static List lifeCycleListenersOf(LifecycleEnvironment lifecycle) {
        return (List) KiwiReflection.getFieldValue(lifecycle, "lifecycleListeners");
    }

    /**
     * Finds {@link ServerLifecycleListener} objects registered with the given Dropwizard app.
     *
     * @param app the DropwizardAppExtension containing the Dropwizard app being tested
     * @param  the configuration type
     * @return list of server lifecycle listener objects
     */
    @Beta
    public static  List serverLifecycleListenersOf(
            DropwizardAppExtension app) {

        return serverLifecycleListenersOf(app.getEnvironment().lifecycle());
    }

    /**
     * Finds {@link ServerLifecycleListener} objects registered with the given {@link LifecycleEnvironment}.
     *
     * @param lifecycle the {@link LifecycleEnvironment} associated with the Dropwizard app being tested
     * @return list of server lifecycle listener objects
     */
    @Beta
    public static List serverLifecycleListenersOf(LifecycleEnvironment lifecycle) {
        var serverListeners = lifeCycleListenersOf(lifecycle).stream()
                .filter(listener ->
                        listener.getClass().getName().equals(DROPWIZARD_PRIVATE_SERVER_LISTENER_CLASS_NAME))
                .collect(toList());

        return serverListeners.stream()
                .map(listener -> KiwiReflection.getFieldValue(listener, "listener"))
                .map(ServerLifecycleListener.class::cast)
                .collect(toList());
    }
}