Browse Source

Polishing

pull/976/merge
Juergen Hoeller 10 years ago
parent
commit
d2d528dc05
  1. 62
      spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java
  2. 10
      spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java
  3. 78
      spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java
  4. 22
      spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java
  5. 56
      spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java
  6. 21
      spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java
  7. 58
      spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java

62
spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java

@ -43,51 +43,47 @@ import static org.springframework.beans.BeanUtils.instantiateClass;
*/ */
abstract class BootstrapUtils { abstract class BootstrapUtils {
private static final String DEFAULT_BOOTSTRAP_CONTEXT_CLASS_NAME = "org.springframework.test.context.support.DefaultBootstrapContext"; private static final String DEFAULT_BOOTSTRAP_CONTEXT_CLASS_NAME =
"org.springframework.test.context.support.DefaultBootstrapContext";
private static final String DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_CLASS_NAME = "org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate"; private static final String DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_CLASS_NAME =
"org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate";
private static final String DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME = "org.springframework.test.context.support.DefaultTestContextBootstrapper"; private static final String DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME =
"org.springframework.test.context.support.DefaultTestContextBootstrapper";
private static final String DEFAULT_WEB_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME = "org.springframework.test.context.web.WebTestContextBootstrapper"; private static final String DEFAULT_WEB_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME =
"org.springframework.test.context.web.WebTestContextBootstrapper";
private static final String WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME = "org.springframework.test.context.web.WebAppConfiguration"; private static final String WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME =
"org.springframework.test.context.web.WebAppConfiguration";
private static final Log logger = LogFactory.getLog(BootstrapUtils.class); private static final Log logger = LogFactory.getLog(BootstrapUtils.class);
private BootstrapUtils() {
/* no-op */
}
/** /**
* Create the {@code BootstrapContext} for the specified {@linkplain Class test class}. * Create the {@code BootstrapContext} for the specified {@linkplain Class test class}.
*
* <p>Uses reflection to create a {@link org.springframework.test.context.support.DefaultBootstrapContext} * <p>Uses reflection to create a {@link org.springframework.test.context.support.DefaultBootstrapContext}
* that uses a {@link org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate}. * that uses a {@link org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate}.
*
* @param testClass the test class for which the bootstrap context should be created * @param testClass the test class for which the bootstrap context should be created
* @return a new {@code BootstrapContext}; never {@code null} * @return a new {@code BootstrapContext}; never {@code null}
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static BootstrapContext createBootstrapContext(Class<?> testClass) { static BootstrapContext createBootstrapContext(Class<?> testClass) {
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = createCacheAwareContextLoaderDelegate(); CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = createCacheAwareContextLoaderDelegate();
Class<? extends BootstrapContext> clazz = null; Class<? extends BootstrapContext> clazz = null;
try { try {
clazz = (Class<? extends BootstrapContext>) ClassUtils.forName(DEFAULT_BOOTSTRAP_CONTEXT_CLASS_NAME, clazz = (Class<? extends BootstrapContext>) ClassUtils.forName(
BootstrapUtils.class.getClassLoader()); DEFAULT_BOOTSTRAP_CONTEXT_CLASS_NAME, BootstrapUtils.class.getClassLoader());
Constructor<? extends BootstrapContext> constructor = clazz.getConstructor(
Constructor<? extends BootstrapContext> constructor = clazz.getConstructor(Class.class, Class.class, CacheAwareContextLoaderDelegate.class);
CacheAwareContextLoaderDelegate.class);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Instantiating BootstrapContext using constructor [%s]", constructor)); logger.debug(String.format("Instantiating BootstrapContext using constructor [%s]", constructor));
} }
return instantiateClass(constructor, testClass, cacheAwareContextLoaderDelegate); return instantiateClass(constructor, testClass, cacheAwareContextLoaderDelegate);
} }
catch (Throwable t) { catch (Throwable ex) {
throw new IllegalStateException("Could not load BootstrapContext [" + clazz + "]", t); throw new IllegalStateException("Could not load BootstrapContext [" + clazz + "]", ex);
} }
} }
@ -104,8 +100,8 @@ abstract class BootstrapUtils {
} }
return instantiateClass(clazz, CacheAwareContextLoaderDelegate.class); return instantiateClass(clazz, CacheAwareContextLoaderDelegate.class);
} }
catch (Throwable t) { catch (Throwable ex) {
throw new IllegalStateException("Could not load CacheAwareContextLoaderDelegate [" + clazz + "]", t); throw new IllegalStateException("Could not load CacheAwareContextLoaderDelegate [" + clazz + "]", ex);
} }
} }
@ -113,7 +109,6 @@ abstract class BootstrapUtils {
* Resolve the {@link TestContextBootstrapper} type for the test class in the * Resolve the {@link TestContextBootstrapper} type for the test class in the
* supplied {@link BootstrapContext}, instantiate it, and provide it a reference * supplied {@link BootstrapContext}, instantiate it, and provide it a reference
* to the {@link BootstrapContext}. * to the {@link BootstrapContext}.
*
* <p>If the {@link BootstrapWith @BootstrapWith} annotation is present on * <p>If the {@link BootstrapWith @BootstrapWith} annotation is present on
* the test class, either directly or as a meta-annotation, then its * the test class, either directly or as a meta-annotation, then its
* {@link BootstrapWith#value value} will be used as the bootstrapper type. * {@link BootstrapWith#value value} will be used as the bootstrapper type.
@ -123,7 +118,6 @@ abstract class BootstrapUtils {
* {@link org.springframework.test.context.web.WebTestContextBootstrapper * {@link org.springframework.test.context.web.WebTestContextBootstrapper
* WebTestContextBootstrapper} will be used, depending on the presence of * WebTestContextBootstrapper} will be used, depending on the presence of
* {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}. * {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}.
*
* @param bootstrapContext the bootstrap context to use * @param bootstrapContext the bootstrap context to use
* @return a fully configured {@code TestContextBootstrapper} * @return a fully configured {@code TestContextBootstrapper}
*/ */
@ -140,19 +134,17 @@ abstract class BootstrapUtils {
logger.debug(String.format("Instantiating TestContextBootstrapper for test class [%s] from class [%s]", logger.debug(String.format("Instantiating TestContextBootstrapper for test class [%s] from class [%s]",
testClass.getName(), clazz.getName())); testClass.getName(), clazz.getName()));
} }
TestContextBootstrapper testContextBootstrapper = instantiateClass(clazz, TestContextBootstrapper.class); TestContextBootstrapper testContextBootstrapper = instantiateClass(clazz, TestContextBootstrapper.class);
testContextBootstrapper.setBootstrapContext(bootstrapContext); testContextBootstrapper.setBootstrapContext(bootstrapContext);
return testContextBootstrapper; return testContextBootstrapper;
} }
catch (Throwable ex) { catch (Throwable ex) {
if (ex instanceof IllegalStateException) { if (ex instanceof IllegalStateException) {
throw (IllegalStateException) ex; throw (IllegalStateException) ex;
} }
throw new IllegalStateException("Could not load TestContextBootstrapper [" + clazz throw new IllegalStateException("Could not load TestContextBootstrapper [" + clazz +
+ "]. Specify @BootstrapWith's 'value' attribute " "]. Specify @BootstrapWith's 'value' attribute or make the default bootstrapper class available.",
+ "or make the default bootstrapper class available.", ex); ex);
} }
} }
@ -160,14 +152,16 @@ abstract class BootstrapUtils {
* @since 4.3 * @since 4.3
*/ */
private static Class<?> resolveExplicitTestContextBootstrapper(Class<?> testClass) { private static Class<?> resolveExplicitTestContextBootstrapper(Class<?> testClass) {
MultiValueMap<String, Object> attributesMultiMap = AnnotatedElementUtils.getAllAnnotationAttributes( MultiValueMap<String, Object> attributesMultiMap =
testClass, BootstrapWith.class.getName()); AnnotatedElementUtils.getAllAnnotationAttributes(testClass, BootstrapWith.class.getName());
List<Object> values = (attributesMultiMap == null ? null : attributesMultiMap.get(AnnotationUtils.VALUE)); List<Object> values = (attributesMultiMap != null ? attributesMultiMap.get(AnnotationUtils.VALUE) : null);
if (values == null) { if (values == null) {
return null; return null;
} }
Assert.state(values.size() == 1, String.format("Configuration error: found multiple declarations of " if (values.size() != 1) {
+ "@BootstrapWith on test class [%s] with values %s", testClass.getName(), values)); throw new IllegalStateException(String.format("Configuration error: found multiple declarations of " +
"@BootstrapWith on test class [%s] with values %s", testClass.getName(), values));
}
return (Class<?>) values.get(0); return (Class<?>) values.get(0);
} }

10
spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2016 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,14 +20,17 @@ package org.springframework.test.context;
* {@code TestExecutionListener} defines a <em>listener</em> API for reacting to * {@code TestExecutionListener} defines a <em>listener</em> API for reacting to
* test execution events published by the {@link TestContextManager} with which * test execution events published by the {@link TestContextManager} with which
* the listener is registered. * the listener is registered.
*
* <p>Concrete implementations must provide a {@code public} no-args constructor, * <p>Concrete implementations must provide a {@code public} no-args constructor,
* so that listeners can be instantiated transparently by tools and configuration * so that listeners can be instantiated transparently by tools and configuration
* mechanisms. * mechanisms.
*
* <p>Implementations may optionally declare the position in which they should * <p>Implementations may optionally declare the position in which they should
* be ordered among the chain of default listeners via the * be ordered among the chain of default listeners via the
* {@link org.springframework.core.Ordered Ordered} interface or * {@link org.springframework.core.Ordered Ordered} interface or
* {@link org.springframework.core.annotation.Order @Order} annotation. See * {@link org.springframework.core.annotation.Order @Order} annotation. See
* {@link TestContextBootstrapper#getTestExecutionListeners()} for details. * {@link TestContextBootstrapper#getTestExecutionListeners()} for details.
*
* <p>Spring provides the following out-of-the-box implementations (all of * <p>Spring provides the following out-of-the-box implementations (all of
* which implement {@code Ordered}): * which implement {@code Ordered}):
* <ul> * <ul>
@ -58,7 +61,6 @@ public interface TestExecutionListener {
* <em>before class</em> lifecycle callbacks. * <em>before class</em> lifecycle callbacks.
* <p>If a given testing framework does not support <em>before class</em> * <p>If a given testing framework does not support <em>before class</em>
* lifecycle callbacks, this method will not be called for that framework. * lifecycle callbacks, this method will not be called for that framework.
*
* @param testContext the test context for the test; never {@code null} * @param testContext the test context for the test; never {@code null}
* @throws Exception allows any exception to propagate * @throws Exception allows any exception to propagate
*/ */
@ -69,7 +71,6 @@ public interface TestExecutionListener {
* {@link TestContext test context}, for example by injecting dependencies. * {@link TestContext test context}, for example by injecting dependencies.
* <p>This method should be called immediately after instantiation of the test * <p>This method should be called immediately after instantiation of the test
* instance but prior to any framework-specific lifecycle callbacks. * instance but prior to any framework-specific lifecycle callbacks.
*
* @param testContext the test context for the test; never {@code null} * @param testContext the test context for the test; never {@code null}
* @throws Exception allows any exception to propagate * @throws Exception allows any exception to propagate
*/ */
@ -82,7 +83,6 @@ public interface TestExecutionListener {
* fixtures. * fixtures.
* <p>This method should be called immediately prior to framework-specific * <p>This method should be called immediately prior to framework-specific
* <em>before</em> lifecycle callbacks. * <em>before</em> lifecycle callbacks.
*
* @param testContext the test context in which the test method will be * @param testContext the test context in which the test method will be
* executed; never {@code null} * executed; never {@code null}
* @throws Exception allows any exception to propagate * @throws Exception allows any exception to propagate
@ -96,7 +96,6 @@ public interface TestExecutionListener {
* fixtures. * fixtures.
* <p>This method should be called immediately after framework-specific * <p>This method should be called immediately after framework-specific
* <em>after</em> lifecycle callbacks. * <em>after</em> lifecycle callbacks.
*
* @param testContext the test context in which the test method was * @param testContext the test context in which the test method was
* executed; never {@code null} * executed; never {@code null}
* @throws Exception allows any exception to propagate * @throws Exception allows any exception to propagate
@ -110,7 +109,6 @@ public interface TestExecutionListener {
* <em>after class</em> lifecycle callbacks. * <em>after class</em> lifecycle callbacks.
* <p>If a given testing framework does not support <em>after class</em> * <p>If a given testing framework does not support <em>after class</em>
* lifecycle callbacks, this method will not be called for that framework. * lifecycle callbacks, this method will not be called for that framework.
*
* @param testContext the test context for the test; never {@code null} * @param testContext the test context for the test; never {@code null}
* @throws Exception allows any exception to propagate * @throws Exception allows any exception to propagate
*/ */

78
spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2016 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.
@ -30,8 +30,8 @@ import org.springframework.core.annotation.AliasFor;
* which {@link TestExecutionListener TestExecutionListeners} should be * which {@link TestExecutionListener TestExecutionListeners} should be
* registered with a {@link TestContextManager}. * registered with a {@link TestContextManager}.
* *
* <p>Typically, {@code @TestExecutionListeners} will be used in conjunction with * <p>Typically, {@code @TestExecutionListeners} will be used in conjunction
* {@link ContextConfiguration @ContextConfiguration}. * with {@link ContextConfiguration @ContextConfiguration}.
* *
* <p>As of Spring Framework 4.0, this annotation may be used as a * <p>As of Spring Framework 4.0, this annotation may be used as a
* <em>meta-annotation</em> to create custom <em>composed annotations</em>. * <em>meta-annotation</em> to create custom <em>composed annotations</em>.
@ -48,42 +48,8 @@ import org.springframework.core.annotation.AliasFor;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
public @interface TestExecutionListeners { public @interface TestExecutionListeners {
/**
* Enumeration of <em>modes</em> that dictate whether or not explicitly
* declared listeners are merged with the default listeners when
* {@code @TestExecutionListeners} is declared on a class that does
* <strong>not</strong> inherit listeners from a superclass.
* @since 4.1
*/
static enum MergeMode {
/**
* Indicates that locally declared listeners should replace the default
* listeners.
*/
REPLACE_DEFAULTS,
/**
* Indicates that locally declared listeners should be merged with the
* default listeners.
* <p>The merging algorithm ensures that duplicates are removed from
* the list and that the resulting set of merged listeners is sorted
* according to the semantics of
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator
* AnnotationAwareOrderComparator}. If a listener implements
* {@link org.springframework.core.Ordered Ordered} or is annotated
* with {@link org.springframework.core.annotation.Order @Order} it can
* influence the position in which it is merged with the defaults; otherwise,
* locally declared listeners will simply be appended to the list of default
* listeners when merged.
*/
MERGE_WITH_DEFAULTS
}
/** /**
* Alias for {@link #listeners}. * Alias for {@link #listeners}.
*
* <p>This attribute may <strong>not</strong> be used in conjunction with * <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #listeners}, but it may be used instead of {@link #listeners}. * {@link #listeners}, but it may be used instead of {@link #listeners}.
*/ */
@ -93,10 +59,8 @@ public @interface TestExecutionListeners {
/** /**
* The {@link TestExecutionListener TestExecutionListeners} to register with * The {@link TestExecutionListener TestExecutionListeners} to register with
* the {@link TestContextManager}. * the {@link TestContextManager}.
*
* <p>This attribute may <strong>not</strong> be used in conjunction with * <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #value}, but it may be used instead of {@link #value}. * {@link #value}, but it may be used instead of {@link #value}.
*
* @see org.springframework.test.context.web.ServletTestExecutionListener * @see org.springframework.test.context.web.ServletTestExecutionListener
* @see org.springframework.test.context.support.DependencyInjectionTestExecutionListener * @see org.springframework.test.context.support.DependencyInjectionTestExecutionListener
* @see org.springframework.test.context.support.DirtiesContextTestExecutionListener * @see org.springframework.test.context.support.DirtiesContextTestExecutionListener
@ -109,7 +73,6 @@ public @interface TestExecutionListeners {
/** /**
* Whether or not {@link #listeners TestExecutionListeners} from superclasses * Whether or not {@link #listeners TestExecutionListeners} from superclasses
* should be <em>inherited</em>. * should be <em>inherited</em>.
*
* <p>The default value is {@code true}, which means that an annotated * <p>The default value is {@code true}, which means that an annotated
* class will <em>inherit</em> the listeners defined by an annotated * class will <em>inherit</em> the listeners defined by an annotated
* superclass. Specifically, the listeners for an annotated class will be * superclass. Specifically, the listeners for an annotated class will be
@ -122,7 +85,6 @@ public @interface TestExecutionListeners {
* {@code DependencyInjectionTestExecutionListener}, * {@code DependencyInjectionTestExecutionListener},
* {@code DirtiesContextTestExecutionListener}, <strong>and</strong> * {@code DirtiesContextTestExecutionListener}, <strong>and</strong>
* {@code TransactionalTestExecutionListener}, in that order. * {@code TransactionalTestExecutionListener}, in that order.
*
* <pre class="code"> * <pre class="code">
* &#064;TestExecutionListeners({ * &#064;TestExecutionListeners({
* DependencyInjectionTestExecutionListener.class, * DependencyInjectionTestExecutionListener.class,
@ -136,7 +98,6 @@ public @interface TestExecutionListeners {
* public class TransactionalTest extends AbstractBaseTest { * public class TransactionalTest extends AbstractBaseTest {
* // ... * // ...
* }</pre> * }</pre>
*
* <p>If {@code inheritListeners} is set to {@code false}, the listeners for * <p>If {@code inheritListeners} is set to {@code false}, the listeners for
* the annotated class will <em>shadow</em> and effectively replace any * the annotated class will <em>shadow</em> and effectively replace any
* listeners defined by a superclass. * listeners defined by a superclass.
@ -158,4 +119,37 @@ public @interface TestExecutionListeners {
*/ */
MergeMode mergeMode() default MergeMode.REPLACE_DEFAULTS; MergeMode mergeMode() default MergeMode.REPLACE_DEFAULTS;
/**
* Enumeration of <em>modes</em> that dictate whether or not explicitly
* declared listeners are merged with the default listeners when
* {@code @TestExecutionListeners} is declared on a class that does
* <strong>not</strong> inherit listeners from a superclass.
* @since 4.1
*/
enum MergeMode {
/**
* Indicates that locally declared listeners should replace the default
* listeners.
*/
REPLACE_DEFAULTS,
/**
* Indicates that locally declared listeners should be merged with the
* default listeners.
* <p>The merging algorithm ensures that duplicates are removed from
* the list and that the resulting set of merged listeners is sorted
* according to the semantics of
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator
* AnnotationAwareOrderComparator}. If a listener implements
* {@link org.springframework.core.Ordered Ordered} or is annotated
* with {@link org.springframework.core.annotation.Order @Order} it can
* influence the position in which it is merged with the defaults; otherwise,
* locally declared listeners will simply be appended to the list of default
* listeners when merged.
*/
MERGE_WITH_DEFAULTS
}
} }

22
spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2016 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.
@ -90,10 +90,8 @@ public @interface TestPropertySource {
/** /**
* Alias for {@link #locations}. * Alias for {@link #locations}.
*
* <p>This attribute may <strong>not</strong> be used in conjunction with * <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #locations}, but it may be used <em>instead</em> of {@link #locations}. * {@link #locations}, but it may be used <em>instead</em> of {@link #locations}.
*
* @see #locations * @see #locations
*/ */
@AliasFor("locations") @AliasFor("locations")
@ -104,12 +102,10 @@ public @interface TestPropertySource {
* {@code Environment}'s set of {@code PropertySources}. Each location * {@code Environment}'s set of {@code PropertySources}. Each location
* will be added to the enclosing {@code Environment} as its own property * will be added to the enclosing {@code Environment} as its own property
* source, in the order declared. * source, in the order declared.
*
* <h3>Supported File Formats</h3> * <h3>Supported File Formats</h3>
* <p>Both traditional and XML-based properties file formats are supported * <p>Both traditional and XML-based properties file formats are supported
* &mdash; for example, {@code "classpath:/com/example/test.properties"} * &mdash; for example, {@code "classpath:/com/example/test.properties"}
* or {@code "file:/path/to/file.xml"}. * or {@code "file:/path/to/file.xml"}.
*
* <h3>Path Resource Semantics</h3> * <h3>Path Resource Semantics</h3>
* <p>Each path will be interpreted as a Spring * <p>Each path will be interpreted as a Spring
* {@link org.springframework.core.io.Resource Resource}. A plain path * {@link org.springframework.core.io.Resource Resource}. A plain path
@ -128,17 +124,13 @@ public @interface TestPropertySource {
* in paths (i.e., <code>${...}</code>) will be * in paths (i.e., <code>${...}</code>) will be
* {@linkplain org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) resolved} * {@linkplain org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) resolved}
* against the {@code Environment}. * against the {@code Environment}.
*
* <h3>Default Properties File Detection</h3> * <h3>Default Properties File Detection</h3>
* <p>See the class-level Javadoc for a discussion on detection of defaults. * <p>See the class-level Javadoc for a discussion on detection of defaults.
*
* <h3>Precedence</h3> * <h3>Precedence</h3>
* <p>Properties loaded from resource locations have lower precedence than * <p>Properties loaded from resource locations have lower precedence than
* inlined {@link #properties}. * inlined {@link #properties}.
*
* <p>This attribute may <strong>not</strong> be used in conjunction with * <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #value}, but it may be used <em>instead</em> of {@link #value}. * {@link #value}, but it may be used <em>instead</em> of {@link #value}.
*
* @see #inheritLocations * @see #inheritLocations
* @see #value * @see #value
* @see #properties * @see #properties
@ -150,18 +142,15 @@ public @interface TestPropertySource {
/** /**
* Whether or not test property source {@link #locations} from superclasses * Whether or not test property source {@link #locations} from superclasses
* should be <em>inherited</em>. * should be <em>inherited</em>.
*
* <p>The default value is {@code true}, which means that a test class will * <p>The default value is {@code true}, which means that a test class will
* <em>inherit</em> property source locations defined by a superclass. * <em>inherit</em> property source locations defined by a superclass.
* Specifically, the property source locations for a test class will be * Specifically, the property source locations for a test class will be
* appended to the list of property source locations defined by a superclass. * appended to the list of property source locations defined by a superclass.
* Thus, subclasses have the option of <em>extending</em> the list of test * Thus, subclasses have the option of <em>extending</em> the list of test
* property source locations. * property source locations.
*
* <p>If {@code inheritLocations} is set to {@code false}, the property * <p>If {@code inheritLocations} is set to {@code false}, the property
* source locations for the test class will <em>shadow</em> and effectively * source locations for the test class will <em>shadow</em> and effectively
* replace any property source locations defined by a superclass. * replace any property source locations defined by a superclass.
*
* <p>In the following example, the {@code ApplicationContext} for * <p>In the following example, the {@code ApplicationContext} for
* {@code BaseTest} will be loaded using only the {@code "base.properties"} * {@code BaseTest} will be loaded using only the {@code "base.properties"}
* file as a test property source. In contrast, the {@code ApplicationContext} * file as a test property source. In contrast, the {@code ApplicationContext}
@ -193,7 +182,6 @@ public @interface TestPropertySource {
* {@code ApplicationContext} is loaded for the test. All key-value pairs * {@code ApplicationContext} is loaded for the test. All key-value pairs
* will be added to the enclosing {@code Environment} as a single test * will be added to the enclosing {@code Environment} as a single test
* {@code PropertySource} with the highest precedence. * {@code PropertySource} with the highest precedence.
*
* <h3>Supported Syntax</h3> * <h3>Supported Syntax</h3>
* <p>The supported syntax for key-value pairs is the same as the * <p>The supported syntax for key-value pairs is the same as the
* syntax defined for entries in a Java * syntax defined for entries in a Java
@ -203,14 +191,11 @@ public @interface TestPropertySource {
* <li>{@code "key:value"}</li> * <li>{@code "key:value"}</li>
* <li>{@code "key value"}</li> * <li>{@code "key value"}</li>
* </ul> * </ul>
*
* <h3>Precedence</h3> * <h3>Precedence</h3>
* <p>Properties declared via this attribute have higher precedence than * <p>Properties declared via this attribute have higher precedence than
* properties loaded from resource {@link #locations}. * properties loaded from resource {@link #locations}.
*
* <p>This attribute may be used in conjunction with {@link #value} * <p>This attribute may be used in conjunction with {@link #value}
* <em>or</em> {@link #locations}. * <em>or</em> {@link #locations}.
*
* @see #inheritProperties * @see #inheritProperties
* @see #locations * @see #locations
* @see org.springframework.core.env.PropertySource * @see org.springframework.core.env.PropertySource
@ -220,17 +205,14 @@ public @interface TestPropertySource {
/** /**
* Whether or not inlined test {@link #properties} from superclasses should * Whether or not inlined test {@link #properties} from superclasses should
* be <em>inherited</em>. * be <em>inherited</em>.
*
* <p>The default value is {@code true}, which means that a test class will * <p>The default value is {@code true}, which means that a test class will
* <em>inherit</em> inlined properties defined by a superclass. Specifically, * <em>inherit</em> inlined properties defined by a superclass. Specifically,
* the inlined properties for a test class will be appended to the list of * the inlined properties for a test class will be appended to the list of
* inlined properties defined by a superclass. Thus, subclasses have the * inlined properties defined by a superclass. Thus, subclasses have the
* option of <em>extending</em> the list of inlined test properties. * option of <em>extending</em> the list of inlined test properties.
*
* <p>If {@code inheritProperties} is set to {@code false}, the inlined * <p>If {@code inheritProperties} is set to {@code false}, the inlined
* properties for the test class will <em>shadow</em> and effectively * properties for the test class will <em>shadow</em> and effectively
* replace any inlined properties defined by a superclass. * replace any inlined properties defined by a superclass.
*
* <p>In the following example, the {@code ApplicationContext} for * <p>In the following example, the {@code ApplicationContext} for
* {@code BaseTest} will be loaded using only the inlined {@code key1} * {@code BaseTest} will be loaded using only the inlined {@code key1}
* property. In contrast, the {@code ApplicationContext} for * property. In contrast, the {@code ApplicationContext} for
@ -242,14 +224,12 @@ public @interface TestPropertySource {
* public class BaseTest { * public class BaseTest {
* // ... * // ...
* } * }
*
* &#064;TestPropertySource(properties = &quot;key2 = value2&quot;) * &#064;TestPropertySource(properties = &quot;key2 = value2&quot;)
* &#064;ContextConfiguration * &#064;ContextConfiguration
* public class ExtendedTest extends BaseTest { * public class ExtendedTest extends BaseTest {
* // ... * // ...
* } * }
* </pre> * </pre>
*
* @see #properties * @see #properties
*/ */
boolean inheritProperties() default true; boolean inheritProperties() default true;

56
spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java

@ -123,8 +123,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>(); List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>();
boolean usingDefaults = false; boolean usingDefaults = false;
AnnotationDescriptor<TestExecutionListeners> descriptor = MetaAnnotationUtils.findAnnotationDescriptor(clazz, AnnotationDescriptor<TestExecutionListeners> descriptor =
annotationType); MetaAnnotationUtils.findAnnotationDescriptor(clazz, annotationType);
// Use defaults? // Use defaults?
if (descriptor == null) { if (descriptor == null) {
@ -146,17 +146,17 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
} }
boolean inheritListeners = testExecutionListeners.inheritListeners(); boolean inheritListeners = testExecutionListeners.inheritListeners();
AnnotationDescriptor<TestExecutionListeners> superDescriptor = MetaAnnotationUtils.findAnnotationDescriptor( AnnotationDescriptor<TestExecutionListeners> superDescriptor =
MetaAnnotationUtils.findAnnotationDescriptor(
descriptor.getRootDeclaringClass().getSuperclass(), annotationType); descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
// If there are no listeners to inherit, we might need to merge the // If there are no listeners to inherit, we might need to merge the
// locally declared listeners with the defaults. // locally declared listeners with the defaults.
if ((!inheritListeners || superDescriptor == null) if ((!inheritListeners || superDescriptor == null) &&
&& (testExecutionListeners.mergeMode() == MergeMode.MERGE_WITH_DEFAULTS)) { testExecutionListeners.mergeMode() == MergeMode.MERGE_WITH_DEFAULTS) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format("Merging default listeners with listeners configured via " +
"Merging default listeners with listeners configured via @TestExecutionListeners for class [%s].", "@TestExecutionListeners for class [%s].", descriptor.getRootDeclaringClass().getName()));
descriptor.getRootDeclaringClass().getName()));
} }
usingDefaults = true; usingDefaults = true;
classesList.addAll(getDefaultTestExecutionListenerClasses()); classesList.addAll(getDefaultTestExecutionListenerClasses());
@ -206,9 +206,9 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
} }
if (ncdfe != null) { if (ncdfe != null) {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info(String.format("Could not instantiate TestExecutionListener [%s]. " logger.info(String.format("Could not instantiate TestExecutionListener [%s]. " +
+ "Specify custom listener classes or make the default listener classes " "Specify custom listener classes or make the default listener classes " +
+ "(and their required dependencies) available. Offending class: [%s]", "(and their required dependencies) available. Offending class: [%s]",
listenerClass.getName(), ncdfe.getMessage())); listenerClass.getName(), ncdfe.getMessage()));
} }
} }
@ -234,8 +234,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
} }
catch (Throwable ex) { catch (Throwable ex) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Could not load default TestExecutionListener class [" + className logger.debug("Could not load default TestExecutionListener class [" + className +
+ "]. Specify custom listener classes or make the default listener classes available.", ex); "]. Specify custom listener classes or make the default listener classes available.", ex);
} }
} }
} }
@ -254,9 +254,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
* @see SpringFactoriesLoader#loadFactoryNames * @see SpringFactoriesLoader#loadFactoryNames
*/ */
protected List<String> getDefaultTestExecutionListenerClassNames() { protected List<String> getDefaultTestExecutionListenerClassNames() {
final List<String> classNames = SpringFactoriesLoader.loadFactoryNames(TestExecutionListener.class, List<String> classNames =
getClass().getClassLoader()); SpringFactoriesLoader.loadFactoryNames(TestExecutionListener.class, getClass().getClassLoader());
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info(String.format("Loaded default TestExecutionListener class names from location [%s]: %s", logger.info(String.format("Loaded default TestExecutionListener class names from location [%s]: %s",
SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames)); SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
@ -273,13 +272,14 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
Class<?> testClass = getBootstrapContext().getTestClass(); Class<?> testClass = getBootstrapContext().getTestClass();
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getCacheAwareContextLoaderDelegate(); CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getCacheAwareContextLoaderDelegate();
if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class, if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(
ContextHierarchy.class) == null) { testClass, ContextConfiguration.class, ContextHierarchy.class) == null) {
return buildDefaultMergedContextConfiguration(testClass, cacheAwareContextLoaderDelegate); return buildDefaultMergedContextConfiguration(testClass, cacheAwareContextLoaderDelegate);
} }
if (AnnotationUtils.findAnnotation(testClass, ContextHierarchy.class) != null) { if (AnnotationUtils.findAnnotation(testClass, ContextHierarchy.class) != null) {
Map<String, List<ContextConfigurationAttributes>> hierarchyMap = ContextLoaderUtils.buildContextHierarchyMap(testClass); Map<String, List<ContextConfigurationAttributes>> hierarchyMap =
ContextLoaderUtils.buildContextHierarchyMap(testClass);
MergedContextConfiguration parentConfig = null; MergedContextConfiguration parentConfig = null;
MergedContextConfiguration mergedConfig = null; MergedContextConfiguration mergedConfig = null;
@ -293,8 +293,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
Assert.notEmpty(reversedList, "ContextConfigurationAttributes list must not be empty"); Assert.notEmpty(reversedList, "ContextConfigurationAttributes list must not be empty");
Class<?> declaringClass = reversedList.get(0).getDeclaringClass(); Class<?> declaringClass = reversedList.get(0).getDeclaringClass();
mergedConfig = buildMergedContextConfiguration(declaringClass, reversedList, parentConfig, mergedConfig = buildMergedContextConfiguration(
cacheAwareContextLoaderDelegate, true); declaringClass, reversedList, parentConfig, cacheAwareContextLoaderDelegate, true);
parentConfig = mergedConfig; parentConfig = mergedConfig;
} }
@ -303,8 +303,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
} }
else { else {
return buildMergedContextConfiguration(testClass, return buildMergedContextConfiguration(testClass,
ContextLoaderUtils.resolveContextConfigurationAttributes(testClass), null, ContextLoaderUtils.resolveContextConfigurationAttributes(testClass),
cacheAwareContextLoaderDelegate, true); null, cacheAwareContextLoaderDelegate, true);
} }
} }
@ -390,14 +390,16 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
Set<ContextCustomizer> contextCustomizers = getContextCustomizers(testClass, Set<ContextCustomizer> contextCustomizers = getContextCustomizers(testClass,
Collections.unmodifiableList(configAttributesList)); Collections.unmodifiableList(configAttributesList));
if (requireLocationsClassesOrInitializers && areAllEmpty(locations, classes, initializers, contextCustomizers)) { if (requireLocationsClassesOrInitializers &&
areAllEmpty(locations, classes, initializers, contextCustomizers)) {
throw new IllegalStateException(String.format( throw new IllegalStateException(String.format(
"%s was unable to detect defaults, and no ApplicationContextInitializers " "%s was unable to detect defaults, and no ApplicationContextInitializers " +
+ "or ContextCustomizers were declared for context configuration attributes %s", "or ContextCustomizers were declared for context configuration attributes %s",
contextLoader.getClass().getSimpleName(), configAttributesList)); contextLoader.getClass().getSimpleName(), configAttributesList));
} }
MergedTestPropertySources mergedTestPropertySources = TestPropertySourceUtils.buildMergedTestPropertySources(testClass); MergedTestPropertySources mergedTestPropertySources =
TestPropertySourceUtils.buildMergedTestPropertySources(testClass);
MergedContextConfiguration mergedConfig = new MergedContextConfiguration(testClass, MergedContextConfiguration mergedConfig = new MergedContextConfiguration(testClass,
StringUtils.toStringArray(locations), StringUtils.toStringArray(locations),
ClassUtils.toClassArray(classes), ClassUtils.toClassArray(classes),

21
spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java

@ -98,11 +98,10 @@ public abstract class MetaAnnotationUtils {
* @return the corresponding annotation descriptor if the annotation was found; * @return the corresponding annotation descriptor if the annotation was found;
* otherwise {@code null} * otherwise {@code null}
*/ */
private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(Class<?> clazz, private static <T extends Annotation> AnnotationDescriptor<T> findAnnotationDescriptor(
Set<Annotation> visited, Class<T> annotationType) { Class<?> clazz, Set<Annotation> visited, Class<T> annotationType) {
Assert.notNull(annotationType, "Annotation type must not be null"); Assert.notNull(annotationType, "Annotation type must not be null");
if (clazz == null || Object.class == clazz) { if (clazz == null || Object.class == clazz) {
return null; return null;
} }
@ -115,11 +114,11 @@ public abstract class MetaAnnotationUtils {
// Declared on a composed annotation (i.e., as a meta-annotation)? // Declared on a composed annotation (i.e., as a meta-annotation)?
for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { if (!AnnotationUtils.isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) {
AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(), AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(
visited, annotationType); composedAnnotation.annotationType(), visited, annotationType);
if (descriptor != null) { if (descriptor != null) {
return new AnnotationDescriptor<T>(clazz, descriptor.getDeclaringClass(), composedAnnotation, return new AnnotationDescriptor<T>(
descriptor.getAnnotation()); clazz, descriptor.getDeclaringClass(), composedAnnotation, descriptor.getAnnotation());
} }
} }
} }
@ -287,8 +286,8 @@ public abstract class MetaAnnotationUtils {
this.declaringClass = declaringClass; this.declaringClass = declaringClass;
this.composedAnnotation = composedAnnotation; this.composedAnnotation = composedAnnotation;
this.annotation = annotation; this.annotation = annotation;
this.annotationAttributes = AnnotatedElementUtils.findMergedAnnotationAttributes(rootDeclaringClass, this.annotationAttributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
annotation.annotationType().getName(), false, false); rootDeclaringClass, annotation.annotationType().getName(), false, false);
} }
public Class<?> getRootDeclaringClass() { public Class<?> getRootDeclaringClass() {
@ -314,8 +313,8 @@ public abstract class MetaAnnotationUtils {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public T synthesizeAnnotation() { public T synthesizeAnnotation() {
return AnnotationUtils.synthesizeAnnotation(getAnnotationAttributes(), (Class<T>) getAnnotationType(), return AnnotationUtils.synthesizeAnnotation(
getRootDeclaringClass()); getAnnotationAttributes(), (Class<T>) getAnnotationType(), getRootDeclaringClass());
} }
public Class<? extends Annotation> getAnnotationType() { public Class<? extends Annotation> getAnnotationType() {

58
spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2015 the original author or authors. * Copyright 2002-2016 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.
@ -187,8 +187,9 @@ public class TestExecutionListenersTests {
static class DefaultListenersTestCase { static class DefaultListenersTestCase {
} }
@TestExecutionListeners(listeners = { QuuxTestExecutionListener.class, @TestExecutionListeners(
DependencyInjectionTestExecutionListener.class }, mergeMode = MERGE_WITH_DEFAULTS) listeners = {QuuxTestExecutionListener.class, DependencyInjectionTestExecutionListener.class},
mergeMode = MERGE_WITH_DEFAULTS)
static class MergedDefaultListenersWithCustomListenerPrependedTestCase { static class MergedDefaultListenersWithCustomListenerPrependedTestCase {
} }
@ -211,12 +212,12 @@ public class TestExecutionListenersTests {
static class SubSubInheritedDefaultListenersTestCase extends SubInheritedDefaultListenersTestCase { static class SubSubInheritedDefaultListenersTestCase extends SubInheritedDefaultListenersTestCase {
} }
@TestExecutionListeners(listeners = { QuuxTestExecutionListener.class }, inheritListeners = false) @TestExecutionListeners(listeners = QuuxTestExecutionListener.class, inheritListeners = false)
static class NonInheritedDefaultListenersTestCase extends InheritedDefaultListenersTestCase { static class NonInheritedDefaultListenersTestCase extends InheritedDefaultListenersTestCase {
} }
@TestExecutionListeners({ FooTestExecutionListener.class, BarTestExecutionListener.class, @TestExecutionListeners(
BazTestExecutionListener.class }) {FooTestExecutionListener.class, BarTestExecutionListener.class, BazTestExecutionListener.class})
static class ExplicitListenersTestCase { static class ExplicitListenersTestCase {
} }
@ -232,36 +233,36 @@ public class TestExecutionListenersTests {
static class DuplicateListenersConfigTestCase { static class DuplicateListenersConfigTestCase {
} }
@TestExecutionListeners({// @TestExecutionListeners({
FooTestExecutionListener.class,// FooTestExecutionListener.class,
BarTestExecutionListener.class,// BarTestExecutionListener.class,
BazTestExecutionListener.class // BazTestExecutionListener.class
}) })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface MetaListeners { @interface MetaListeners {
} }
@TestExecutionListeners(QuuxTestExecutionListener.class) @TestExecutionListeners(QuuxTestExecutionListener.class)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface MetaInheritedListeners { @interface MetaInheritedListeners {
} }
@TestExecutionListeners(listeners = QuuxTestExecutionListener.class, inheritListeners = false) @TestExecutionListeners(listeners = QuuxTestExecutionListener.class, inheritListeners = false)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface MetaNonInheritedListeners { @interface MetaNonInheritedListeners {
} }
@TestExecutionListeners @TestExecutionListeners
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface MetaListenersWithOverrides { @interface MetaListenersWithOverrides {
Class<? extends TestExecutionListener>[] listeners() default { FooTestExecutionListener.class, Class<? extends TestExecutionListener>[] listeners() default
BarTestExecutionListener.class }; {FooTestExecutionListener.class, BarTestExecutionListener.class};
} }
@TestExecutionListeners @TestExecutionListeners
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface MetaInheritedListenersWithOverrides { @interface MetaInheritedListenersWithOverrides {
Class<? extends TestExecutionListener>[] listeners() default QuuxTestExecutionListener.class; Class<? extends TestExecutionListener>[] listeners() default QuuxTestExecutionListener.class;
@ -270,7 +271,7 @@ public class TestExecutionListenersTests {
@TestExecutionListeners @TestExecutionListeners
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
static @interface MetaNonInheritedListenersWithOverrides { @interface MetaNonInheritedListenersWithOverrides {
Class<? extends TestExecutionListener>[] listeners() default QuuxTestExecutionListener.class; Class<? extends TestExecutionListener>[] listeners() default QuuxTestExecutionListener.class;
@ -289,24 +290,23 @@ public class TestExecutionListenersTests {
static class MetaNonInheritedListenersTestCase extends MetaInheritedListenersTestCase { static class MetaNonInheritedListenersTestCase extends MetaInheritedListenersTestCase {
} }
@MetaListenersWithOverrides(listeners = {// @MetaListenersWithOverrides(listeners = {
FooTestExecutionListener.class,// FooTestExecutionListener.class,
BarTestExecutionListener.class,// BarTestExecutionListener.class,
BazTestExecutionListener.class // BazTestExecutionListener.class
}) })
static class MetaWithOverridesTestCase { static class MetaWithOverridesTestCase {
} }
@MetaInheritedListenersWithOverrides(listeners = { FooTestExecutionListener.class, BarTestExecutionListener.class }) @MetaInheritedListenersWithOverrides(listeners = {FooTestExecutionListener.class, BarTestExecutionListener.class})
static class MetaInheritedListenersWithOverridesTestCase extends MetaWithOverridesTestCase { static class MetaInheritedListenersWithOverridesTestCase extends MetaWithOverridesTestCase {
} }
@MetaNonInheritedListenersWithOverrides(listeners = {// @MetaNonInheritedListenersWithOverrides(listeners = {
FooTestExecutionListener.class,// FooTestExecutionListener.class,
BarTestExecutionListener.class,// BarTestExecutionListener.class,
BazTestExecutionListener.class // BazTestExecutionListener.class
},// }, inheritListeners = true)
inheritListeners = true)
static class MetaNonInheritedListenersWithOverridesTestCase extends MetaInheritedListenersWithOverridesTestCase { static class MetaNonInheritedListenersWithOverridesTestCase extends MetaInheritedListenersWithOverridesTestCase {
} }

Loading…
Cancel
Save