Java tutorial
/* * 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(); } } }