From 044f51283b92609e0a17c90adcaf0dc57201477c Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 16 Jun 2013 00:01:34 +0200 Subject: [PATCH] Introduce ActiveProfilesResolver in the TCF Prior to this commit, the active bean definition profiles to use when loading an ApplicationContext for tests could only be configured declaratively (i.e., via hard-coded values supplied to the 'value' or 'profiles' attribute of @ActiveProfiles). This commit makes it possible to programmatically configure active bean definition profiles in tests via a new ActiveProfileResolver interface. Custom resolvers can be registered via a new 'resolver' attribute introduced in @ActiveProfiles. Overview of changes: - Introduced a new ActiveProfilesResolver API. - Added a 'resolver' attribute to @ActiveProfiles. - Updated ContextLoaderUtils.resolveActiveProfiles() to support ActiveProfilesResolvers. - Documented these new features in the reference manual. - Added new content to the reference manual regarding the 'inheritProfiles' attribute of @ActiveProfiles - Removed the use of Docbook markup in the testing chapter of the reference manual for Java code examples in order to allow comments to have proper syntax highlighting in the generated HTML and PDF. Issue: SPR-10338 --- .../test/context/ActiveProfiles.java | 24 +- .../test/context/ActiveProfilesResolver.java | 50 +++ .../test/context/ContextLoaderUtils.java | 84 ++++- .../test/context/ContextCacheTests.java | 15 + .../test/context/ContextLoaderUtilsTests.java | 173 +++++++++- .../context/junit4/SpringJUnit4TestSuite.java | 4 + ...vProfileResolverAnnotationConfigTests.java | 34 ++ .../ProfileAnnotationConfigTestSuite.java | 5 +- ...vProfileResolverAnnotationConfigTests.java | 34 ++ .../ClassNameActiveProfilesResolver.java | 31 ++ .../ClassNameActiveProfilesResolverTest.java | 57 ++++ .../xml/DevProfileResolverXmlConfigTests.java | 34 ++ .../xml/ProfileXmlConfigTestSuite.java | 5 +- src/reference/docbook/testing.xml | 318 ++++++++++++------ 14 files changed, 731 insertions(+), 137 deletions(-) create mode 100644 spring-test/src/main/java/org/springframework/test/context/ActiveProfilesResolver.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileResolverAnnotationConfigTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileResolverAnnotationConfigTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolver.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolverTest.java create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileResolverXmlConfigTests.java diff --git a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java index 39bfb5d3cd8..0b76fa42318 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java +++ b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import java.lang.annotation.Target; * @see SmartContextLoader * @see MergedContextConfiguration * @see ContextConfiguration + * @see ActiveProfilesResolver * @see org.springframework.context.ApplicationContext * @see org.springframework.context.annotation.Profile */ @@ -47,8 +48,8 @@ public @interface ActiveProfiles { * Alias for {@link #profiles}. * *

This attribute may not be used in conjunction - * with {@link #profiles}, but it may be used instead of - * {@link #profiles}. + * with {@link #profiles} or {@link #resolver}, but it may be used + * instead of them. */ String[] value() default {}; @@ -56,11 +57,24 @@ public @interface ActiveProfiles { * The bean definition profiles to activate. * *

This attribute may not be used in conjunction - * with {@link #value}, but it may be used instead of - * {@link #value}. + * with {@link #value} or {@link #resolver}, but it may be used + * instead of them. */ String[] profiles() default {}; + /** + * The type of {@link ActiveProfilesResolver} to use for resolving the active + * bean definition profiles programmatically. + * + *

This attribute may not be used in conjunction + * with {@link #profiles} or {@link #value}, but it may be used instead + * of them in order to resolve the active profiles programmatically. + * + * @since 4.0 + * @see ActiveProfilesResolver + */ + Class resolver() default ActiveProfilesResolver.class; + /** * Whether or not bean definition profiles from superclasses should be * inherited. diff --git a/spring-test/src/main/java/org/springframework/test/context/ActiveProfilesResolver.java b/spring-test/src/main/java/org/springframework/test/context/ActiveProfilesResolver.java new file mode 100644 index 00000000000..0985ebb02c3 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/ActiveProfilesResolver.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +/** + * Strategy interface for programmatically resolving which active bean + * definition profiles should be used when loading an + * {@link org.springframework.context.ApplicationContext ApplicationContext} + * for a test class. + * + *

A custom {@code ActiveProfilesResolver} can be registered via the + * {@link ActiveProfiles#resolver resolver} attribute of {@code @ActiveProfiles}. + * + *

Concrete implementations must provide a {@code public} no-args constructor. + * + * @author Sam Brannen + * @author Michail Nikolaev + * @since 4.0 + * @see ActiveProfiles + */ +public interface ActiveProfilesResolver { + + /** + * Resolve the bean definition profiles to use when loading an + * {@code ApplicationContext} for the given {@linkplain Class test class}. + * + * @param testClass the test class for which the profiles should be resolved; + * never {@code null} + * @return the list of bean definition profiles to use when loading the + * {@code ApplicationContext}; never {@code null} + * @see ActiveProfiles#resolver + * @see ActiveProfiles#inheritProfiles + */ + String[] resolve(Class testClass); + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java index a43897eb0b6..3fb2b0a6e7f 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java @@ -16,11 +16,6 @@ package org.springframework.test.context; -import static org.springframework.beans.BeanUtils.instantiateClass; -import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass; -import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClassForTypes; -import static org.springframework.core.annotation.AnnotationUtils.isAnnotationDeclaredLocally; - import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.util.ArrayList; @@ -42,6 +37,9 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import static org.springframework.beans.BeanUtils.*; +import static org.springframework.core.annotation.AnnotationUtils.*; + /** * Utility methods for working with {@link ContextLoader ContextLoaders} and * {@link SmartContextLoader SmartContextLoaders} and resolving resource locations, @@ -49,12 +47,14 @@ import org.springframework.util.StringUtils; * initializers. * * @author Sam Brannen + * @author Michail Nikolaev * @since 3.1 * @see ContextLoader * @see SmartContextLoader * @see ContextConfiguration * @see ContextConfigurationAttributes * @see ActiveProfiles + * @see ActiveProfilesResolver * @see ApplicationContextInitializer * @see ContextHierarchy * @see MergedContextConfiguration @@ -477,24 +477,43 @@ abstract class ContextLoaderUtils { while (declaringClass != null) { ActiveProfiles annotation = declaringClass.getAnnotation(annotationType); - if (logger.isTraceEnabled()) { logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation, declaringClass.getName())); } + validateActiveProfilesConfiguration(declaringClass, annotation); String[] profiles = annotation.profiles(); String[] valueProfiles = annotation.value(); + Class resolverClass = annotation.resolver(); - if (!ObjectUtils.isEmpty(valueProfiles) && !ObjectUtils.isEmpty(profiles)) { - String msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] " - + "and 'profiles' [%s] attributes. Only one declaration of active bean " - + "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(), - ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles)); - logger.error(msg); - throw new IllegalStateException(msg); + boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass); + boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles); + + if (resolverDeclared) { + ActiveProfilesResolver resolver = null; + try { + resolver = instantiateClass(resolverClass, ActiveProfilesResolver.class); + } + catch (Exception e) { + String msg = String.format("Could not instantiate ActiveProfilesResolver of " + + "type [%s] for test class [%s].", resolverClass.getName(), declaringClass.getName()); + logger.error(msg); + throw new IllegalStateException(msg, e); + } + + if (resolver != null) { + profiles = resolver.resolve(declaringClass); + if (profiles == null) { + String msg = String.format( + "ActiveProfilesResolver [%s] returned a null array of bean definition profiles.", + resolverClass.getName()); + logger.error(msg); + throw new IllegalStateException(msg); + } + } } - else if (!ObjectUtils.isEmpty(valueProfiles)) { + else if (valueDeclared) { profiles = valueProfiles; } @@ -511,6 +530,43 @@ abstract class ContextLoaderUtils { return StringUtils.toStringArray(activeProfiles); } + private static void validateActiveProfilesConfiguration(Class declaringClass, ActiveProfiles annotation) { + String[] valueProfiles = annotation.value(); + String[] profiles = annotation.profiles(); + Class resolverClass = annotation.resolver(); + boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles); + boolean profilesDeclared = !ObjectUtils.isEmpty(profiles); + boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass); + + String msg = null; + + if (valueDeclared && profilesDeclared) { + msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] " + + "and 'profiles' [%s] attributes. Only one declaration of active bean " + + "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(), + ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles)); + } + else if (valueDeclared && resolverDeclared) { + msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] " + + "and 'resolver' [%s] attributes. Only one source of active bean " + + "definition profiles is permitted per @ActiveProfiles annotation, " + + "either declaritively or programmatically.", declaringClass.getName(), + ObjectUtils.nullSafeToString(valueProfiles), resolverClass.getName()); + } + else if (profilesDeclared && resolverDeclared) { + msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'profiles' [%s] " + + "and 'resolver' [%s] attributes. Only one source of active bean " + + "definition profiles is permitted per @ActiveProfiles annotation, " + + "either declaritively or programmatically.", declaringClass.getName(), + ObjectUtils.nullSafeToString(profiles), resolverClass.getName()); + } + + if (msg != null) { + logger.error(msg); + throw new IllegalStateException(msg); + } + } + /** * Build the {@link MergedContextConfiguration merged context configuration} for * the supplied {@link Class testClass} and {@code defaultContextLoaderClassName}, diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java index eb1c39a908b..21f15f441f4 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java @@ -32,6 +32,7 @@ import static org.springframework.test.context.SpringRunnerContextCacheTests.*; * conjunction with cache keys used in {@link TestContext}. * * @author Sam Brannen + * @author Michail Nikolaev * @since 3.1 * @see SpringRunnerContextCacheTests */ @@ -84,6 +85,7 @@ public class ContextCacheTests { loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 3, 1); loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 4, 1); loadCtxAndAssertStats(BarFooProfilesTestCase.class, 1, 5, 1); + loadCtxAndAssertStats(FooBarActiveProfilesResolverTestCase.class, 1, 6, 1); } @Test @@ -287,6 +289,19 @@ public class ContextCacheTests { private static class BarFooProfilesTestCase { } + private static class FooBarActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "foo", "bar" }; + } + } + + @ActiveProfiles(resolver = FooBarActiveProfilesResolver.class) + @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class) + private static class FooBarActiveProfilesResolverTestCase { + } + @ContextHierarchy({ @ContextConfiguration }) private static class ClassHierarchyContextHierarchyLevel1TestCase { diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java index ecedda4ddf9..6f032545b61 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java @@ -16,10 +16,6 @@ package org.springframework.test.context; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.springframework.test.context.ContextLoaderUtils.*; - import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -38,10 +34,15 @@ import org.springframework.test.context.support.DelegatingSmartContextLoader; import org.springframework.test.context.support.GenericPropertiesContextLoader; import org.springframework.web.context.support.GenericWebApplicationContext; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.test.context.ContextLoaderUtils.*; + /** * Unit tests for {@link ContextLoaderUtils}. * * @author Sam Brannen + * @author Michail Nikolaev * @since 3.1 */ public class ContextLoaderUtilsTests { @@ -606,8 +607,94 @@ public class ContextLoaderUtilsTests { assertTrue(list.contains("cat")); } + /** + * @since 4.0 + */ + @Test + public void resolveActiveProfilesWithResolver() { + String[] profiles = resolveActiveProfiles(FooActiveProfilesResolverTest.class); + assertNotNull(profiles); + assertEquals(1, profiles.length); + assertArrayEquals(new String[] { "foo" }, profiles); + } - // ------------------------------------------------------------------------- + /** + * @since 4.0 + */ + @Test + public void resolveActiveProfilesWithInheritedResolver() { + String[] profiles = resolveActiveProfiles(InheritedFooActiveProfilesResolverTest.class); + assertNotNull(profiles); + assertEquals(1, profiles.length); + assertArrayEquals(new String[] { "foo" }, profiles); + } + + /** + * @since 4.0 + */ + @Test + public void resolveActiveProfilesWithMergedInheritedResolver() { + String[] profiles = resolveActiveProfiles(MergedInheritedFooActiveProfilesResolverTest.class); + assertNotNull(profiles); + assertEquals(2, profiles.length); + List list = Arrays.asList(profiles); + assertTrue(list.contains("foo")); + assertTrue(list.contains("bar")); + } + + /** + * @since 4.0 + */ + @Test + public void resolveActiveProfilesWithOverridenInheritedResolver() { + String[] profiles = resolveActiveProfiles(OverridenInheritedFooActiveProfilesResolverTest.class); + assertNotNull(profiles); + assertEquals(1, profiles.length); + assertArrayEquals(new String[] { "bar" }, profiles); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void resolveActiveProfilesWithConflictingResolverAndProfiles() { + resolveActiveProfiles(ConflictingResolverAndProfilesTest.class); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void resolveActiveProfilesWithConflictingResolverAndValue() { + resolveActiveProfiles(ConflictingResolverAndValueTest.class); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void resolveActiveProfilesWithConflictingProfilesAndValue() { + resolveActiveProfiles(ConflictingProfilesAndValueTest.class); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void resolveActiveProfilesWithResolverWithoutDefaultConstructor() { + resolveActiveProfiles(NoDefaultConstructorActiveProfilesResolverTest.class); + } + + /** + * @since 4.0 + */ + @Test(expected = IllegalStateException.class) + public void resolveActiveProfilesWithResolverThatReturnsNull() { + resolveActiveProfiles(NullActiveProfilesResolverTest.class); + } + + + // --- General Purpose Classes and Config ---------------------------------- private static class Enigma { } @@ -677,6 +764,80 @@ public class ContextLoaderUtilsTests { private static class Animals extends LocationsBar { } + // --- ActiveProfilesResolver ---------------------------------------------- + + public static class FooActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "foo" }; + } + } + + public static class BarActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "bar" }; + } + } + + public static class NullActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return null; + } + } + + public static class NoDefaultConstructorActiveProfilesResolver implements ActiveProfilesResolver { + + public NoDefaultConstructorActiveProfilesResolver(Object agument) { + } + + @Override + public String[] resolve(Class testClass) { + return null; + } + } + + @ActiveProfiles(resolver = NullActiveProfilesResolver.class) + private static class NullActiveProfilesResolverTest { + } + + @ActiveProfiles(resolver = NoDefaultConstructorActiveProfilesResolver.class) + private static class NoDefaultConstructorActiveProfilesResolverTest { + } + + @ActiveProfiles(resolver = FooActiveProfilesResolver.class) + private static class FooActiveProfilesResolverTest { + } + + private static class InheritedFooActiveProfilesResolverTest extends FooActiveProfilesResolverTest { + } + + @ActiveProfiles(resolver = BarActiveProfilesResolver.class) + private static class MergedInheritedFooActiveProfilesResolverTest extends InheritedFooActiveProfilesResolverTest { + } + + @ActiveProfiles(resolver = BarActiveProfilesResolver.class, inheritProfiles = false) + private static class OverridenInheritedFooActiveProfilesResolverTest extends InheritedFooActiveProfilesResolverTest { + } + + @ActiveProfiles(resolver = BarActiveProfilesResolver.class, profiles = "conflict") + private static class ConflictingResolverAndProfilesTest { + } + + @ActiveProfiles(resolver = BarActiveProfilesResolver.class, value = "conflict") + private static class ConflictingResolverAndValueTest { + } + + @ActiveProfiles(profiles = "conflict", value = "conflict") + private static class ConflictingProfilesAndValueTest { + } + + // --- ApplicationContextInitializer --------------------------------------- + private static class FooInitializer implements ApplicationContextInitializer { @Override @@ -707,6 +868,8 @@ public class ContextLoaderUtilsTests { private static class OverriddenInitializersAndClassesBar extends InitializersFoo { } + // --- @ContextHierarchy --------------------------------------------------- + @ContextConfiguration("foo.xml") @ContextHierarchy(@ContextConfiguration("bar.xml")) private static class SingleTestClassWithContextConfigurationAndContextHierarchy { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java index 72ddd1b8c4f..181bc8f7cb8 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java @@ -37,7 +37,9 @@ import org.springframework.test.context.junit4.annotation.ExplicitConfigClassesI import org.springframework.test.context.junit4.orm.HibernateSessionFlushingTests; import org.springframework.test.context.junit4.profile.annotation.DefaultProfileAnnotationConfigTests; import org.springframework.test.context.junit4.profile.annotation.DevProfileAnnotationConfigTests; +import org.springframework.test.context.junit4.profile.annotation.DevProfileResolverAnnotationConfigTests; import org.springframework.test.context.junit4.profile.xml.DefaultProfileXmlConfigTests; +import org.springframework.test.context.junit4.profile.xml.DevProfileResolverXmlConfigTests; import org.springframework.test.context.junit4.profile.xml.DevProfileXmlConfigTests; /** @@ -77,8 +79,10 @@ StandardJUnit4FeaturesTests.class,// DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.class,// DefaultProfileAnnotationConfigTests.class,// DevProfileAnnotationConfigTests.class,// + DevProfileResolverAnnotationConfigTests.class,// DefaultProfileXmlConfigTests.class,// DevProfileXmlConfigTests.class,// + DevProfileResolverXmlConfigTests.class,// ExpectedExceptionSpringRunnerTests.class,// TimedSpringRunnerTests.class,// RepeatedSpringRunnerTests.class,// diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileResolverAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileResolverAnnotationConfigTests.java new file mode 100644 index 00000000000..09a4bca53aa --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileResolverAnnotationConfigTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.profile.annotation; + +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ActiveProfilesResolver; + +/** + * @author Michail Nikolaev + * @since 4.0 + */ +@ActiveProfiles(resolver = DevProfileResolverAnnotationConfigTests.class, inheritProfiles = false) +public class DevProfileResolverAnnotationConfigTests extends DevProfileAnnotationConfigTests implements + ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "dev" }; + } +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java index b3b7068c39a..d0286917d83 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,8 @@ import org.junit.runners.Suite.SuiteClasses; // Note: the following 'multi-line' layout is for enhanced code readability. @SuiteClasses({// DefaultProfileAnnotationConfigTests.class,// - DevProfileAnnotationConfigTests.class // + DevProfileAnnotationConfigTests.class,// + DevProfileResolverAnnotationConfigTests.class // }) public class ProfileAnnotationConfigTestSuite { } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileResolverAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileResolverAnnotationConfigTests.java new file mode 100644 index 00000000000..2cfafabe379 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileResolverAnnotationConfigTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.profile.importresource; + +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ActiveProfilesResolver; + +/** + * @author Michail Nikolaev + * @since 4.0 + */ +@ActiveProfiles(resolver = DevProfileResolverAnnotationConfigTests.class, inheritProfiles = false) +public class DevProfileResolverAnnotationConfigTests extends DevProfileAnnotationConfigTests implements + ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "dev" }; + } +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolver.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolver.java new file mode 100644 index 00000000000..51d5fc4c5d2 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolver.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.profile.resolver; + +import org.springframework.test.context.ActiveProfilesResolver; + +/** + * @author Michail Nikolaev + * @since 4.0 + */ +public class ClassNameActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { testClass.getSimpleName().toLowerCase() }; + } +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolverTest.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolverTest.java new file mode 100644 index 00000000000..52e705250df --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/resolver/ClassNameActiveProfilesResolverTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.profile.resolver; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * @author Michail Nikolaev + * @since 4.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@ActiveProfiles(resolver = ClassNameActiveProfilesResolver.class) +public class ClassNameActiveProfilesResolverTest { + + @Configuration + static class Config { + + } + + + @Autowired + private ApplicationContext applicationContext; + + + @Test + public void test() { + assertTrue(Arrays.asList(applicationContext.getEnvironment().getActiveProfiles()).contains( + getClass().getSimpleName().toLowerCase())); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileResolverXmlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileResolverXmlConfigTests.java new file mode 100644 index 00000000000..36c3fdcc075 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileResolverXmlConfigTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit4.profile.xml; + +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ActiveProfilesResolver; + +/** + * @author Michail Nikolaev + * @since 4.0 + */ +@ActiveProfiles(resolver = DevProfileResolverXmlConfigTests.class, inheritProfiles = false) +public class DevProfileResolverXmlConfigTests extends DevProfileXmlConfigTests implements ActiveProfilesResolver { + + @Override + public String[] resolve(Class testClass) { + return new String[] { "dev" }; + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java index 38a5939c617..16230c0a554 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,8 @@ import org.junit.runners.Suite.SuiteClasses; // Note: the following 'multi-line' layout is for enhanced code readability. @SuiteClasses({// DefaultProfileXmlConfigTests.class,// - DevProfileXmlConfigTests.class // + DevProfileXmlConfigTests.class,// + DevProfileResolverXmlConfigTests.class // }) public class ProfileXmlConfigTestSuite { } diff --git a/src/reference/docbook/testing.xml b/src/reference/docbook/testing.xml index 72bb4d3c8f4..ec78308b388 100644 --- a/src/reference/docbook/testing.xml +++ b/src/reference/docbook/testing.xml @@ -197,7 +197,6 @@ TestContext framework is agnostic of the actual testing framework in use, thus allowing instrumentation of tests in various environments including JUnit, TestNG, and so on. -

@@ -444,13 +443,13 @@ @ContextConfiguration("/test-config.xml") public class XmlApplicationContextTests { - // class body... + // class body... } @ContextConfiguration(classes = TestConfig.class) public class ConfigClassApplicationContextTests { - // class body... + // class body... } As an alternative or in addition to declaring resource @@ -462,7 +461,7 @@ public class ConfigClassApplicationContextTests { @ContextConfiguration(initializers = CustomContextIntializer.class) public class ContextInitializerTests { - // class body... + // class body... } @ContextConfiguration may @@ -477,7 +476,7 @@ public class ContextInitializerTests { role="bold">locations = "/test-context.xml", loader = CustomContextLoader.class) public class CustomLoaderXmlApplicationContextTests { - // class body... + // class body... } @@ -514,7 +513,7 @@ public class CustomLoaderXmlApplicationContextTests { @ContextConfiguration @WebAppConfiguration public class WebAppTests { - // class body... + // class body... } To override the default, specify a different base resource @@ -527,7 +526,7 @@ public class WebAppTests { @ContextConfiguration @WebAppConfiguration("classpath:test-web-resources") public class WebAppTests { - // class body... + // class body... } Note that @WebAppConfiguration @@ -559,7 +558,7 @@ public class WebAppTests { @ContextConfiguration("/child-config.xml") }) public class ContextHierarchyTests { - // class body... + // class body... } @WebAppConfiguration @@ -568,7 +567,7 @@ public class ContextHierarchyTests { @ContextConfiguration(classes = WebConfig.class) }) public class WebIntegrationTests { - // class body... + // class body... } If you need to merge or override the configuration for a given @@ -594,19 +593,24 @@ public class WebIntegrationTests { @ContextConfiguration @ActiveProfiles("dev") public class DeveloperTests { - // class body... + // class body... } @ContextConfiguration @ActiveProfiles({"dev", "integration"}) public class DeveloperIntegrationTests { - // class body... + // class body... } @ActiveProfiles provides support for inheriting active bean definition - profiles declared by superclasses by default. + profiles declared by superclasses by default. It is also possible + to resolve active bean definition profiles programmatically by + implementing a custom + ActiveProfilesResolver and + registering it via the resolver attribute of + @ActiveProfiles. See @@ -649,19 +653,20 @@ public class DeveloperIntegrationTests { @DirtiesContext public class ContextDirtyingTests { - // some tests that result in the Spring container being dirtied + // some tests that result in the Spring container being dirtied } After each test method in the current test class, when declared on a class with class mode set to - AFTER_EACH_TEST_METHOD.@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) + AFTER_EACH_TEST_METHOD. + + @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) public class ContextDirtyingTests { - // some tests that result in the Spring container being dirtied -} + // some tests that result in the Spring container being dirtied +} @@ -670,7 +675,7 @@ public class ContextDirtyingTests { @DirtiesContext @Test public void testProcessWhichDirtiesAppCtx() { - // some logic that results in the Spring container being dirtied + // some logic that results in the Spring container being dirtied } @@ -696,7 +701,7 @@ public void testProcessWhichDirtiesAppCtx() { @ContextConfiguration("/child-config.xml") }) public class BaseTests { - // class body... + // class body... } public class ExtendedTests extends BaseTests { @@ -704,7 +709,7 @@ public class ExtendedTests extends BaseTests { @Test @DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL) public void test() { - // some logic that results in the child context being dirtied + // some logic that results in the child context being dirtied } } @@ -729,7 +734,7 @@ public class ExtendedTests extends BaseTests { @ContextConfiguration @TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) public class CustomTestExecutionListenerTests { - // class body... + // class body... } @TestExecutionListeners @@ -761,7 +766,7 @@ public class CustomTestExecutionListenerTests { @TransactionConfiguration(transactionManager = "txMgr", defaultRollback = false) public class CustomConfiguredTransactionalTests { - // class body... + // class body... } @@ -793,7 +798,7 @@ public class CustomConfiguredTransactionalTests { @Rollback(false) @Test public void testProcessWithoutRollback() { - // ... + // ... } @@ -809,7 +814,7 @@ public void testProcessWithoutRollback() { @BeforeTransaction public void beforeTransaction() { - // logic to be executed before a transaction is started + // logic to be executed before a transaction is started } @@ -825,7 +830,7 @@ public void testProcessWithoutRollback() { @AfterTransaction public void afterTransaction() { - // logic to be executed after a transaction has ended + // logic to be executed after a transaction has ended } @@ -944,7 +949,7 @@ public void testProcessWithoutRollback() { role="bold">name="java.vendor", value="Sun Microsystems Inc.") @Test public void testProcessWhichRunsOnlyOnSunJvm() { - // some logic that should run only on Java VMs from Sun Microsystems + // some logic that should run only on Java VMs from Sun Microsystems } Alternatively, you can configure @@ -957,7 +962,7 @@ public void testProcessWhichRunsOnlyOnSunJvm() { role="bold">name="test-groups", values={"unit-tests", "integration-tests"}) @Test public void testProcessWhichRunsForUnitOrIntegrationTestGroups() { - // some logic that should run only for unit and integration test groups + // some logic that should run only for unit and integration test groups } @@ -977,7 +982,7 @@ public void testProcessWhichRunsForUnitOrIntegrationTestGroups() { @ProfileValueSourceConfiguration(CustomProfileValueSource.class) public class CustomProfileValueSourceTests { - // class body... + // class body... } @@ -997,7 +1002,7 @@ public class CustomProfileValueSourceTests { @Timed(millis=1000) public void testProcessWithOneSecondTimeout() { - // some logic that should not take longer than 1 second to execute + // some logic that should not take longer than 1 second to execute } Spring's @Timed annotation has @@ -1030,7 +1035,7 @@ public void testProcessWithOneSecondTimeout() { @Repeat(10) @Test public void testProcessRepeatedly() { - // ... + // ... } @@ -1286,7 +1291,7 @@ public class MyTest { @Autowired private ApplicationContext applicationContext; - // class body... + // class body... } Similarly, if your test is configured to load a @@ -1301,7 +1306,7 @@ public class MyWebAppTest { @Autowired private WebApplicationContext wac; - // class body... + // class body... } Dependency injection via @@ -1355,11 +1360,11 @@ public class MyWebAppTest { is. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from "/app-config.xml" and -// "/test-config.xml" in the root of the classpath +// ApplicationContext will be loaded from "/app-config.xml" and +// "/test-config.xml" in the root of the classpath @ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) public class MyTest { - // class body... + // class body... } @ContextConfiguration supports an @@ -1374,7 +1379,7 @@ public class MyTest { @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({"/app-config.xml", "/test-config.xml"}) public class MyTest { - // class body... + // class body... } If you omit both the locations and @@ -1391,11 +1396,11 @@ public class MyTest { package com.example; @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from -// "classpath:/com/example/MyTest-context.xml" +// ApplicationContext will be loaded from +// "classpath:/com/example/MyTest-context.xml" @ContextConfiguration public class MyTest { - // class body... + // class body... }
@@ -1410,10 +1415,10 @@ public class MyTest { references to annotated classes. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from AppConfig and TestConfig +// ApplicationContext will be loaded from AppConfig and TestConfig @ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) public class MyTest { - // class body... + // class body... } If you omit the classes attribute from the @@ -1433,19 +1438,19 @@ public class MyTest { configuration class if desired. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from the -// static inner Config class +// ApplicationContext will be loaded from the +// static inner Config class @ContextConfiguration public class OrderServiceTest { @Configuration static class Config { - // this bean will be injected into the OrderServiceTest class + // this bean will be injected into the OrderServiceTest class @Bean public OrderService orderService() { OrderService orderService = new OrderServiceImpl(); - // set properties, etc. + // set properties, etc. return orderService; } } @@ -1455,7 +1460,7 @@ public class OrderServiceTest { @Test public void testOrderService() { - // test the orderService + // test the orderService } } @@ -1520,13 +1525,13 @@ public class OrderServiceTest { Spring's @Order annotation. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from TestConfig -// and initialized by TestAppCtxInitializer +// ApplicationContext will be loaded from TestConfig +// and initialized by TestAppCtxInitializer @ContextConfiguration( classes = TestConfig.class, initializers = TestAppCtxInitializer.class) public class MyTest { - // class body... + // class body... } It is also possible to omit the declaration of XML configuration @@ -1539,11 +1544,11 @@ public class MyTest { or configuration classes. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be initialized by EntireAppInitializer -// which presumably registers beans in the context +// ApplicationContext will be initialized by EntireAppInitializer +// which presumably registers beans in the context @ContextConfiguration(initializers = EntireAppInitializer.class) public class MyTest { - // class body... + // class body... } @@ -1585,18 +1590,18 @@ public class MyTest { "base-config.xml". @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from "/base-config.xml" -// in the root of the classpath +// ApplicationContext will be loaded from "/base-config.xml" +// in the root of the classpath @ContextConfiguration("/base-config.xml") public class BaseTest { - // class body... + // class body... } -// ApplicationContext will be loaded from "/base-config.xml" and -// "/extended-config.xml" in the root of the classpath +// ApplicationContext will be loaded from "/base-config.xml" and +// "/extended-config.xml" in the root of the classpath @ContextConfiguration("/extended-config.xml") public class ExtendedTest extends BaseTest { - // class body... + // class body... } Similarly, in the following example that uses annotated classes, @@ -1609,16 +1614,16 @@ public class ExtendedTest extends BaseTest { BaseConfig. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be loaded from BaseConfig +// ApplicationContext will be loaded from BaseConfig @ContextConfiguration(classes = BaseConfig.class) public class BaseTest { - // class body... + // class body... } -// ApplicationContext will be loaded from BaseConfig and ExtendedConfig +// ApplicationContext will be loaded from BaseConfig and ExtendedConfig @ContextConfiguration(classes = ExtendedConfig.class) public class ExtendedTest extends BaseTest { - // class body... + // class body... } In the following example that uses context initializers, the @@ -1632,17 +1637,17 @@ public class ExtendedTest extends BaseTest { Spring's @Order annotation. @RunWith(SpringJUnit4ClassRunner.class) -// ApplicationContext will be initialized by BaseInitializer +// ApplicationContext will be initialized by BaseInitializer @ContextConfiguration(initializers=BaseInitializer.class) public class BaseTest { - // class body... + // class body... } -// ApplicationContext will be initialized by BaseInitializer -// and ExtendedInitializer +// ApplicationContext will be initialized by BaseInitializer +// and ExtendedInitializer @ContextConfiguration(initializers=ExtendedInitializer.class) public class ExtendedTest extends BaseTest { - // class body... + // class body... } @@ -1814,7 +1819,7 @@ public class TransferServiceTest { @Test public void testTransferService() { - // test the transferService + // test the transferService } } @@ -1849,7 +1854,113 @@ public class TransferServiceTest { @ContextConfiguration annotation. The body of the test class itself remains completely unchanged. - + It is often the case that a single set of profiles is used + across multiple test classes within a given project. Thus, to avoid + duplicate declarations of the + @ActiveProfiles annotation it is + possible to declare @ActiveProfiles + once on a base class, and subclasses will automatically inherit the + @ActiveProfiles configuration from the + base class. In the following example, the declaration of + @ActiveProfiles (as well as other + annotations) has been moved to an abstract superclass, + AbstractIntegrationTest. + + package com.bank.service; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration( + classes = { + TransferServiceConfig.class, + StandaloneDataConfig.class, + JndiDataConfig.class}) +@ActiveProfiles("dev") +public abstract class AbstractIntegrationTest { +} + + package com.bank.service; + +// "dev" profile inherited from superclass +public class TransferServiceTest extends AbstractIntegrationTest { + + @Autowired + private TransferService transferService; + + @Test + public void testTransferService() { + // test the transferService + } +} + + @ActiveProfiles also supports an + inheritProfiles attribute that can be used to + disable the inheritance of active profiles. + + package com.bank.service; + +// "dev" profile overridden with "production" +@ActiveProfiles(profiles = "production", inheritProfiles = false) +public class ProductionTransferServiceTest extends AbstractIntegrationTest { + // test body +} + + Furthermore, it is sometimes necessary to resolve active + profiles for tests programmatically instead of + declaratively — for example, based on: + + + + the current operating system + + + + whether tests are being executed on a continuous integration + build server + + + + the presence of certain environment variables + + + + the presence of custom class-level annotations + + + + etc. + + + + To resolve active bean definition profiles programmatically, + simply implement a custom + ActiveProfilesResolver and register it + via the resolver attribute of + @ActiveProfiles. The following example + demonstrates how to implement and register a custom + OperatingSystemActiveProfilesResolver. For + further information, refer to the respective Javadoc. + + package com.bank.service; + +// "dev" profile overridden programmatically via a custom resolver +@ActiveProfiles( + resolver = OperatingSystemActiveProfilesResolver.class, + inheritProfiles = false) +public class TransferServiceTest extends AbstractIntegrationTest { + // test body +} + + package com.bank.service.test; + +public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver { + + @Override + String[] resolve(Class<?> testClass) { + String profile = ...; + // determine the value of profile based on the operating system + return new String[] {profile}; + } +}
@@ -2416,11 +2527,11 @@ public class ExtendedTests extends BaseTests {} injection. @RunWith(SpringJUnit4ClassRunner.class) -// specifies the Spring configuration to load for this test fixture +// specifies the Spring configuration to load for this test fixture @ContextConfiguration("repository-config.xml") public class HibernateTitleRepositoryTests { - // this instance will be dependency injected by type + // this instance will be dependency injected by type @Autowired private HibernateTitleRepository titleRepository; @@ -2436,11 +2547,11 @@ public class HibernateTitleRepositoryTests { below. @RunWith(SpringJUnit4ClassRunner.class) -// specifies the Spring configuration to load for this test fixture +// specifies the Spring configuration to load for this test fixture @ContextConfiguration("repository-config.xml") public class HibernateTitleRepositoryTests { - // this instance will be dependency injected by type + // this instance will be dependency injected by type private HibernateTitleRepository titleRepository; @Autowired @@ -2490,7 +2601,7 @@ public class HibernateTitleRepositoryTests { specific target bean as follows, but make sure to delegate to the overridden method in the superclass as well. - // ... + // ... @Autowired @Override @@ -2498,7 +2609,7 @@ public class HibernateTitleRepositoryTests { super.setDataSource(dataSource); } -// ... +// ... The specified qualifier value indicates the specific DataSource bean to inject, narrowing @@ -2749,29 +2860,29 @@ public class FictitiousTransactionalTest { @BeforeTransaction public void verifyInitialDatabaseState() { - // logic to verify the initial state before a transaction is started + // logic to verify the initial state before a transaction is started } @Before public void setUpTestDataWithinTransaction() { - // set up test data within the transaction + // set up test data within the transaction } @Test - // overrides the class-level defaultRollback setting + // overrides the class-level defaultRollback setting @Rollback(true) public void modifyDatabaseWithinTransaction() { - // logic which uses the test data and modifies database state + // logic which uses the test data and modifies database state } @After public void tearDownWithinTransaction() { - // execute "tear down" logic within the transaction + // execute "tear down" logic within the transaction } @AfterTransaction public void verifyFinalDatabaseState() { - // logic to verify the final state after transaction has rolled back + // logic to verify the final state after transaction has rolled back } } @@ -2793,7 +2904,7 @@ public class FictitiousTransactionalTest { frameworks that maintain an in-memory unit of work. - // ... + // ... @Autowired private SessionFactory sessionFactory; @@ -2812,7 +2923,7 @@ public void updateWithSessionFlush() { sessionFactory.getCurrentSession().flush(); } -// ... +// ...
@@ -2922,7 +3033,7 @@ public class SimpleTest { @Test public void testMethod() { - // execute test logic... + // execute test logic... } } @@ -3226,8 +3337,7 @@ public class MyWebTests { <bean id="accountService" class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="org.example.AccountService"/> -</bean> - +</bean> Then you can inject the mock service into the test in order set up and verify expectations: @@ -3274,26 +3384,22 @@ public class AccountTests { additional builder-style methods corresponding to properties of MockHttpServletRequest. For example: - mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON)); - + mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON)); In addition to all the HTTP methods, you can also perform file upload requests, which internally creates an instance of MockMultipartHttpServletRequest: - mockMvc.perform(fileUpload("/doc").file("a1", "ABC".getBytes("UTF-8"))); - + mockMvc.perform(fileUpload("/doc").file("a1", "ABC".getBytes("UTF-8"))); Query string parameters can be specified in the URI template: - mockMvc.perform(get("/hotels?foo={foo}", "bar")); - + mockMvc.perform(get("/hotels?foo={foo}", "bar")); Or by adding Servlet request parameters: - mockMvc.perform(get("/hotels").param("foo", "bar")); - + mockMvc.perform(get("/hotels").param("foo", "bar")); If application code relies on Servlet request parameters, and doesn't check the query string, as is most often the case, then it @@ -3308,8 +3414,7 @@ public class AccountTests { servletPath accordingly so that request mappings will work: - mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main")) - + mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main")) Looking at the above example, it would be cumbersome to set the contextPath and servletPath with every performed request. That's why @@ -3326,9 +3431,7 @@ public class AccountTests { .defaultRequest(get("/") .contextPath("/app").servletPath("/main") .accept(MediaType.APPLICATION_JSON).build(); - } - -} + } The above properties will apply to every request performed through the MockMvc. If the same property is @@ -3345,8 +3448,7 @@ public class AccountTests { .andExpect(..) after call to perform the request: - mockMvc.perform(get("/accounts/1")).andExpect(status().isOk()); - + mockMvc.perform(get("/accounts/1")).andExpect(status().isOk()); MockMvcResultMatchers.* defines a number of static members, some of which return types with additional methods, @@ -3369,8 +3471,7 @@ public class AccountTests { mockMvc.perform(post("/persons")) .andExpect(status().isOk()) - .andExpect(model().attributeHasErrors("person")); - + .andExpect(model().attributeHasErrors("person")); Many times when writing tests, it's useful to dump the result of the performed request. This can be done as follows, where @@ -3518,7 +3619,7 @@ mockServer.verify(); shown below: import static org.junit.Assert.assertEquals; -// import ... +// import ... @ContextConfiguration public abstract class AbstractClinicTests extends AbstractTransactionalJUnit4SpringContextTests { @@ -3535,10 +3636,10 @@ public abstract class AbstractClinicTests extends Abstract assertEquals("Leary", v1.getLastName()); assertEquals(1, v1.getNrOfSpecialties()); assertEquals("radiology", (v1.getSpecialties().get(0)).getName()); - // ... + // ... } - // ... + // ... } Notes: @@ -3608,8 +3709,7 @@ public abstract class AbstractClinicTests extends Abstract AbstractClinicTests-context.xml. @ContextConfiguration -public class HibernateClinicTests extends AbstractClinicTests { } - +public class HibernateClinicTests extends AbstractClinicTests { } In a large-scale application, the Spring configuration is often split across multiple files. Consequently, configuration locations are