org.springframework.boot.test.context.assertj.ApplicationContextAssert.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.boot.test.context.assertj.ApplicationContextAssert.java

Source

/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.test.context.assertj;

import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;

import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractObjectArrayAssert;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.AbstractThrowableAssert;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.MapAssert;
import org.assertj.core.error.BasicErrorMessageFactory;

import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.Assert;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to an
 * {@link ApplicationContext}.
 *
 * @param <C> the application context type
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @since 2.0.0
 * @see ApplicationContextRunner
 * @see AssertableApplicationContext
 */
public class ApplicationContextAssert<C extends ApplicationContext>
        extends AbstractAssert<ApplicationContextAssert<C>, C> {

    private final Throwable startupFailure;

    /**
     * Create a new {@link ApplicationContextAssert} instance.
     * @param applicationContext the source application context
     * @param startupFailure the startup failure or {@code null}
     */
    ApplicationContextAssert(C applicationContext, Throwable startupFailure) {
        super(applicationContext, ApplicationContextAssert.class);
        Assert.notNull(applicationContext, "ApplicationContext must not be null");
        this.startupFailure = startupFailure;
    }

    /**
     * Verifies that the application context contains a bean with the given name.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).hasBean("fooBean"); </pre>
     * @param name the name of the bean
     * @return {@code this} assertion object.
     * @throws AssertionError if the application context did not start
     * @throws AssertionError if the application context does not contain a bean with the
     * given name
     */
    public ApplicationContextAssert<C> hasBean(String name) {
        if (this.startupFailure != null) {
            throwAssertionError(contextFailedToStartWhenExpecting("to have bean named:%n <%s>", name));
        }
        if (findBean(name) == null) {
            throwAssertionError(new BasicErrorMessageFactory(
                    "%nExpecting:%n <%s>%nto have bean named:%n <%s>%nbut found no such bean",
                    getApplicationContext(), name));
        }
        return this;
    }

    /**
     * Verifies that the application context (or ancestors) contains a single bean with
     * the given type.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).hasSingleBean(Foo.class); </pre>
     * @param type the bean type
     * @return {@code this} assertion object.
     * @throws AssertionError if the application context did not start
     * @throws AssertionError if the application context does no beans of the given type
     * @throws AssertionError if the application context contains multiple beans of the
     * given type
     */
    public ApplicationContextAssert<C> hasSingleBean(Class<?> type) {
        return hasSingleBean(type, Scope.INCLUDE_ANCESTORS);
    }

    /**
     * Verifies that the application context contains a single bean with the given type.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).hasSingleBean(Foo.class, Scope.NO_ANCESTORS); </pre>
     * @param type the bean type
     * @param scope the scope of the assertion
     * @return {@code this} assertion object.
     * @throws AssertionError if the application context did not start
     * @throws AssertionError if the application context does no beans of the given type
     * @throws AssertionError if the application context contains multiple beans of the
     * given type
     */
    public ApplicationContextAssert<C> hasSingleBean(Class<?> type, Scope scope) {
        Assert.notNull(scope, "Scope must not be null");
        if (this.startupFailure != null) {
            throwAssertionError(contextFailedToStartWhenExpecting("to have a single bean of type:%n <%s>", type));
        }
        String[] names = scope.getBeanNamesForType(getApplicationContext(), type);
        if (names.length == 0) {
            throwAssertionError(new BasicErrorMessageFactory(
                    "%nExpecting:%n <%s>%nto have a single bean of type:%n <%s>%nbut found no beans of that type",
                    getApplicationContext(), type));
        }
        if (names.length > 1) {
            throwAssertionError(new BasicErrorMessageFactory(
                    "%nExpecting:%n <%s>%nto have a single bean of type:%n <%s>%nbut found:%n <%s>",
                    getApplicationContext(), type, names));
        }
        return this;
    }

    /**
     * Verifies that the application context (or ancestors) does not contain any beans of
     * the given type.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).doesNotHaveBean(Foo.class); </pre>
     * @param type the bean type
     * @return {@code this} assertion object.
     * @throws AssertionError if the application context did not start
     * @throws AssertionError if the application context contains any beans of the given
     * type
     */
    public ApplicationContextAssert<C> doesNotHaveBean(Class<?> type) {
        return doesNotHaveBean(type, Scope.INCLUDE_ANCESTORS);
    }

    /**
     * Verifies that the application context does not contain any beans of the given type.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).doesNotHaveBean(Foo.class, Scope.NO_ANCESTORS); </pre>
     * @param type the bean type
     * @param scope the scope of the assertion
     * @return {@code this} assertion object.
     * @throws AssertionError if the application context did not start
     * @throws AssertionError if the application context contains any beans of the given
     * type
     */
    public ApplicationContextAssert<C> doesNotHaveBean(Class<?> type, Scope scope) {
        Assert.notNull(scope, "Scope must not be null");
        if (this.startupFailure != null) {
            throwAssertionError(contextFailedToStartWhenExpecting("not to have any beans of type:%n <%s>", type));
        }
        String[] names = scope.getBeanNamesForType(getApplicationContext(), type);
        if (names.length > 0) {
            throwAssertionError(new BasicErrorMessageFactory(
                    "%nExpecting:%n <%s>%nnot to have a beans of type:%n <%s>%nbut found:%n <%s>",
                    getApplicationContext(), type, names));
        }
        return this;
    }

    /**
     * Verifies that the application context does not contain a beans of the given name.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).doesNotHaveBean("fooBean"); </pre>
     * @param name the name of the bean
     * @return {@code this} assertion object.
     * @throws AssertionError if the application context did not start
     * @throws AssertionError if the application context contains a beans of the given
     * name
     */
    public ApplicationContextAssert<C> doesNotHaveBean(String name) {
        if (this.startupFailure != null) {
            throwAssertionError(contextFailedToStartWhenExpecting("not to have any beans of name:%n <%s>", name));
        }
        try {
            Object bean = getApplicationContext().getBean(name);
            throwAssertionError(new BasicErrorMessageFactory(
                    "%nExpecting:%n <%s>%nnot to have a bean of name:%n <%s>%nbut found:%n <%s>",
                    getApplicationContext(), name, bean));
        } catch (NoSuchBeanDefinitionException ex) {
        }
        return this;
    }

    /**
     * Obtain the beans names of the given type from the application context, the names
     * becoming the object array under test.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).getBeanNames(Foo.class).containsOnly("fooBean"); </pre>
     * @param <T> the bean type
     * @param type the bean type
     * @return array assertions for the bean names
     * @throws AssertionError if the application context did not start
     */
    public <T> AbstractObjectArrayAssert<?, String> getBeanNames(Class<T> type) {
        if (this.startupFailure != null) {
            throwAssertionError(contextFailedToStartWhenExpecting("to get beans names with type:%n <%s>", type));
        }
        return Assertions.assertThat(getApplicationContext().getBeanNamesForType(type))
                .as("Bean names of type <%s> from <%s>", type, getApplicationContext());
    }

    /**
     * Obtain a single bean of the given type from the application context (or ancestors),
     * the bean becoming the object under test. If no beans of the specified type can be
     * found an assert on {@code null} is returned.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).getBean(Foo.class).isInstanceOf(DefaultFoo.class);
     * assertThat(context).getBean(Bar.class).isNull();</pre>
     * @param <T> the bean type
     * @param type the bean type
     * @return bean assertions for the bean, or an assert on {@code null} if the no bean
     * is found
     * @throws AssertionError if the application context did not start
     * @throws AssertionError if the application context contains multiple beans of the
     * given type
     */
    public <T> AbstractObjectAssert<?, T> getBean(Class<T> type) {
        return getBean(type, Scope.INCLUDE_ANCESTORS);
    }

    /**
     * Obtain a single bean of the given type from the application context, the bean
     * becoming the object under test. If no beans of the specified type can be found an
     * assert on {@code null} is returned.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).getBean(Foo.class, Scope.NO_ANCESTORS).isInstanceOf(DefaultFoo.class);
     * assertThat(context).getBean(Bar.class, Scope.NO_ANCESTORS).isNull();</pre>
     * @param <T> the bean type
     * @param type the bean type
     * @param scope the scope of the assertion
     * @return bean assertions for the bean, or an assert on {@code null} if the no bean
     * is found
     * @throws AssertionError if the application context did not start
     * @throws AssertionError if the application context contains multiple beans of the
     * given type
     */
    public <T> AbstractObjectAssert<?, T> getBean(Class<T> type, Scope scope) {
        Assert.notNull(scope, "Scope must not be null");
        if (this.startupFailure != null) {
            throwAssertionError(contextFailedToStartWhenExpecting("to contain bean of type:%n <%s>", type));
        }
        String[] names = scope.getBeanNamesForType(getApplicationContext(), type);
        String name = (names.length > 0) ? getPrimary(names, scope) : null;
        if (names.length > 1 && name == null) {
            throwAssertionError(new BasicErrorMessageFactory(
                    "%nExpecting:%n <%s>%nsingle bean of type:%n <%s>%nbut found:%n <%s>", getApplicationContext(),
                    type, names));
        }
        T bean = (name != null) ? getApplicationContext().getBean(name, type) : null;
        return Assertions.assertThat(bean).as("Bean of type <%s> from <%s>", type, getApplicationContext());
    }

    private String getPrimary(String[] names, Scope scope) {
        if (names.length == 1) {
            return names[0];
        }
        String primary = null;
        for (String name : names) {
            if (isPrimary(name, scope)) {
                if (primary != null) {
                    return null;
                }
                primary = name;
            }
        }
        return primary;
    }

    private boolean isPrimary(String name, Scope scope) {
        ApplicationContext context = getApplicationContext();
        while (context != null) {
            if (context instanceof ConfigurableApplicationContext) {
                ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) context)
                        .getBeanFactory();
                if (factory.containsBean(name) && factory.getMergedBeanDefinition(name).isPrimary()) {
                    return true;
                }
            }
            context = (scope != Scope.NO_ANCESTORS) ? context.getParent() : null;
        }
        return false;
    }

    /**
     * Obtain a single bean of the given name from the application context, the bean
     * becoming the object under test. If no bean of the specified name can be found an
     * assert on {@code null} is returned.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).getBean("foo").isInstanceOf(Foo.class);
     * assertThat(context).getBean("foo").isNull();</pre>
     * @param name the name of the bean
     * @return bean assertions for the bean, or an assert on {@code null} if the no bean
     * is found
     * @throws AssertionError if the application context did not start
     */
    public AbstractObjectAssert<?, Object> getBean(String name) {
        if (this.startupFailure != null) {
            throwAssertionError(contextFailedToStartWhenExpecting("to contain a bean of name:%n <%s>", name));
        }
        Object bean = findBean(name);
        return Assertions.assertThat(bean).as("Bean of name <%s> from <%s>", name, getApplicationContext());
    }

    /**
     * Obtain a single bean of the given name and type from the application context, the
     * bean becoming the object under test. If no bean of the specified name can be found
     * an assert on {@code null} is returned.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).getBean("foo", Foo.class).isInstanceOf(DefaultFoo.class);
     * assertThat(context).getBean("foo", Foo.class).isNull();</pre>
     * @param <T> the bean type
     * @param name the name of the bean
     * @param type the bean type
     * @return bean assertions for the bean, or an assert on {@code null} if the no bean
     * is found
     * @throws AssertionError if the application context did not start
     * @throws AssertionError if the application context contains a bean with the given
     * name but a different type
     */
    @SuppressWarnings("unchecked")
    public <T> AbstractObjectAssert<?, T> getBean(String name, Class<T> type) {
        if (this.startupFailure != null) {
            throwAssertionError(
                    contextFailedToStartWhenExpecting("to contain a bean of name:%n <%s> (%s)", name, type));
        }
        Object bean = findBean(name);
        if (bean != null && type != null && !type.isInstance(bean)) {
            throwAssertionError(new BasicErrorMessageFactory(
                    "%nExpecting:%n <%s>%nto contain a bean of name:%n <%s> (%s)%nbut found:%n <%s> of type <%s>",
                    getApplicationContext(), name, type, bean, bean.getClass()));
        }
        return Assertions.assertThat((T) bean).as("Bean of name <%s> and type <%s> from <%s>", name, type,
                getApplicationContext());
    }

    private Object findBean(String name) {
        try {
            return getApplicationContext().getBean(name);
        } catch (NoSuchBeanDefinitionException ex) {
            return null;
        }
    }

    /**
     * Obtain a map bean names and instances of the given type from the application
     * context (or ancestors), the map becoming the object under test. If no bean of the
     * specified type can be found an assert on an empty {@code map} is returned.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).getBeans(Foo.class).containsKey("foo");
     * </pre>
     * @param <T> the bean type
     * @param type the bean type
     * @return bean assertions for the beans, or an assert on an empty {@code map} if the
     * no beans are found
     * @throws AssertionError if the application context did not start
     */
    public <T> MapAssert<String, T> getBeans(Class<T> type) {
        return getBeans(type, Scope.INCLUDE_ANCESTORS);
    }

    /**
     * Obtain a map bean names and instances of the given type from the application
     * context, the map becoming the object under test. If no bean of the specified type
     * can be found an assert on an empty {@code map} is returned.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).getBeans(Foo.class, Scope.NO_ANCESTORS).containsKey("foo");
     * </pre>
     * @param <T> the bean type
     * @param type the bean type
     * @param scope the scope of the assertion
     * @return bean assertions for the beans, or an assert on an empty {@code map} if the
     * no beans are found
     * @throws AssertionError if the application context did not start
     */
    public <T> MapAssert<String, T> getBeans(Class<T> type, Scope scope) {
        Assert.notNull(scope, "Scope must not be null");
        if (this.startupFailure != null) {
            throwAssertionError(contextFailedToStartWhenExpecting("to get beans of type:%n <%s>", type));
        }
        return Assertions.assertThat(scope.getBeansOfType(getApplicationContext(), type))
                .as("Beans of type <%s> from <%s>", type, getApplicationContext());
    }

    /**
     * Obtain the failure that stopped the application context from running, the failure
     * becoming the object under test.
     * <p>
     * Example: <pre class="code">
     * assertThat(context).getFailure().containsMessage("missing bean");
     * </pre>
     * @return assertions on the cause of the failure
     * @throws AssertionError if the application context started without a failure
     */
    public AbstractThrowableAssert<?, ? extends Throwable> getFailure() {
        hasFailed();
        return assertThat(this.startupFailure);
    }

    /**
     * Verifies that the application has failed to start.
     * <p>
     * Example: <pre class="code"> assertThat(context).hasFailed();
     * </pre>
     * @return {@code this} assertion object.
     * @throws AssertionError if the application context started without a failure
     */
    public ApplicationContextAssert<C> hasFailed() {
        if (this.startupFailure == null) {
            throwAssertionError(new BasicErrorMessageFactory(
                    "%nExpecting:%n <%s>%nto have failed%nbut context started successfully",
                    getApplicationContext()));
        }
        return this;
    }

    /**
     * Verifies that the application has not failed to start.
     * <p>
     * Example: <pre class="code"> assertThat(context).hasNotFailed();
     * </pre>
     * @return {@code this} assertion object.
     * @throws AssertionError if the application context failed to start
     */
    public ApplicationContextAssert<C> hasNotFailed() {
        if (this.startupFailure != null) {
            throwAssertionError(contextFailedToStartWhenExpecting("to have not failed"));
        }
        return this;
    }

    protected final C getApplicationContext() {
        return this.actual;
    }

    protected final Throwable getStartupFailure() {
        return this.startupFailure;
    }

    private ContextFailedToStart<C> contextFailedToStartWhenExpecting(String expectationFormat,
            Object... arguments) {
        return new ContextFailedToStart<>(getApplicationContext(), this.startupFailure, expectationFormat,
                arguments);
    }

    /**
     * The scope of an assertion.
     */
    public enum Scope {

        /**
         * Limited to the current context.
         */
        NO_ANCESTORS {

            @Override
            String[] getBeanNamesForType(ApplicationContext applicationContext, Class<?> type) {
                return applicationContext.getBeanNamesForType(type);
            }

            @Override
            <T> Map<String, T> getBeansOfType(ApplicationContext applicationContext, Class<T> type) {
                return applicationContext.getBeansOfType(type);
            }

        },

        /**
         * Consider the ancestor contexts as well as the current context.
         */
        INCLUDE_ANCESTORS {

            @Override
            String[] getBeanNamesForType(ApplicationContext applicationContext, Class<?> type) {
                return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, type);
            }

            @Override
            <T> Map<String, T> getBeansOfType(ApplicationContext applicationContext, Class<T> type) {
                return BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, type);
            }

        };

        abstract String[] getBeanNamesForType(ApplicationContext applicationContext, Class<?> type);

        abstract <T> Map<String, T> getBeansOfType(ApplicationContext applicationContext, Class<T> type);

    }

    private static final class ContextFailedToStart<C extends ApplicationContext> extends BasicErrorMessageFactory {

        private ContextFailedToStart(C context, Throwable ex, String expectationFormat, Object... arguments) {
            super("%nExpecting:%n <%s>%n" + expectationFormat + ":%nbut context failed to start:%n%s",
                    combineArguments(context.toString(), ex, arguments));
        }

        private static Object[] combineArguments(String context, Throwable ex, Object[] arguments) {
            Object[] combinedArguments = new Object[arguments.length + 2];
            combinedArguments[0] = unquotedString(context);
            System.arraycopy(arguments, 0, combinedArguments, 1, arguments.length);
            combinedArguments[combinedArguments.length - 1] = unquotedString(getIndentedStackTraceAsString(ex));
            return combinedArguments;
        }

        private static String getIndentedStackTraceAsString(Throwable ex) {
            String stackTrace = getStackTraceAsString(ex);
            return indent(stackTrace);
        }

        private static String getStackTraceAsString(Throwable ex) {
            StringWriter writer = new StringWriter();
            PrintWriter printer = new PrintWriter(writer);
            ex.printStackTrace(printer);
            return writer.toString();
        }

        private static String indent(String input) {
            BufferedReader reader = new BufferedReader(new StringReader(input));
            StringWriter writer = new StringWriter();
            PrintWriter printer = new PrintWriter(writer);
            reader.lines().forEach((line) -> {
                printer.print(" ");
                printer.println(line);
            });
            return writer.toString();
        }

    }

}