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