Commit 728b5223 authored by Phillip Webb's avatar Phillip Webb

Include scope support on ApplicationContextAssert

Update `ApplicationContextAssert` with support for scopes. Allows
tests to consider the all ancestors, or limit assertions to just the
current context.

Fixes gh-12015
parent d6858ae1
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -20,6 +20,7 @@ import java.io.BufferedReader; ...@@ -20,6 +20,7 @@ import java.io.BufferedReader;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringReader; import java.io.StringReader;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Map;
import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractObjectArrayAssert; import org.assertj.core.api.AbstractObjectArrayAssert;
...@@ -29,6 +30,7 @@ import org.assertj.core.api.Assertions; ...@@ -29,6 +30,7 @@ import org.assertj.core.api.Assertions;
import org.assertj.core.api.MapAssert; import org.assertj.core.api.MapAssert;
import org.assertj.core.error.BasicErrorMessageFactory; import org.assertj.core.error.BasicErrorMessageFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
...@@ -88,7 +90,8 @@ public class ApplicationContextAssert<C extends ApplicationContext> ...@@ -88,7 +90,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
} }
/** /**
* 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.
* <p> * <p>
* Example: <pre class="code"> * Example: <pre class="code">
* assertThat(context).hasSingleBean(Foo.class); </pre> * assertThat(context).hasSingleBean(Foo.class); </pre>
...@@ -100,11 +103,29 @@ public class ApplicationContextAssert<C extends ApplicationContext> ...@@ -100,11 +103,29 @@ public class ApplicationContextAssert<C extends ApplicationContext>
* given type * given type
*/ */
public ApplicationContextAssert<C> hasSingleBean(Class<?> 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); </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) { if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting( throwAssertionError(contextFailedToStartWhenExpecting(
"to have a single bean of type:%n <%s>", type)); "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) { if (names.length == 0) {
throwAssertionError(new BasicErrorMessageFactory( throwAssertionError(new BasicErrorMessageFactory(
"%nExpecting:%n <%s>%nto have a single bean of type:%n <%s>%nbut found no beans of that type", "%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<C extends ApplicationContext> ...@@ -119,7 +140,8 @@ public class ApplicationContextAssert<C extends ApplicationContext>
} }
/** /**
* 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.
* <p> * <p>
* Example: <pre class="code"> * Example: <pre class="code">
* assertThat(context).doesNotHaveBean(Foo.class); </pre> * assertThat(context).doesNotHaveBean(Foo.class); </pre>
...@@ -130,11 +152,28 @@ public class ApplicationContextAssert<C extends ApplicationContext> ...@@ -130,11 +152,28 @@ public class ApplicationContextAssert<C extends ApplicationContext>
* type * type
*/ */
public ApplicationContextAssert<C> doesNotHaveBean(Class<?> 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); </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) { if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting( throwAssertionError(contextFailedToStartWhenExpecting(
"not to have any beans of type:%n <%s>", type)); "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) { if (names.length > 0) {
throwAssertionError(new BasicErrorMessageFactory( throwAssertionError(new BasicErrorMessageFactory(
"%nExpecting:%n <%s>%nnot to have a beans of type:%n <%s>%nbut found:%n <%s>", "%nExpecting:%n <%s>%nnot to have a beans of type:%n <%s>%nbut found:%n <%s>",
...@@ -190,6 +229,26 @@ public class ApplicationContextAssert<C extends ApplicationContext> ...@@ -190,6 +229,26 @@ public class ApplicationContextAssert<C extends ApplicationContext>
.as("Bean names of type <%s> from <%s>", type, getApplicationContext()); .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 * 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 * becoming the object under test. If no beans of the specified type can be found an
...@@ -200,18 +259,20 @@ public class ApplicationContextAssert<C extends ApplicationContext> ...@@ -200,18 +259,20 @@ public class ApplicationContextAssert<C extends ApplicationContext>
* assertThat(context).getBean(Bar.class).isNull();</pre> * assertThat(context).getBean(Bar.class).isNull();</pre>
* @param <T> the bean type * @param <T> the bean type
* @param type 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 * @return bean assertions for the bean, or an assert on {@code null} if the no bean
* is found * is found
* @throws AssertionError if the application context did not start * @throws AssertionError if the application context did not start
* @throws AssertionError if the application context contains multiple beans of the * @throws AssertionError if the application context contains multiple beans of the
* given type * given type
*/ */
public <T> AbstractObjectAssert<?, T> getBean(Class<T> type) { public <T> AbstractObjectAssert<?, T> getBean(Class<T> type, Scope scope) {
Assert.notNull(scope, "Scope must not be null");
if (this.startupFailure != null) { if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting( throwAssertionError(contextFailedToStartWhenExpecting(
"to contain bean of type:%n <%s>", type)); "to contain bean of type:%n <%s>", type));
} }
String[] names = getApplicationContext().getBeanNamesForType(type); String[] names = scope.getBeanNamesForType(getApplicationContext(), type);
if (names.length > 1) { if (names.length > 1) {
throwAssertionError(new BasicErrorMessageFactory( throwAssertionError(new BasicErrorMessageFactory(
"%nExpecting:%n <%s>%nsingle bean of type:%n <%s>%nbut found:%n <%s>", "%nExpecting:%n <%s>%nsingle bean of type:%n <%s>%nbut found:%n <%s>",
...@@ -289,6 +350,24 @@ public class ApplicationContextAssert<C extends ApplicationContext> ...@@ -289,6 +350,24 @@ public class ApplicationContextAssert<C extends ApplicationContext>
} }
} }
/**
* 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 * 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 * context, the map becoming the object under test. If no bean of the specified type
...@@ -299,16 +378,18 @@ public class ApplicationContextAssert<C extends ApplicationContext> ...@@ -299,16 +378,18 @@ public class ApplicationContextAssert<C extends ApplicationContext>
* </pre> * </pre>
* @param <T> the bean type * @param <T> the bean type
* @param type 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 * @return bean assertions for the beans, or an assert on an empty {@code map} if the
* no beans are found * no beans are found
* @throws AssertionError if the application context did not start * @throws AssertionError if the application context did not start
*/ */
public <T> MapAssert<String, T> getBeans(Class<T> type) { public <T> MapAssert<String, T> getBeans(Class<T> type, Scope scope) {
Assert.notNull(scope, "Scope must not be null");
if (this.startupFailure != null) { if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting( throwAssertionError(contextFailedToStartWhenExpecting(
"to get beans of type:%n <%s>", type)); "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()); .as("Beans of type <%s> from <%s>", type, getApplicationContext());
} }
...@@ -373,6 +454,59 @@ public class ApplicationContextAssert<C extends ApplicationContext> ...@@ -373,6 +454,59 @@ public class ApplicationContextAssert<C extends ApplicationContext>
expectationFormat, arguments); 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> private static final class ContextFailedToStart<C extends ApplicationContext>
extends BasicErrorMessageFactory { extends BasicErrorMessageFactory {
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,10 +16,13 @@ ...@@ -16,10 +16,13 @@
package org.springframework.boot.test.context.assertj; package org.springframework.boot.test.context.assertj;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.boot.test.context.assertj.ApplicationContextAssert.Scope;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.StaticApplicationContext; import org.springframework.context.support.StaticApplicationContext;
...@@ -36,10 +39,25 @@ public class ApplicationContextAssertTests { ...@@ -36,10 +39,25 @@ public class ApplicationContextAssertTests {
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
private StaticApplicationContext context = new StaticApplicationContext(); private StaticApplicationContext parent;
private StaticApplicationContext context;
private RuntimeException failure = new RuntimeException(); 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 @Test
public void createWhenApplicationContextIsNullShouldThrowException() { public void createWhenApplicationContextIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class); this.thrown.expect(IllegalArgumentException.class);
...@@ -110,6 +128,22 @@ public class ApplicationContextAssertTests { ...@@ -110,6 +128,22 @@ public class ApplicationContextAssertTests {
assertThat(getAssert(this.failure)).hasSingleBean(Foo.class); 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 @Test
public void doesNotHaveBeanOfTypeWhenHasNoBeanOfTypeShouldPass() { public void doesNotHaveBeanOfTypeWhenHasNoBeanOfTypeShouldPass() {
assertThat(getAssert(this.context)).doesNotHaveBean(Foo.class); assertThat(getAssert(this.context)).doesNotHaveBean(Foo.class);
...@@ -132,6 +166,21 @@ public class ApplicationContextAssertTests { ...@@ -132,6 +166,21 @@ public class ApplicationContextAssertTests {
assertThat(getAssert(this.failure)).doesNotHaveBean(Foo.class); 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 @Test
public void doesNotHaveBeanOfNameWhenHasNoBeanOfTypeShouldPass() { public void doesNotHaveBeanOfNameWhenHasNoBeanOfTypeShouldPass() {
assertThat(getAssert(this.context)).doesNotHaveBean("foo"); assertThat(getAssert(this.context)).doesNotHaveBean("foo");
...@@ -204,6 +253,36 @@ public class ApplicationContextAssertTests { ...@@ -204,6 +253,36 @@ public class ApplicationContextAssertTests {
assertThat(getAssert(this.failure)).getBean(Foo.class); 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 @Test
public void getBeanOfNameWhenHasBeanShouldReturnBeanAssert() { public void getBeanOfNameWhenHasBeanShouldReturnBeanAssert() {
this.context.registerSingleton("foo", Foo.class); this.context.registerSingleton("foo", Foo.class);
...@@ -274,6 +353,22 @@ public class ApplicationContextAssertTests { ...@@ -274,6 +353,22 @@ public class ApplicationContextAssertTests {
assertThat(getAssert(this.failure)).getBeans(Foo.class); 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 @Test
public void getFailureWhenFailedShouldReturnFailure() { public void getFailureWhenFailedShouldReturnFailure() {
assertThat(getAssert(this.failure)).getFailure().isSameAs(this.failure); assertThat(getAssert(this.failure)).getFailure().isSameAs(this.failure);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment