Introduce buildTestContext() in TestContextBootstrapper

This commit moves the responsibility of building a TestContext from the
TestContextManager to a TestContextBootstrapper.

In addition, DefaultTestContext is now a public class residing in the
"support" subpackage.

Issue: SPR-12683
This commit is contained in:
Sam Brannen 2015-04-18 23:06:57 +02:00
parent 0392a88c69
commit 186abcb054
6 changed files with 121 additions and 66 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 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.
@ -28,7 +28,7 @@ package org.springframework.test.context;
public interface BootstrapContext { public interface BootstrapContext {
/** /**
* Get the {@link Class test class} for this bootstrap context. * Get the {@linkplain Class test class} for this bootstrap context.
* @return the test class (never {@code null}) * @return the test class (never {@code null})
*/ */
Class<?> getTestClass(); Class<?> getTestClass();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 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.
@ -29,15 +29,14 @@ import java.util.List;
* *
* <p>The {@link TestContextManager} uses a {@code TestContextBootstrapper} to * <p>The {@link TestContextManager} uses a {@code TestContextBootstrapper} to
* {@linkplain #getTestExecutionListeners get the TestExecutionListeners} for the * {@linkplain #getTestExecutionListeners get the TestExecutionListeners} for the
* current test and to {@linkplain #buildMergedContextConfiguration build the * current test and to {@linkplain #buildTestContext build the TestContext} that
* merged context configuration} necessary to create the {@link TestContext} that
* it manages. * it manages.
* *
* <p>Concrete implementations must provide a {@code public} no-args constructor. * <p>Concrete implementations must provide a {@code public} no-args constructor.
* *
* <p><strong>Note</strong>: this SPI might potentially change in the future in * <p><strong>WARNING</strong>: this SPI will likely change in the future in
* order to accommodate new requirements. Implementers are therefore strongly encouraged * order to accommodate new requirements. Implementers are therefore strongly encouraged
* <em>not</em> to implement this interface directly but rather to <em>extend</em> * <strong>not</strong> to implement this interface directly but rather to <em>extend</em>
* {@link org.springframework.test.context.support.AbstractTestContextBootstrapper * {@link org.springframework.test.context.support.AbstractTestContextBootstrapper
* AbstractTestContextBootstrapper} or one of its concrete subclasses instead. * AbstractTestContextBootstrapper} or one of its concrete subclasses instead.
* *
@ -58,6 +57,46 @@ public interface TestContextBootstrapper {
*/ */
BootstrapContext getBootstrapContext(); BootstrapContext getBootstrapContext();
/**
* Build the {@link TestContext} for the {@link BootstrapContext}
* associated with this bootstrapper.
* @return a new {@link TestContext}, never {@code null}
* @since 4.2
* @see #buildMergedContextConfiguration()
*/
TestContext buildTestContext();
/**
* Build the {@linkplain MergedContextConfiguration merged context configuration}
* for the test class in the {@link BootstrapContext} associated with this
* bootstrapper.
* <p>Implementations must take the following into account when building the
* merged configuration:
* <ul>
* <li>Context hierarchies declared via {@link ContextHierarchy @ContextHierarchy}
* and {@link ContextConfiguration @ContextConfiguration}</li>
* <li>Active bean definition profiles declared via {@link ActiveProfiles @ActiveProfiles}</li>
* <li>{@linkplain org.springframework.context.ApplicationContextInitializer
* Context initializers} declared via {@link ContextConfiguration#initializers}</li>
* <li>Test property sources declared via {@link TestPropertySource @TestPropertySource}</li>
* </ul>
* <p>Consult the Javadoc for the aforementioned annotations for details on
* the required semantics.
* <p>Note that the implementation of {@link #buildTestContext()} should
* typically delegate to this method when constructing the {@code TestContext}.
* <p>When determining which {@link ContextLoader} to use for a given test
* class, the following algorithm should be used:
* <ol>
* <li>If a {@code ContextLoader} class has been explicitly declared via
* {@link ContextConfiguration#loader}, use it.</li>
* <li>Otherwise, concrete implementations are free to determine which
* {@code ContextLoader} class to use as as default.</li>
* </ol>
* @return the merged context configuration, never {@code null}
* @see #buildTestContext()
*/
MergedContextConfiguration buildMergedContextConfiguration();
/** /**
* Get a list of newly instantiated {@link TestExecutionListener TestExecutionListeners} * Get a list of newly instantiated {@link TestExecutionListener TestExecutionListeners}
* for the test class in the {@link BootstrapContext} associated with this bootstrapper. * for the test class in the {@link BootstrapContext} associated with this bootstrapper.
@ -82,31 +121,4 @@ public interface TestContextBootstrapper {
*/ */
List<TestExecutionListener> getTestExecutionListeners(); List<TestExecutionListener> getTestExecutionListeners();
/**
* Build the {@linkplain MergedContextConfiguration merged context configuration}
* for the test class in the {@link BootstrapContext} associated with this
* bootstrapper.
* <p>Implementations must take the following into account when building the
* merged configuration:
* <ul>
* <li>Context hierarchies declared via {@link ContextHierarchy @ContextHierarchy}
* and {@link ContextConfiguration @ContextConfiguration}</li>
* <li>Active bean definition profiles declared via {@link ActiveProfiles @ActiveProfiles}</li>
* <li>{@linkplain org.springframework.context.ApplicationContextInitializer
* Context initializers} declared via {@link ContextConfiguration#initializers}</li>
* </ul>
* <p>Consult the Javadoc for the aforementioned annotations for details on
* the required semantics.
* <p>When determining which {@link ContextLoader} to use for a given test
* class, the following algorithm should be used:
* <ol>
* <li>If a {@code ContextLoader} class has been explicitly declared via
* {@link ContextConfiguration#loader}, use it.</li>
* <li>Otherwise, concrete implementations are free to determine which
* {@code ContextLoader} class to use as as default.</li>
* </ol>
* @return the merged context configuration, never {@code null}
*/
MergedContextConfiguration buildMergedContextConfiguration();
} }

View File

@ -28,15 +28,11 @@ import org.springframework.util.Assert;
/** /**
* {@code TestContextManager} is the main entry point into the <em>Spring * {@code TestContextManager} is the main entry point into the <em>Spring
* TestContext Framework</em>, which provides support for loading and accessing * TestContext Framework</em>.
* {@link org.springframework.context.ApplicationContext application contexts},
* dependency injection of test instances,
* {@link org.springframework.transaction.annotation.Transactional transactional}
* execution of test methods, etc.
* *
* <p>Specifically, a {@code TestContextManager} is responsible for managing a * <p>Specifically, a {@code TestContextManager} is responsible for managing a
* single {@link TestContext} and signaling events to all registered * single {@link TestContext} and signaling events to all registered
* {@link TestExecutionListener TestExecutionListeners} at well defined test * {@link TestExecutionListener TestExecutionListeners} at the following test
* execution points: * execution points:
* *
* <ul> * <ul>
@ -56,6 +52,21 @@ import org.springframework.util.Assert;
* 4's {@link org.junit.AfterClass @AfterClass})</li> * 4's {@link org.junit.AfterClass @AfterClass})</li>
* </ul> * </ul>
* *
* <p>Support for loading and accessing
* {@link org.springframework.context.ApplicationContext application contexts},
* dependency injection of test instances,
* {@link org.springframework.transaction.annotation.Transactional transactional}
* execution of test methods, etc. is provided by
* {@link SmartContextLoader ContextLoaders} and {@link TestExecutionListener
* TestExecutionListeners}, which are configured via
* {@link ContextConfiguration @ContextConfiguration} and
* {@link TestExecutionListeners @TestExecutionListeners}.
*
* <p>Bootstrapping of the {@code TestContext}, the default {@code ContextLoader},
* default {@code TestExecutionListeners}, and their collaborators is performed
* by a {@link TestContextBootstrapper}, which is configured via
* {@link BootstrapWith @BootstrapWith}.
*
* @author Sam Brannen * @author Sam Brannen
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 2.5 * @since 2.5
@ -90,7 +101,7 @@ public class TestContextManager {
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = new DefaultCacheAwareContextLoaderDelegate(); CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = new DefaultCacheAwareContextLoaderDelegate();
BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate); BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext); TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
this.testContext = new DefaultTestContext(testContextBootstrapper); this.testContext = testContextBootstrapper.buildTestContext();
registerTestExecutionListeners(testContextBootstrapper.getTestExecutionListeners()); registerTestExecutionListeners(testContextBootstrapper.getTestExecutionListeners());
} }
@ -190,7 +201,7 @@ public class TestContextManager {
* @see #getTestExecutionListeners() * @see #getTestExecutionListeners()
*/ */
public void prepareTestInstance(Object testInstance) throws Exception { public void prepareTestInstance(Object testInstance) throws Exception {
Assert.notNull(testInstance, "testInstance must not be null"); Assert.notNull(testInstance, "Test instance must not be null");
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("prepareTestInstance(): instance [" + testInstance + "]"); logger.trace("prepareTestInstance(): instance [" + testInstance + "]");
} }
@ -271,7 +282,7 @@ public class TestContextManager {
* @see #getTestExecutionListeners() * @see #getTestExecutionListeners()
*/ */
public void afterTestMethod(Object testInstance, Method testMethod, Throwable exception) throws Exception { public void afterTestMethod(Object testInstance, Method testMethod, Throwable exception) throws Exception {
Assert.notNull(testInstance, "testInstance must not be null"); Assert.notNull(testInstance, "Test instance must not be null");
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("afterTestMethod(): instance [" + testInstance + "], method [" + testMethod + logger.trace("afterTestMethod(): instance [" + testInstance + "], method [" + testMethod +
"], exception [" + exception + "]"); "], exception [" + exception + "]");

View File

@ -44,6 +44,7 @@ import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.ContextLoader; import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.SmartContextLoader; import org.springframework.test.context.SmartContextLoader;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestContextBootstrapper; import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.TestExecutionListeners;
@ -93,6 +94,21 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
return this.bootstrapContext; return this.bootstrapContext;
} }
/**
* Build a new {@link DefaultTestContext} using the {@linkplain Class test class}
* and {@link CacheAwareContextLoaderDelegate} in the {@link BootstrapContext}
* associated with this bootstrapper and by delegating to
* {@link #buildMergedContextConfiguration()}.
* <p>Concrete subclasses may choose to override this method to return a
* custom {@link TestContext} implementation.
* @since 4.2
*/
@Override
public TestContext buildTestContext() {
return new DefaultTestContext(bootstrapContext.getTestClass(), buildMergedContextConfiguration(),
bootstrapContext.getCacheAwareContextLoaderDelegate());
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 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.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.test.context; package org.springframework.test.context.support;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -22,21 +22,19 @@ import org.springframework.context.ApplicationContext;
import org.springframework.core.AttributeAccessorSupport; import org.springframework.core.AttributeAccessorSupport;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode; import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContext;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Default implementation of the {@link TestContext} interface. * Default implementation of the {@link TestContext} interface.
* *
* <p>Although {@code DefaultTestContext} was first introduced in Spring Framework
* 4.0, the initial implementation of this class was extracted from the existing
* code base for {@code TestContext} when {@code TestContext} was converted into
* an interface.
*
* @author Sam Brannen * @author Sam Brannen
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 4.0 * @since 4.0
*/ */
class DefaultTestContext extends AttributeAccessorSupport implements TestContext { public class DefaultTestContext extends AttributeAccessorSupport implements TestContext {
private static final long serialVersionUID = -5827157174866681233L; private static final long serialVersionUID = -5827157174866681233L;
@ -54,24 +52,42 @@ class DefaultTestContext extends AttributeAccessorSupport implements TestContext
/** /**
* Construct a new test context using the supplied {@link TestContextBootstrapper}. * Construct a new {@code DefaultTestContext} from the supplied arguments.
* @param testContextBootstrapper the {@code TestContextBootstrapper} to use * @param testClass the test class for this test context; never {@code null}
* to construct the test context (must not be {@code null}) * @param mergedContextConfiguration the merged application context
* configuration for this test context; never {@code null}
* @param cacheAwareContextLoaderDelegate the delegate to use for loading
* and closing the application context for this test context; never {@code null}
*/ */
DefaultTestContext(TestContextBootstrapper testContextBootstrapper) { public DefaultTestContext(Class<?> testClass, MergedContextConfiguration mergedContextConfiguration,
Assert.notNull(testContextBootstrapper, "TestContextBootstrapper must not be null"); CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
Assert.notNull(testClass, "testClass must not be null");
BootstrapContext bootstrapContext = testContextBootstrapper.getBootstrapContext(); Assert.notNull(mergedContextConfiguration, "MergedContextConfiguration must not be null");
this.testClass = bootstrapContext.getTestClass(); Assert.notNull(cacheAwareContextLoaderDelegate, "CacheAwareContextLoaderDelegate must not be null");
this.cacheAwareContextLoaderDelegate = bootstrapContext.getCacheAwareContextLoaderDelegate(); this.testClass = testClass;
this.mergedContextConfiguration = testContextBootstrapper.buildMergedContextConfiguration(); this.mergedContextConfiguration = mergedContextConfiguration;
this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate;
} }
/**
* Get the {@linkplain ApplicationContext application context} for this
* test context.
* <p>The default implementation delegates to the {@link CacheAwareContextLoaderDelegate}
* that was supplied when this {@code TestContext} was constructed.
* @see CacheAwareContextLoaderDelegate#loadContext
*/
public ApplicationContext getApplicationContext() { public ApplicationContext getApplicationContext() {
return this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration); return this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration);
} }
/**
* Mark the {@linkplain ApplicationContext application context} associated
* with this test context as <em>dirty</em> (i.e., by removing it from the
* context cache and closing it).
* <p>The default implementation delegates to the {@link CacheAwareContextLoaderDelegate}
* that was supplied when this {@code TestContext} was constructed.
* @see CacheAwareContextLoaderDelegate#closeContext
*/
public void markApplicationContextDirty(HierarchyMode hierarchyMode) { public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
this.cacheAwareContextLoaderDelegate.closeContext(this.mergedContextConfiguration, hierarchyMode); this.cacheAwareContextLoaderDelegate.closeContext(this.mergedContextConfiguration, hierarchyMode);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 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.
@ -28,12 +28,12 @@ public abstract class TestContextTestUtils {
return buildTestContext(testClass, new DefaultCacheAwareContextLoaderDelegate(contextCache)); return buildTestContext(testClass, new DefaultCacheAwareContextLoaderDelegate(contextCache));
} }
public static TestContext buildTestContext( public static TestContext buildTestContext(Class<?> testClass,
Class<?> testClass, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate); BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate);
TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext); TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext);
return new DefaultTestContext(testContextBootstrapper); return testContextBootstrapper.buildTestContext();
} }
} }