diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssert.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssert.java index 8b6817a9b2..7105917486 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssert.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssert.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -20,6 +20,7 @@ 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; @@ -29,6 +30,7 @@ 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.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; @@ -88,7 +90,8 @@ public class ApplicationContextAssert } /** - * Verifies that the application context contains a single bean with the given type. + * Verifies that the application context (or ancestors) contains a single bean with + * the given type. *

* Example:

 	 * assertThat(context).hasSingleBean(Foo.class); 
@@ -100,11 +103,29 @@ public class ApplicationContextAssert * given type */ public ApplicationContextAssert hasSingleBean(Class type) { + return hasSingleBean(type, Scope.INCLUDE_ANCESTORS); + } + + /** + * Verifies that the application context contains a single bean with the given type. + *

+ * Example:

+	 * assertThat(context).hasSingleBean(Foo.class); 
+ * @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 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 = getApplicationContext().getBeanNamesForType(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", @@ -119,7 +140,8 @@ public class ApplicationContextAssert } /** - * Verifies that the application context does not contain any beans of the given type. + * Verifies that the application context (or ancestors) does not contain any beans of + * the given type. *

* Example:

 	 * assertThat(context).doesNotHaveBean(Foo.class); 
@@ -130,11 +152,28 @@ public class ApplicationContextAssert * type */ public ApplicationContextAssert doesNotHaveBean(Class type) { + return doesNotHaveBean(type, Scope.INCLUDE_ANCESTORS); + } + + /** + * Verifies that the application context does not contain any beans of the given type. + *

+ * Example:

+	 * assertThat(context).doesNotHaveBean(Foo.class); 
+ * @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 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 = getApplicationContext().getBeanNamesForType(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>", @@ -191,9 +230,9 @@ public class ApplicationContextAssert } /** - * 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. + * 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. *

* Example:

 	 * assertThat(context).getBean(Foo.class).isInstanceOf(DefaultFoo.class);
@@ -207,11 +246,33 @@ public class ApplicationContextAssert
 	 * given type
 	 */
 	public  AbstractObjectAssert getBean(Class 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.
+	 * 

+ * Example:

+	 * assertThat(context).getBean(Foo.class).isInstanceOf(DefaultFoo.class);
+	 * assertThat(context).getBean(Bar.class).isNull();
+ * @param 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 AbstractObjectAssert getBean(Class 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 = getApplicationContext().getBeanNamesForType(type); + String[] names = scope.getBeanNamesForType(getApplicationContext(), type); if (names.length > 1) { throwAssertionError(new BasicErrorMessageFactory( "%nExpecting:%n <%s>%nsingle bean of type:%n <%s>%nbut found:%n <%s>", @@ -291,8 +352,8 @@ public class ApplicationContextAssert /** * 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. + * 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. *

* Example:

 	 * assertThat(context).getBeans(Foo.class).containsKey("foo");
@@ -304,11 +365,31 @@ public class ApplicationContextAssert
 	 * @throws AssertionError if the application context did not start
 	 */
 	public  MapAssert getBeans(Class 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.
+	 * 

+ * Example:

+	 * assertThat(context).getBeans(Foo.class).containsKey("foo");
+	 * 
+ * @param 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 MapAssert getBeans(Class 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(getApplicationContext().getBeansOfType(type)) + return Assertions.assertThat(scope.getBeansOfType(getApplicationContext(), type)) .as("Beans of type <%s> from <%s>", type, getApplicationContext()); } @@ -373,6 +454,59 @@ public class ApplicationContextAssert 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 + Map getBeansOfType(ApplicationContext applicationContext, + Class 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 + Map getBeansOfType(ApplicationContext applicationContext, + Class type) { + return BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, + type); + } + + }; + + abstract String[] getBeanNamesForType(ApplicationContext applicationContext, + Class type); + + abstract Map getBeansOfType(ApplicationContext applicationContext, + Class type); + + } + private static final class ContextFailedToStart extends BasicErrorMessageFactory { diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertTests.java index 60973dd3ee..0b9629acd6 100644 --- a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertTests.java +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -16,10 +16,13 @@ package org.springframework.boot.test.context.assertj; +import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.boot.test.context.assertj.ApplicationContextAssert.Scope; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.StaticApplicationContext; @@ -36,10 +39,25 @@ public class ApplicationContextAssertTests { @Rule public ExpectedException thrown = ExpectedException.none(); - private StaticApplicationContext context = new StaticApplicationContext(); + private StaticApplicationContext parent; + + private StaticApplicationContext context; private RuntimeException failure = new RuntimeException(); + @Before + public void setup() { + this.parent = new StaticApplicationContext(); + this.context = new StaticApplicationContext(); + this.context.setParent(this.parent); + } + + @After + public void cleanup() { + this.context.close(); + this.parent.close(); + } + @Test public void createWhenApplicationContextIsNullShouldThrowException() { this.thrown.expect(IllegalArgumentException.class); @@ -110,6 +128,22 @@ public class ApplicationContextAssertTests { assertThat(getAssert(this.failure)).hasSingleBean(Foo.class); } + @Test + public void hasSingleBeanWhenInParentShouldFail() { + this.parent.registerSingleton("foo", Foo.class); + this.context.registerSingleton("bar", Foo.class); + this.thrown.expect(AssertionError.class); + this.thrown.expectMessage("but found:"); + assertThat(getAssert(this.context)).hasSingleBean(Foo.class); + } + + @Test + public void hasSingleBeanWithLimitedScopeWhenInParentShouldPass() { + this.parent.registerSingleton("foo", Foo.class); + this.context.registerSingleton("bar", Foo.class); + assertThat(getAssert(this.context)).hasSingleBean(Foo.class, Scope.NO_ANCESTORS); + } + @Test public void doesNotHaveBeanOfTypeWhenHasNoBeanOfTypeShouldPass() { assertThat(getAssert(this.context)).doesNotHaveBean(Foo.class); @@ -132,6 +166,21 @@ public class ApplicationContextAssertTests { assertThat(getAssert(this.failure)).doesNotHaveBean(Foo.class); } + @Test + public void doesNotHaveBeanOfTypeWhenInParentShouldFail() { + this.thrown.expect(AssertionError.class); + this.thrown.expectMessage("but found"); + this.parent.registerSingleton("foo", Foo.class); + assertThat(getAssert(this.context)).doesNotHaveBean(Foo.class); + } + + @Test + public void doesNotHaveBeanOfTypeWithLimitedScopeWhenInParentShouldPass() { + this.parent.registerSingleton("foo", Foo.class); + assertThat(getAssert(this.context)).doesNotHaveBean(Foo.class, + Scope.NO_ANCESTORS); + } + @Test public void doesNotHaveBeanOfNameWhenHasNoBeanOfTypeShouldPass() { assertThat(getAssert(this.context)).doesNotHaveBean("foo"); @@ -204,6 +253,36 @@ public class ApplicationContextAssertTests { assertThat(getAssert(this.failure)).getBean(Foo.class); } + @Test + public void getBeanOfTypeWhenInParentShouldReturnBeanAssert() { + this.parent.registerSingleton("foo", Foo.class); + assertThat(getAssert(this.context)).getBean(Foo.class).isNotNull(); + } + + @Test + public void getBeanOfTypeWhenInParentWithLimtedScopeShouldReturnNullAssert() { + this.parent.registerSingleton("foo", Foo.class); + assertThat(getAssert(this.context)).getBean(Foo.class, Scope.NO_ANCESTORS) + .isNull(); + } + + @Test + public void getBeanOfTypeWhenHasMultipleBeansIncludingParentShouldFail() { + this.parent.registerSingleton("foo", Foo.class); + this.context.registerSingleton("bar", Foo.class); + this.thrown.expect(AssertionError.class); + this.thrown.expectMessage("but found"); + assertThat(getAssert(this.context)).getBean(Foo.class); + } + + @Test + public void getBeanOfTypeWithLimitedScopeWhenHasMultipleBeansIncludingParentShouldReturnBeanAssert() { + this.parent.registerSingleton("foo", Foo.class); + this.context.registerSingleton("bar", Foo.class); + assertThat(getAssert(this.context)).getBean(Foo.class, Scope.NO_ANCESTORS) + .isNotNull(); + } + @Test public void getBeanOfNameWhenHasBeanShouldReturnBeanAssert() { this.context.registerSingleton("foo", Foo.class); @@ -274,6 +353,22 @@ public class ApplicationContextAssertTests { assertThat(getAssert(this.failure)).getBeans(Foo.class); } + @Test + public void getBeansShouldIncludeBeansFromParentScope() { + this.parent.registerSingleton("foo", Foo.class); + this.context.registerSingleton("bar", Foo.class); + assertThat(getAssert(this.context)).getBeans(Foo.class).hasSize(2) + .containsKeys("foo", "bar"); + } + + @Test + public void getBeansWithLimitedScopeShouldNotIncludeBeansFromParentScope() { + this.parent.registerSingleton("foo", Foo.class); + this.context.registerSingleton("bar", Foo.class); + assertThat(getAssert(this.context)).getBeans(Foo.class, Scope.NO_ANCESTORS) + .hasSize(1).containsKeys("bar"); + } + @Test public void getFailureWhenFailedShouldReturnFailure() { assertThat(getAssert(this.failure)).getFailure().isSameAs(this.failure);