diff options
author | Emmanuel Bourg <ebourg@apache.org> | 2016-08-03 19:55:01 +0200 |
---|---|---|
committer | Emmanuel Bourg <ebourg@apache.org> | 2016-08-03 19:55:01 +0200 |
commit | 75a721d1019da2a2fa86e24ff439df4a224e5b19 (patch) | |
tree | 2c44c00ce2c8641cccad177177e5682e187a17ea /spring-test | |
parent | 9eaca6a06af3cbceb3754de19d477be770614265 (diff) |
Imported Upstream version 4.3.2
Diffstat (limited to 'spring-test')
203 files changed, 6595 insertions, 1813 deletions
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java index 01840d66..d1a8c37c 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java @@ -390,8 +390,8 @@ public class MockHttpServletRequest implements HttpServletRequest { if (contentType != null) { try { MediaType mediaType = MediaType.parseMediaType(contentType); - if (mediaType.getCharSet() != null) { - this.characterEncoding = mediaType.getCharSet().name(); + if (mediaType.getCharset() != null) { + this.characterEncoding = mediaType.getCharset().name(); } } catch (Exception ex) { diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java index 5fc724b8..1d5b838d 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java @@ -236,8 +236,8 @@ public class MockHttpServletResponse implements HttpServletResponse { if (contentType != null) { try { MediaType mediaType = MediaType.parseMediaType(contentType); - if (mediaType.getCharSet() != null) { - this.characterEncoding = mediaType.getCharSet().name(); + if (mediaType.getCharset() != null) { + this.characterEncoding = mediaType.getCharset().name(); this.charset = true; } } diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java b/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java index 8be8a9a1..7bca348a 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import java.util.Collections; import java.util.Enumeration; import java.util.EventListener; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; @@ -144,7 +143,7 @@ public class MockServletContext implements ServletContext { private String servletContextName = "MockServletContext"; - private final Set<String> declaredRoles = new HashSet<String>(); + private final Set<String> declaredRoles = new LinkedHashSet<String>(); private Set<SessionTrackingMode> sessionTrackingModes; @@ -370,7 +369,6 @@ public class MockServletContext implements ServletContext { /** * Register a {@link RequestDispatcher} (typically a {@link MockRequestDispatcher}) * that acts as a wrapper for the named Servlet. - * * @param name the name of the wrapped Servlet * @param requestDispatcher the dispatcher that wraps the named Servlet * @see #getNamedDispatcher @@ -384,7 +382,6 @@ public class MockServletContext implements ServletContext { /** * Unregister the {@link RequestDispatcher} with the given name. - * * @param name the name of the dispatcher to unregister * @see #getNamedDispatcher * @see #registerNamedDispatcher @@ -429,13 +426,13 @@ public class MockServletContext implements ServletContext { @Override @Deprecated public Enumeration<Servlet> getServlets() { - return Collections.enumeration(new HashSet<Servlet>()); + return Collections.enumeration(Collections.<Servlet>emptySet()); } @Override @Deprecated public Enumeration<String> getServletNames() { - return Collections.enumeration(new HashSet<String>()); + return Collections.enumeration(Collections.<String>emptySet()); } @Override diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java index 66a663e0..716f71b0 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java +++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -21,7 +21,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.Enumeration; -import java.util.HashSet; import java.util.Set; import javax.portlet.PortletContext; import javax.portlet.PortletRequestDispatcher; @@ -156,7 +155,7 @@ public class ServletWrappingPortletContext implements PortletContext { @Override public Enumeration<String> getContainerRuntimeOptions() { - return Collections.enumeration(new HashSet<String>()); + return Collections.enumeration(Collections.<String>emptySet()); } } diff --git a/spring-test/src/main/java/org/springframework/test/annotation/Commit.java b/spring-test/src/main/java/org/springframework/test/annotation/Commit.java index e23c254f..98b3d96b 100644 --- a/spring-test/src/main/java/org/springframework/test/annotation/Commit.java +++ b/spring-test/src/main/java/org/springframework/test/annotation/Commit.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.test.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -51,6 +52,7 @@ import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented +@Inherited @Rollback(false) public @interface Commit { } diff --git a/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java index cd285587..e5f61e45 100644 --- a/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -24,7 +24,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * <p>{@code ProfileValueSourceConfiguration} is a class-level annotation which + * {@code ProfileValueSourceConfiguration} is a class-level annotation which * is used to specify what type of {@link ProfileValueSource} to use when * retrieving <em>profile values</em> configured via the {@link IfProfileValue * @IfProfileValue} annotation. @@ -38,17 +38,15 @@ import java.lang.annotation.Target; * @see IfProfileValue * @see ProfileValueUtils */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) public @interface ProfileValueSourceConfiguration { /** - * <p> * The type of {@link ProfileValueSource} to use when retrieving * <em>profile values</em>. - * </p> * * @see SystemProfileValueSource */ diff --git a/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java index 27ce7f64..17c60cad 100644 --- a/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java +++ b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -21,13 +21,12 @@ import java.lang.reflect.Method; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; -import static org.springframework.core.annotation.AnnotationUtils.*; - /** * General utility methods for working with <em>profile values</em>. * @@ -49,12 +48,10 @@ public abstract class ProfileValueUtils { * {@link ProfileValueSourceConfiguration * @ProfileValueSourceConfiguration} annotation and instantiates a new * instance of that type. - * <p> - * If {@link ProfileValueSourceConfiguration + * <p>If {@link ProfileValueSourceConfiguration * @ProfileValueSourceConfiguration} is not present on the specified * class or if a custom {@link ProfileValueSource} is not declared, the * default {@link SystemProfileValueSource} will be returned instead. - * * @param testClass The test class for which the ProfileValueSource should * be retrieved * @return the configured (or default) ProfileValueSource for the specified @@ -66,10 +63,10 @@ public abstract class ProfileValueUtils { Assert.notNull(testClass, "testClass must not be null"); Class<ProfileValueSourceConfiguration> annotationType = ProfileValueSourceConfiguration.class; - ProfileValueSourceConfiguration config = findAnnotation(testClass, annotationType); + ProfileValueSourceConfiguration config = AnnotatedElementUtils.findMergedAnnotation(testClass, annotationType); if (logger.isDebugEnabled()) { - logger.debug("Retrieved @ProfileValueSourceConfiguration [" + config + "] for test class [" - + testClass.getName() + "]"); + logger.debug("Retrieved @ProfileValueSourceConfiguration [" + config + "] for test class [" + + testClass.getName() + "]"); } Class<? extends ProfileValueSource> profileValueSourceType; @@ -80,8 +77,8 @@ public abstract class ProfileValueUtils { profileValueSourceType = (Class<? extends ProfileValueSource>) AnnotationUtils.getDefaultValue(annotationType); } if (logger.isDebugEnabled()) { - logger.debug("Retrieved ProfileValueSource type [" + profileValueSourceType + "] for class [" - + testClass.getName() + "]"); + logger.debug("Retrieved ProfileValueSource type [" + profileValueSourceType + "] for class [" + + testClass.getName() + "]"); } ProfileValueSource profileValueSource; @@ -92,10 +89,10 @@ public abstract class ProfileValueUtils { try { profileValueSource = profileValueSourceType.newInstance(); } - catch (Exception e) { + catch (Exception ex) { if (logger.isWarnEnabled()) { - logger.warn("Could not instantiate a ProfileValueSource of type [" + profileValueSourceType - + "] for class [" + testClass.getName() + "]: using default.", e); + logger.warn("Could not instantiate a ProfileValueSource of type [" + profileValueSourceType + + "] for class [" + testClass.getName() + "]: using default.", ex); } profileValueSource = SystemProfileValueSource.getInstance(); } @@ -108,16 +105,14 @@ public abstract class ProfileValueUtils { * Determine if the supplied {@code testClass} is <em>enabled</em> in * the current environment, as specified by the {@link IfProfileValue * @IfProfileValue} annotation at the class level. - * <p> - * Defaults to {@code true} if no {@link IfProfileValue + * <p>Defaults to {@code true} if no {@link IfProfileValue * @IfProfileValue} annotation is declared. - * * @param testClass the test class * @return {@code true} if the test is <em>enabled</em> in the current * environment */ public static boolean isTestEnabledInThisEnvironment(Class<?> testClass) { - IfProfileValue ifProfileValue = findAnnotation(testClass, IfProfileValue.class); + IfProfileValue ifProfileValue = AnnotatedElementUtils.findMergedAnnotation(testClass, IfProfileValue.class); return isTestEnabledInThisEnvironment(retrieveProfileValueSource(testClass), ifProfileValue); } @@ -127,10 +122,8 @@ public abstract class ProfileValueUtils { * @IfProfileValue} annotation, which may be declared on the test * method itself or at the class level. Class-level usage overrides * method-level usage. - * <p> - * Defaults to {@code true} if no {@link IfProfileValue + * <p>Defaults to {@code true} if no {@link IfProfileValue * @IfProfileValue} annotation is declared. - * * @param testMethod the test method * @param testClass the test class * @return {@code true} if the test is <em>enabled</em> in the current @@ -146,10 +139,8 @@ public abstract class ProfileValueUtils { * @IfProfileValue} annotation, which may be declared on the test * method itself or at the class level. Class-level usage overrides * method-level usage. - * <p> - * Defaults to {@code true} if no {@link IfProfileValue + * <p>Defaults to {@code true} if no {@link IfProfileValue * @IfProfileValue} annotation is declared. - * * @param profileValueSource the ProfileValueSource to use to determine if * the test is enabled * @param testMethod the test method @@ -160,11 +151,11 @@ public abstract class ProfileValueUtils { public static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource, Method testMethod, Class<?> testClass) { - IfProfileValue ifProfileValue = findAnnotation(testClass, IfProfileValue.class); + IfProfileValue ifProfileValue = AnnotatedElementUtils.findMergedAnnotation(testClass, IfProfileValue.class); boolean classLevelEnabled = isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue); if (classLevelEnabled) { - ifProfileValue = findAnnotation(testMethod, IfProfileValue.class); + ifProfileValue = AnnotatedElementUtils.findMergedAnnotation(testMethod, IfProfileValue.class); return isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue); } @@ -175,7 +166,6 @@ public abstract class ProfileValueUtils { * Determine if the {@code value} (or one of the {@code values}) * in the supplied {@link IfProfileValue @IfProfileValue} annotation is * <em>enabled</em> in the current environment. - * * @param profileValueSource the ProfileValueSource to use to determine if * the test is enabled * @param ifProfileValue the annotation to introspect; may be @@ -195,8 +185,8 @@ public abstract class ProfileValueUtils { String[] annotatedValues = ifProfileValue.values(); if (StringUtils.hasLength(ifProfileValue.value())) { if (annotatedValues.length > 0) { - throw new IllegalArgumentException("Setting both the 'value' and 'values' attributes " - + "of @IfProfileValue is not allowed: choose one or the other."); + throw new IllegalArgumentException("Setting both the 'value' and 'values' attributes " + + "of @IfProfileValue is not allowed: choose one or the other."); } annotatedValues = new String[] { ifProfileValue.value() }; } diff --git a/spring-test/src/main/java/org/springframework/test/annotation/Rollback.java b/spring-test/src/main/java/org/springframework/test/annotation/Rollback.java index 37bf2102..c1f06e64 100644 --- a/spring-test/src/main/java/org/springframework/test/annotation/Rollback.java +++ b/spring-test/src/main/java/org/springframework/test/annotation/Rollback.java @@ -18,6 +18,7 @@ package org.springframework.test.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -56,6 +57,7 @@ import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented +@Inherited public @interface Rollback { /** diff --git a/spring-test/src/main/java/org/springframework/test/annotation/TestAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/annotation/TestAnnotationUtils.java index 5f4eb1c2..a2b33faa 100644 --- a/spring-test/src/main/java/org/springframework/test/annotation/TestAnnotationUtils.java +++ b/spring-test/src/main/java/org/springframework/test/annotation/TestAnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ package org.springframework.test.annotation; import java.lang.reflect.Method; import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.AnnotationUtils; /** * Collection of utility methods for working with Spring's core testing annotations. @@ -52,7 +51,7 @@ public class TestAnnotationUtils { * not annotated with {@code @Repeat} */ public static int getRepeatCount(Method method) { - Repeat repeat = AnnotationUtils.findAnnotation(method, Repeat.class); + Repeat repeat = AnnotatedElementUtils.findMergedAnnotation(method, Repeat.class); if (repeat == null) { return 1; } 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 fc259102..54b08892 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 @@ -43,10 +43,10 @@ import org.springframework.core.annotation.AliasFor; * @see org.springframework.context.ApplicationContext * @see org.springframework.context.annotation.Profile */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) public @interface ActiveProfiles { /** diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java index c16d7def..22358071 100644 --- a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java @@ -17,22 +17,22 @@ package org.springframework.test.context; import java.lang.reflect.Constructor; -import java.util.List; +import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.util.ClassUtils; -import org.springframework.util.MultiValueMap; /** * {@code BootstrapUtils} is a collection of utility methods to assist with * bootstrapping the <em>Spring TestContext Framework</em>. * * @author Sam Brannen + * @author Phillip Webb * @since 4.1 * @see BootstrapWith * @see BootstrapContext @@ -49,6 +49,12 @@ abstract class BootstrapUtils { private static final String DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME = "org.springframework.test.context.support.DefaultTestContextBootstrapper"; + private static final String DEFAULT_WEB_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME = + "org.springframework.test.context.web.WebTestContextBootstrapper"; + + private static final String WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME = + "org.springframework.test.context.web.WebAppConfiguration"; + private static final Log logger = LogFactory.getLog(BootstrapUtils.class); @@ -103,51 +109,64 @@ abstract class BootstrapUtils { * <p>If the {@link BootstrapWith @BootstrapWith} annotation is present on * the test class, either directly or as a meta-annotation, then its * {@link BootstrapWith#value value} will be used as the bootstrapper type. - * Otherwise, the {@link org.springframework.test.context.support.DefaultTestContextBootstrapper - * DefaultTestContextBootstrapper} will be used. + * Otherwise, either the + * {@link org.springframework.test.context.support.DefaultTestContextBootstrapper + * DefaultTestContextBootstrapper} or the + * {@link org.springframework.test.context.web.WebTestContextBootstrapper + * WebTestContextBootstrapper} will be used, depending on the presence of + * {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}. * @param bootstrapContext the bootstrap context to use * @return a fully configured {@code TestContextBootstrapper} */ - @SuppressWarnings("unchecked") static TestContextBootstrapper resolveTestContextBootstrapper(BootstrapContext bootstrapContext) { Class<?> testClass = bootstrapContext.getTestClass(); - Class<? extends TestContextBootstrapper> clazz = null; + Class<?> clazz = null; try { - MultiValueMap<String, Object> attributesMultiMap = AnnotatedElementUtils.getAllAnnotationAttributes( - testClass, BootstrapWith.class.getName()); - List<Object> values = (attributesMultiMap == null ? null : attributesMultiMap.get(AnnotationUtils.VALUE)); - - if (values != null) { - if (values.size() != 1) { - throw new IllegalStateException(String.format("Configuration error: found multiple declarations of " + - "@BootstrapWith on test class [%s] with values %s", testClass.getName(), values)); - } - clazz = (Class<? extends TestContextBootstrapper>) values.get(0); - } - else { - clazz = (Class<? extends TestContextBootstrapper>) ClassUtils.forName( - DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, BootstrapUtils.class.getClassLoader()); + clazz = resolveExplicitTestContextBootstrapper(testClass); + if (clazz == null) { + clazz = resolveDefaultTestContextBootstrapper(testClass); } - if (logger.isDebugEnabled()) { logger.debug(String.format("Instantiating TestContextBootstrapper for test class [%s] from class [%s]", testClass.getName(), clazz.getName())); } - TestContextBootstrapper testContextBootstrapper = BeanUtils.instantiateClass(clazz, TestContextBootstrapper.class); testContextBootstrapper.setBootstrapContext(bootstrapContext); return testContextBootstrapper; } + catch (IllegalStateException ex) { + throw ex; + } catch (Throwable ex) { - if (ex instanceof IllegalStateException) { - throw (IllegalStateException) ex; - } throw new IllegalStateException("Could not load TestContextBootstrapper [" + clazz + "]. Specify @BootstrapWith's 'value' attribute or make the default bootstrapper class available.", ex); } } + private static Class<?> resolveExplicitTestContextBootstrapper(Class<?> testClass) { + Set<BootstrapWith> annotations = AnnotatedElementUtils.findAllMergedAnnotations(testClass, BootstrapWith.class); + if (annotations.size() < 1) { + return null; + } + if (annotations.size() > 1) { + throw new IllegalStateException(String.format( + "Configuration error: found multiple declarations of @BootstrapWith for test class [%s]: %s", + testClass.getName(), annotations)); + } + return annotations.iterator().next().value(); + } + + private static Class<?> resolveDefaultTestContextBootstrapper(Class<?> testClass) throws Exception { + ClassLoader classLoader = BootstrapUtils.class.getClassLoader(); + AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(testClass, + WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME, false, false); + if (attributes != null) { + return ClassUtils.forName(DEFAULT_WEB_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, classLoader); + } + return ClassUtils.forName(DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, classLoader); + } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java index 302ce42e..7cf742f6 100644 --- a/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java +++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapWith.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,10 +35,10 @@ import java.lang.annotation.Target; * @see BootstrapContext * @see TestContextBootstrapper */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) public @interface BootstrapWith { /** diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java index 9087bdbe..669adbd6 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,10 +83,10 @@ import org.springframework.core.annotation.AliasFor; * @see MergedContextConfiguration * @see org.springframework.context.ApplicationContext */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) public @interface ContextConfiguration { /** diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java index 0e0feb47..f32731df 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,7 @@ import org.springframework.util.StringUtils; * attributes declared via {@link ContextConfiguration @ContextConfiguration}. * * @author Sam Brannen + * @author Phillip Webb * @since 3.1 * @see ContextConfiguration * @see SmartContextLoader#processContextConfiguration(ContextConfigurationAttributes) @@ -41,6 +42,11 @@ import org.springframework.util.StringUtils; */ public class ContextConfigurationAttributes { + private static final String[] EMPTY_LOCATIONS = new String[0]; + + private static final Class<?>[] EMPTY_CLASSES = new Class<?>[0]; + + private static final Log logger = LogFactory.getLog(ContextConfigurationAttributes.class); private final Class<?> declaringClass; @@ -61,6 +67,17 @@ public class ContextConfigurationAttributes { /** + * Construct a new {@link ContextConfigurationAttributes} instance with default values. + * @param declaringClass the test class that declared {@code @ContextConfiguration}, + * either explicitly or implicitly + * @since 4.3 + */ + @SuppressWarnings("unchecked") + public ContextConfigurationAttributes(Class<?> declaringClass) { + this(declaringClass, EMPTY_LOCATIONS, EMPTY_CLASSES, false, (Class[]) EMPTY_CLASSES, true, ContextLoader.class); + } + + /** * Construct a new {@link ContextConfigurationAttributes} instance for the * supplied {@link ContextConfiguration @ContextConfiguration} annotation and * the {@linkplain Class test class} that declared it. @@ -84,8 +101,8 @@ public class ContextConfigurationAttributes { @SuppressWarnings("unchecked") public ContextConfigurationAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) { this(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getClassArray("classes"), annAttrs.getBoolean("inheritLocations"), - (Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[]) annAttrs.getClassArray("initializers"), - annAttrs.getBoolean("inheritInitializers"), annAttrs.getString("name"), (Class<? extends ContextLoader>) annAttrs.getClass("loader")); + (Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[]) annAttrs.getClassArray("initializers"), + annAttrs.getBoolean("inheritInitializers"), annAttrs.getString("name"), (Class<? extends ContextLoader>) annAttrs.getClass("loader")); } /** @@ -139,8 +156,8 @@ public class ContextConfigurationAttributes { if (!ObjectUtils.isEmpty(locations) && !ObjectUtils.isEmpty(classes) && logger.isDebugEnabled()) { logger.debug(String.format( "Test class [%s] has been configured with @ContextConfiguration's 'locations' (or 'value') %s " + - "and 'classes' %s attributes. Most SmartContextLoader implementations support " + - "only one declaration of resources per @ContextConfiguration annotation.", + "and 'classes' %s attributes. Most SmartContextLoader implementations support " + + "only one declaration of resources per @ContextConfiguration annotation.", declaringClass.getName(), ObjectUtils.nullSafeToString(locations), ObjectUtils.nullSafeToString(classes))); } @@ -158,7 +175,8 @@ public class ContextConfigurationAttributes { /** * Get the {@linkplain Class class} that declared the - * {@link ContextConfiguration @ContextConfiguration} annotation. + * {@link ContextConfiguration @ContextConfiguration} annotation, either explicitly + * or implicitly. * @return the declaring class (never {@code null}) */ public Class<?> getDeclaringClass() { diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java new file mode 100644 index 00000000..d9462faa --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizer.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2016 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; + +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Strategy interface for customizing {@link ConfigurableApplicationContext + * application contexts} that are created and managed by the <em>Spring + * TestContext Framework</em>. + * + * <p>Customizers are created by {@link ContextCustomizerFactory} implementations. + * + * <p>Implementations must implement correct {@code equals} and {@code hashCode} + * methods since customizers form part of the {@link MergedContextConfiguration} + * which is used as a cache key. + * + * @author Phillip Webb + * @author Sam Brannen + * @since 4.3 + * @see ContextCustomizerFactory + * @see org.springframework.test.context.support.AbstractContextLoader#customizeContext + */ +public interface ContextCustomizer { + + /** + * Customize the supplied {@code ConfigurableApplicationContext} <em>after</em> + * bean definitions have been loaded into the context but <em>before</em> the + * context has been refreshed. + * @param context the context to customize + * @param mergedConfig the merged context configuration + */ + void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig); + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java new file mode 100644 index 00000000..9f07700a --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/ContextCustomizerFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2016 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; + +import java.util.List; + +/** + * Factory for creating {@link ContextCustomizer ContextCustomizers}. + * + * <p>Factories are invoked after {@link ContextLoader ContextLoaders} have + * processed context configuration attributes but before the + * {@link MergedContextConfiguration} is created. + * + * <p>By default, the Spring TestContext Framework will use the + * {@link org.springframework.core.io.support.SpringFactoriesLoader SpringFactoriesLoader} + * mechanism for loading factories configured in all {@code META-INF/spring.factories} + * files on the classpath. + * + * @author Phillip Webb + * @author Sam Brannen + * @since 4.3 + */ +public interface ContextCustomizerFactory { + + /** + * Create a {@link ContextCustomizer} that should be used to customize a + * {@link org.springframework.context.ConfigurableApplicationContext ConfigurableApplicationContext} + * before it is refreshed. + * @param testClass the test class + * @param configAttributes the list of context configuration attributes for + * the test class, ordered <em>bottom-up</em> (i.e., as if we were traversing + * up the class hierarchy); never {@code null} or empty + * @return a {@link ContextCustomizer} or {@code null} if no customizer should + * be used + */ + ContextCustomizer createContextCustomizer(Class<?> testClass, List<ContextConfigurationAttributes> configAttributes); + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java b/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java index 61b2fa8c..3a3d2859 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -139,10 +139,10 @@ import java.lang.annotation.Target; * @see ContextConfiguration * @see org.springframework.context.ApplicationContext */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) public @interface ContextHierarchy { /** diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java index 5090d7c5..45f31a18 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java @@ -55,6 +55,7 @@ import org.springframework.util.StringUtils; * that was loaded using properties of this {@code MergedContextConfiguration}. * * @author Sam Brannen + * @author Phillip Webb * @since 3.1 * @see ContextConfiguration * @see ContextHierarchy @@ -74,6 +75,8 @@ public class MergedContextConfiguration implements Serializable { private static final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> EMPTY_INITIALIZER_CLASSES = Collections.<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> emptySet(); + private static final Set<ContextCustomizer> EMPTY_CONTEXT_CUSTOMIZERS = Collections.<ContextCustomizer> emptySet(); + private final Class<?> testClass; @@ -89,6 +92,8 @@ public class MergedContextConfiguration implements Serializable { private final String[] propertySourceProperties; + private final Set<ContextCustomizer> contextCustomizers; + private final ContextLoader contextLoader; private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate; @@ -111,6 +116,11 @@ public class MergedContextConfiguration implements Serializable { Collections.unmodifiableSet(contextInitializerClasses) : EMPTY_INITIALIZER_CLASSES); } + private static Set<ContextCustomizer> processContextCustomizers(Set<ContextCustomizer> contextCustomizers) { + return (contextCustomizers != null ? + Collections.unmodifiableSet(contextCustomizers) : EMPTY_CONTEXT_CUSTOMIZERS); + } + private static String[] processActiveProfiles(String[] activeProfiles) { if (activeProfiles == null) { return EMPTY_STRING_ARRAY; @@ -201,8 +211,8 @@ public class MergedContextConfiguration implements Serializable { public MergedContextConfiguration(MergedContextConfiguration mergedConfig) { this(mergedConfig.testClass, mergedConfig.locations, mergedConfig.classes, mergedConfig.contextInitializerClasses, mergedConfig.activeProfiles, mergedConfig.propertySourceLocations, - mergedConfig.propertySourceProperties, mergedConfig.contextLoader, - mergedConfig.cacheAwareContextLoaderDelegate, mergedConfig.parent); + mergedConfig.propertySourceProperties, mergedConfig.contextCustomizers, + mergedConfig.contextLoader, mergedConfig.cacheAwareContextLoaderDelegate, mergedConfig.parent); } /** @@ -233,6 +243,41 @@ public class MergedContextConfiguration implements Serializable { String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { + this(testClass, locations, classes, contextInitializerClasses, activeProfiles, + propertySourceLocations, propertySourceProperties, + EMPTY_CONTEXT_CUSTOMIZERS, contextLoader, + cacheAwareContextLoaderDelegate, parent); + } + + /** + * Create a new {@code MergedContextConfiguration} instance for the + * supplied parameters. + * <p>If a {@code null} value is supplied for {@code locations}, + * {@code classes}, {@code activeProfiles}, {@code propertySourceLocations}, + * or {@code propertySourceProperties} an empty array will be stored instead. + * If a {@code null} value is supplied for {@code contextInitializerClasses} + * or {@code contextCustomizers}, an empty set will be stored instead. + * Furthermore, active profiles will be sorted, and duplicate profiles + * will be removed. + * @param testClass the test class for which the configuration was merged + * @param locations the merged context resource locations + * @param classes the merged annotated classes + * @param contextInitializerClasses the merged context initializer classes + * @param activeProfiles the merged active bean definition profiles + * @param propertySourceLocations the merged {@code PropertySource} locations + * @param propertySourceProperties the merged {@code PropertySource} properties + * @param contextCustomizers the context customizers + * @param contextLoader the resolved {@code ContextLoader} + * @param cacheAwareContextLoaderDelegate a cache-aware context loader + * delegate with which to retrieve the parent context + * @param parent the parent configuration or {@code null} if there is no parent + * @since 4.3 + */ + public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes, + Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses, + String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, + Set<ContextCustomizer> contextCustomizers, ContextLoader contextLoader, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { this.testClass = testClass; this.locations = processStrings(locations); @@ -241,6 +286,7 @@ public class MergedContextConfiguration implements Serializable { this.activeProfiles = processActiveProfiles(activeProfiles); this.propertySourceLocations = processStrings(propertySourceLocations); this.propertySourceProperties = processStrings(propertySourceProperties); + this.contextCustomizers = processContextCustomizers(contextCustomizers); this.contextLoader = contextLoader; this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate; this.parent = parent; @@ -349,6 +395,14 @@ public class MergedContextConfiguration implements Serializable { } /** + * Get the merged {@link ContextCustomizer ContextCustomizers} that will be applied + * when the application context is loaded. + */ + public Set<ContextCustomizer> getContextCustomizers() { + return this.contextCustomizers; + } + + /** * Get the resolved {@link ContextLoader} for the {@linkplain #getTestClass() test class}. */ public ContextLoader getContextLoader() { @@ -424,6 +478,9 @@ public class MergedContextConfiguration implements Serializable { if (!Arrays.equals(this.propertySourceProperties, otherConfig.propertySourceProperties)) { return false; } + if (!this.contextCustomizers.equals(otherConfig.contextCustomizers)) { + return false; + } if (this.parent == null) { if (otherConfig.parent != null) { @@ -454,6 +511,7 @@ public class MergedContextConfiguration implements Serializable { result = 31 * result + Arrays.hashCode(this.activeProfiles); result = 31 * result + Arrays.hashCode(this.propertySourceLocations); result = 31 * result + Arrays.hashCode(this.propertySourceProperties); + result = 31 * result + this.contextCustomizers.hashCode(); result = 31 * result + (this.parent != null ? this.parent.hashCode() : 0); result = 31 * result + nullSafeToString(this.contextLoader).hashCode(); return result; @@ -466,6 +524,7 @@ public class MergedContextConfiguration implements Serializable { * {@linkplain #getActiveProfiles() active profiles}, * {@linkplain #getPropertySourceLocations() property source locations}, * {@linkplain #getPropertySourceProperties() property source properties}, + * {@linkplain #getContextCustomizers() context customizers}, * the name of the {@link #getContextLoader() ContextLoader}, and the * {@linkplain #getParent() parent configuration}. */ @@ -479,6 +538,7 @@ public class MergedContextConfiguration implements Serializable { .append("activeProfiles", ObjectUtils.nullSafeToString(this.activeProfiles)) .append("propertySourceLocations", ObjectUtils.nullSafeToString(this.propertySourceLocations)) .append("propertySourceProperties", ObjectUtils.nullSafeToString(this.propertySourceProperties)) + .append("contextCustomizers", this.contextCustomizers) .append("contextLoader", nullSafeToString(this.contextLoader)) .append("parent", this.parent) .toString(); diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java index 2ec2b7d4..905ed70b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestContextBootstrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,13 +31,14 @@ import java.util.List; * * <p>A custom bootstrapping strategy can be configured for a test class (or * test class hierarchy) via {@link BootstrapWith @BootstrapWith}, either - * directly or as a meta-annotation. See - * {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration} - * for an example. + * directly or as a meta-annotation. * - * <p>If a bootstrapper is not explicitly configured via {@code @BootstrapWith}, the - * {@link org.springframework.test.context.support.DefaultTestContextBootstrapper DefaultTestContextBootstrapper} - * will be used. + * <p>If a bootstrapper is not explicitly configured via {@code @BootstrapWith}, + * either the {@link org.springframework.test.context.support.DefaultTestContextBootstrapper + * DefaultTestContextBootstrapper} or the + * {@link org.springframework.test.context.web.WebTestContextBootstrapper + * WebTestContextBootstrapper} will be used, depending on the presence of + * {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}. * * <h3>Implementation Notes</h3> * diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java index 3fd77678..28979647 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * {@code TestContextManager} is the main entry point into the <em>Spring @@ -37,18 +38,18 @@ import org.springframework.util.Assert; * * <ul> * <li>{@link #beforeTestClass() before test class execution}: prior to any - * <em>before class methods</em> of a particular testing framework (e.g., JUnit - * 4's {@link org.junit.BeforeClass @BeforeClass})</li> + * <em>before class callbacks</em> of a particular testing framework (e.g., + * JUnit 4's {@link org.junit.BeforeClass @BeforeClass})</li> * <li>{@link #prepareTestInstance(Object) test instance preparation}: * immediately following instantiation of the test instance</li> * <li>{@link #beforeTestMethod(Object, Method) before test method execution}: - * prior to any <em>before methods</em> of a particular testing framework (e.g., - * JUnit 4's {@link org.junit.Before @Before})</li> + * prior to any <em>before method callbacks</em> of a particular testing framework + * (e.g., JUnit 4's {@link org.junit.Before @Before})</li> * <li>{@link #afterTestMethod(Object, Method, Throwable) after test method - * execution}: after any <em>after methods</em> of a particular testing + * execution}: after any <em>after method callbacks</em> of a particular testing * framework (e.g., JUnit 4's {@link org.junit.After @After})</li> * <li>{@link #afterTestClass() after test class execution}: after any - * <em>after class methods</em> of a particular testing framework (e.g., JUnit + * <em>after class callbacks</em> of a particular testing framework (e.g., JUnit * 4's {@link org.junit.AfterClass @AfterClass})</li> * </ul> * @@ -78,7 +79,6 @@ import org.springframework.util.Assert; * @see TestExecutionListeners * @see ContextConfiguration * @see ContextHierarchy - * @see org.springframework.test.context.transaction.TransactionConfiguration */ public class TestContextManager { @@ -124,7 +124,7 @@ public class TestContextManager { /** * Get the {@link TestContext} managed by this {@code TestContextManager}. */ - protected final TestContext getTestContext() { + public final TestContext getTestContext() { return this.testContext; } @@ -194,10 +194,12 @@ public class TestContextManager { try { testExecutionListener.beforeTestClass(getTestContext()); } - catch (Exception ex) { - logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + - "] to process 'before class' callback for test class [" + testClass + "]", ex); - throw ex; + catch (Throwable ex) { + if (logger.isWarnEnabled()) { + logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + + "] to process 'before class' callback for test class [" + testClass + "]", ex); + } + ReflectionUtils.rethrowException(ex); } } } @@ -227,10 +229,12 @@ public class TestContextManager { try { testExecutionListener.prepareTestInstance(getTestContext()); } - catch (Exception ex) { - logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener + - "] to prepare test instance [" + testInstance + "]", ex); - throw ex; + catch (Throwable ex) { + if (logger.isErrorEnabled()) { + logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener + + "] to prepare test instance [" + testInstance + "]", ex); + } + ReflectionUtils.rethrowException(ex); } } } @@ -264,11 +268,13 @@ public class TestContextManager { try { testExecutionListener.beforeTestMethod(getTestContext()); } - catch (Exception ex) { - logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + - "] to process 'before' execution of test method [" + testMethod + "] for test instance [" + - testInstance + "]", ex); - throw ex; + catch (Throwable ex) { + if (logger.isWarnEnabled()) { + logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + + "] to process 'before' execution of test method [" + testMethod + "] for test instance [" + + testInstance + "]", ex); + } + ReflectionUtils.rethrowException(ex); } } } @@ -305,24 +311,26 @@ public class TestContextManager { } getTestContext().updateState(testInstance, testMethod, exception); - Exception afterTestMethodException = null; + Throwable afterTestMethodException = null; // Traverse the TestExecutionListeners in reverse order to ensure proper // "wrapper"-style execution of listeners. for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) { try { testExecutionListener.afterTestMethod(getTestContext()); } - catch (Exception ex) { - logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + - "] to process 'after' execution for test: method [" + testMethod + "], instance [" + - testInstance + "], exception [" + exception + "]", ex); + catch (Throwable ex) { + if (logger.isWarnEnabled()) { + logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + + "] to process 'after' execution for test: method [" + testMethod + "], instance [" + + testInstance + "], exception [" + exception + "]", ex); + } if (afterTestMethodException == null) { afterTestMethodException = ex; } } } if (afterTestMethodException != null) { - throw afterTestMethodException; + ReflectionUtils.rethrowException(afterTestMethodException); } } @@ -347,23 +355,25 @@ public class TestContextManager { } getTestContext().updateState(null, null, null); - Exception afterTestClassException = null; + Throwable afterTestClassException = null; // Traverse the TestExecutionListeners in reverse order to ensure proper // "wrapper"-style execution of listeners. for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) { try { testExecutionListener.afterTestClass(getTestContext()); } - catch (Exception ex) { - logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + - "] to process 'after class' callback for test class [" + testClass + "]", ex); + catch (Throwable ex) { + if (logger.isWarnEnabled()) { + logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener + + "] to process 'after class' callback for test class [" + testClass + "]", ex); + } if (afterTestClassException == null) { afterTestClassException = ex; } } } if (afterTestClassException != null) { - throw afterTestClassException; + ReflectionUtils.rethrowException(afterTestClassException); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java index 81d61cb2..fbca021a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java @@ -36,6 +36,8 @@ package org.springframework.test.context; * <ul> * <li>{@link org.springframework.test.context.web.ServletTestExecutionListener * ServletTestExecutionListener}</li> + * <li>{@link org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener + * DirtiesContextBeforeModesTestExecutionListener}</li> * <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener * DependencyInjectionTestExecutionListener}</li> * <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener diff --git a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java index d99c48d7..d1db5c75 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java @@ -42,10 +42,10 @@ import org.springframework.core.annotation.AliasFor; * @see TestContextManager * @see ContextConfiguration */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) public @interface TestExecutionListeners { /** diff --git a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java index ec2528af..6937870b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java +++ b/spring-test/src/main/java/org/springframework/test/context/TestPropertySource.java @@ -82,10 +82,10 @@ import org.springframework.core.annotation.AliasFor; * @see org.springframework.core.env.PropertySource * @see org.springframework.context.annotation.PropertySource */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) public @interface TestPropertySource { /** diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java index 27902a6d..1f15c763 100644 --- a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java +++ b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCache.java @@ -26,7 +26,9 @@ import org.springframework.test.context.MergedContextConfiguration; * <em>Spring TestContext Framework</em>. * * <p>A {@code ContextCache} maintains a cache of {@code ApplicationContexts} - * keyed by {@link MergedContextConfiguration} instances. + * keyed by {@link MergedContextConfiguration} instances, potentially configured + * with a {@linkplain ContextCacheUtils#retrieveMaxCacheSize maximum size} and + * a custom eviction policy. * * <h3>Rationale</h3> * <p>Context caching can have significant performance benefits if context @@ -40,6 +42,7 @@ import org.springframework.test.context.MergedContextConfiguration; * @author Sam Brannen * @author Juergen Hoeller * @since 4.2 + * @see ContextCacheUtils#retrieveMaxCacheSize() */ public interface ContextCache { @@ -49,6 +52,25 @@ public interface ContextCache { */ String CONTEXT_CACHE_LOGGING_CATEGORY = "org.springframework.test.context.cache"; + /** + * The default maximum size of the context cache: {@value #DEFAULT_MAX_CONTEXT_CACHE_SIZE}. + * @since 4.3 + * @see #MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME + */ + int DEFAULT_MAX_CONTEXT_CACHE_SIZE = 32; + + /** + * System property used to configure the maximum size of the {@link ContextCache} + * as a positive integer. May alternatively be configured via the + * {@link org.springframework.core.SpringProperties} mechanism. + * <p>Note that implementations of {@code ContextCache} are not required to + * actually support a maximum cache size. Consult the documentation of the + * corresponding implementation for details. + * @since 4.3 + * @see #DEFAULT_MAX_CONTEXT_CACHE_SIZE + */ + String MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME = "spring.test.context.cache.maxSize"; + /** * Determine whether there is a cached context for the given key. diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java new file mode 100644 index 00000000..66427cee --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2016 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.cache; + +import org.springframework.core.SpringProperties; +import org.springframework.util.StringUtils; + +/** + * Collection of utilities for working with {@link ContextCache ContextCaches}. + * + * @author Sam Brannen + * @since 4.3 + */ +public abstract class ContextCacheUtils { + + /** + * Retrieve the maximum size of the {@link ContextCache}. + * <p>Uses {@link SpringProperties} to retrieve a system property or Spring + * property named {@code spring.test.context.cache.maxSize}. + * <p>Falls back to the value of the {@link ContextCache#DEFAULT_MAX_CONTEXT_CACHE_SIZE} + * if no such property has been set or if the property is not an integer. + * @return the maximum size of the context cache + * @see ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME + */ + public static int retrieveMaxCacheSize() { + try { + String maxSize = SpringProperties.getProperty(ContextCache.MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME); + if (StringUtils.hasText(maxSize)) { + return Integer.parseInt(maxSize.trim()); + } + } + catch (Exception ex) { + // ignore + } + + // Fallback + return ContextCache.DEFAULT_MAX_CONTEXT_CACHE_SIZE; + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java index 09678135..d4aaa441 100644 --- a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java +++ b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultContextCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,9 @@ package org.springframework.test.context.cache; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -37,12 +39,18 @@ import org.springframework.util.Assert; /** * Default implementation of the {@link ContextCache} API. * - * <p>Uses {@link ConcurrentHashMap ConcurrentHashMaps} to cache - * {@link ApplicationContext} and {@link MergedContextConfiguration} instances. + * <p>Uses a synchronized {@link Map} configured with a maximum size + * and a <em>least recently used</em> (LRU) eviction policy to cache + * {@link ApplicationContext} instances. + * + * <p>The maximum size may be supplied as a {@linkplain #DefaultContextCache(int) + * constructor argument} or set via a system property or Spring property named + * {@code spring.test.context.cache.maxSize}. * * @author Sam Brannen * @author Juergen Hoeller * @since 2.5 + * @see ContextCacheUtils#retrieveMaxCacheSize() */ public class DefaultContextCache implements ContextCache { @@ -52,7 +60,7 @@ public class DefaultContextCache implements ContextCache { * Map of context keys to Spring {@code ApplicationContext} instances. */ private final Map<MergedContextConfiguration, ApplicationContext> contextMap = - new ConcurrentHashMap<MergedContextConfiguration, ApplicationContext>(64); + Collections.synchronizedMap(new LruCache(32, 0.75f)); /** * Map of parent keys to sets of children keys, representing a top-down <em>tree</em> @@ -61,7 +69,9 @@ public class DefaultContextCache implements ContextCache { * of other contexts. */ private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap = - new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64); + new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(32); + + private final int maxSize; private final AtomicInteger hitCount = new AtomicInteger(); @@ -69,6 +79,32 @@ public class DefaultContextCache implements ContextCache { /** + * Create a new {@code DefaultContextCache} using the maximum cache size + * obtained via {@link ContextCacheUtils#retrieveMaxCacheSize()}. + * @since 4.3 + * @see #DefaultContextCache(int) + * @see ContextCacheUtils#retrieveMaxCacheSize() + */ + public DefaultContextCache() { + this(ContextCacheUtils.retrieveMaxCacheSize()); + } + + /** + * Create a new {@code DefaultContextCache} using the supplied maximum + * cache size. + * @param maxSize the maximum cache size + * @throws IllegalArgumentException if the supplied {@code maxSize} value + * is not positive + * @since 4.3 + * @see #DefaultContextCache() + */ + public DefaultContextCache(int maxSize) { + Assert.isTrue(maxSize > 0, "'maxSize' must be positive"); + this.maxSize = maxSize; + } + + + /** * {@inheritDoc} */ @Override @@ -182,6 +218,13 @@ public class DefaultContextCache implements ContextCache { } /** + * Get the maximum size of this cache. + */ + public int getMaxSize() { + return this.maxSize; + } + + /** * {@inheritDoc} */ @Override @@ -210,7 +253,7 @@ public class DefaultContextCache implements ContextCache { */ @Override public void reset() { - synchronized (contextMap) { + synchronized (this.contextMap) { clear(); clearStatistics(); } @@ -221,7 +264,7 @@ public class DefaultContextCache implements ContextCache { */ @Override public void clear() { - synchronized (contextMap) { + synchronized (this.contextMap) { this.contextMap.clear(); this.hierarchyMap.clear(); } @@ -232,7 +275,7 @@ public class DefaultContextCache implements ContextCache { */ @Override public void clearStatistics() { - synchronized (contextMap) { + synchronized (this.contextMap) { this.hitCount.set(0); this.missCount.set(0); } @@ -259,10 +302,44 @@ public class DefaultContextCache implements ContextCache { public String toString() { return new ToStringCreator(this) .append("size", size()) + .append("maxSize", getMaxSize()) .append("parentContextCount", getParentContextCount()) .append("hitCount", getHitCount()) .append("missCount", getMissCount()) .toString(); } + + /** + * Simple cache implementation based on {@link LinkedHashMap} with a maximum + * size and a <em>least recently used</em> (LRU) eviction policy that + * properly closes application contexts. + * @since 4.3 + */ + @SuppressWarnings("serial") + private class LruCache extends LinkedHashMap<MergedContextConfiguration, ApplicationContext> { + + /** + * Create a new {@code LruCache} with the supplied initial capacity + * and load factor. + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + */ + LruCache(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor, true); + } + + @Override + protected boolean removeEldestEntry(Map.Entry<MergedContextConfiguration, ApplicationContext> eldest) { + if (this.size() > DefaultContextCache.this.getMaxSize()) { + // Do NOT delete "DefaultContextCache.this."; otherwise, we accidentally + // invoke java.util.Map.remove(Object, Object). + DefaultContextCache.this.remove(eldest.getKey(), HierarchyMode.CURRENT_LEVEL); + } + + // Return false since we invoke a custom eviction algorithm. + return false; + } + } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java index 65fe0a83..5794dde3 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/Sql.java @@ -50,9 +50,7 @@ import org.springframework.core.annotation.AliasFor; * multiple instances of {@code @Sql}. * * <p>This annotation may be used as a <em>meta-annotation</em> to create custom - * <em>composed annotations</em>; however, attribute overrides are not currently - * supported for {@linkplain Repeatable repeatable} annotations that are used as - * meta-annotations. + * <em>composed annotations</em> with attribute overrides. * * @author Sam Brannen * @since 4.1 diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java index 71e0e1f6..c58867eb 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -129,10 +129,10 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen private void executeSqlScripts(TestContext testContext, ExecutionPhase executionPhase) throws Exception { boolean classLevel = false; - Set<Sql> sqlAnnotations = AnnotationUtils.getRepeatableAnnotations(testContext.getTestMethod(), Sql.class, + Set<Sql> sqlAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(testContext.getTestMethod(), Sql.class, SqlGroup.class); if (sqlAnnotations.isEmpty()) { - sqlAnnotations = AnnotationUtils.getRepeatableAnnotations(testContext.getTestClass(), Sql.class, + sqlAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(testContext.getTestClass(), Sql.class, SqlGroup.class); if (!sqlAnnotations.isEmpty()) { classLevel = true; @@ -157,7 +157,6 @@ public class SqlScriptsTestExecutionListener extends AbstractTestExecutionListen * @param classLevel {@code true} if {@link Sql @Sql} was declared at the * class level */ - @SuppressWarnings("serial") private void executeSqlScripts(Sql sql, ExecutionPhase executionPhase, TestContext testContext, boolean classLevel) throws Exception { if (executionPhase != sql.executionPhase()) { diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java index d16b05f8..474aef2c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,16 +61,16 @@ import org.springframework.test.context.web.ServletTestExecutionListener; * <ul> * <li>If you do not wish for your test classes to be tied to a Spring-specific * class hierarchy, you may configure your own custom test classes by using - * {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration @ContextConfiguration}, + * {@link SpringRunner}, {@link ContextConfiguration @ContextConfiguration}, * {@link TestExecutionListeners @TestExecutionListeners}, etc.</li> * <li>If you wish to extend this class and use a runner other than the - * {@link SpringJUnit4ClassRunner}, as of Spring Framework 4.2 you can use + * {@link SpringRunner}, as of Spring Framework 4.2 you can use * {@link org.springframework.test.context.junit4.rules.SpringClassRule SpringClassRule} and * {@link org.springframework.test.context.junit4.rules.SpringMethodRule SpringMethodRule} * and specify your runner of choice via {@link RunWith @RunWith(...)}.</li> * </ul> * - * <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher. + * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher. * * @author Sam Brannen * @since 2.5 @@ -85,7 +85,7 @@ import org.springframework.test.context.web.ServletTestExecutionListener; * @see AbstractTransactionalJUnit4SpringContextTests * @see org.springframework.test.context.testng.AbstractTestNGSpringContextTests */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @TestExecutionListeners({ ServletTestExecutionListener.class, DirtiesContextBeforeModesTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class }) public abstract class AbstractJUnit4SpringContextTests implements ApplicationContextAware { diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java index 9745e597..b6c533de 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,16 +64,16 @@ import org.springframework.transaction.annotation.Transactional; * <ul> * <li>If you do not wish for your test classes to be tied to a Spring-specific * class hierarchy, you may configure your own custom test classes by using - * {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration @ContextConfiguration}, + * {@link SpringRunner}, {@link ContextConfiguration @ContextConfiguration}, * {@link TestExecutionListeners @TestExecutionListeners}, etc.</li> * <li>If you wish to extend this class and use a runner other than the - * {@link SpringJUnit4ClassRunner}, as of Spring Framework 4.2 you can use + * {@link SpringRunner}, as of Spring Framework 4.2 you can use * {@link org.springframework.test.context.junit4.rules.SpringClassRule SpringClassRule} and * {@link org.springframework.test.context.junit4.rules.SpringMethodRule SpringMethodRule} * and specify your runner of choice via {@link org.junit.runner.RunWith @RunWith(...)}.</li> * </ul> * - * <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher. + * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher. * * @author Sam Brannen * @author Juergen Hoeller @@ -83,7 +83,6 @@ import org.springframework.transaction.annotation.Transactional; * @see org.springframework.test.context.TestExecutionListeners * @see org.springframework.test.context.transaction.TransactionalTestExecutionListener * @see org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener - * @see org.springframework.test.context.transaction.TransactionConfiguration * @see org.springframework.transaction.annotation.Transactional * @see org.springframework.test.annotation.Commit * @see org.springframework.test.annotation.Rollback diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java index 1d6a11f9..a0ed3db9 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.test.context.junit4; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -55,6 +56,9 @@ import org.springframework.util.ReflectionUtils; * <em>Spring TestContext Framework</em> to standard JUnit tests by means of the * {@link TestContextManager} and associated support classes and annotations. * + * <p>To use this class, simply annotate a JUnit 4 based test class with + * {@code @RunWith(SpringJUnit4ClassRunner.class)} or {@code @RunWith(SpringRunner.class)}. + * * <p>The following list constitutes all annotations currently supported directly * or indirectly by {@code SpringJUnit4ClassRunner}. <em>(Note that additional * annotations may be supported by various @@ -75,11 +79,12 @@ import org.springframework.util.ReflectionUtils; * <p>If you would like to use the Spring TestContext Framework with a runner * other than this one, use {@link SpringClassRule} and {@link SpringMethodRule}. * - * <p><strong>NOTE:</strong> As of Spring Framework 4.1, this class requires JUnit 4.9 or higher. + * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher. * * @author Sam Brannen * @author Juergen Hoeller * @since 2.5 + * @see SpringRunner * @see TestContextManager * @see AbstractJUnit4SpringContextTests * @see AbstractTransactionalJUnit4SpringContextTests @@ -92,27 +97,20 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { private static final Method withRulesMethod; - // Used by RunAfterTestClassCallbacks and RunAfterTestMethodCallbacks - private static final String MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME = "org.junit.runners.model.MultipleFailureException"; - static { - boolean junit4dot9Present = ClassUtils.isPresent(MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME, - SpringJUnit4ClassRunner.class.getClassLoader()); - if (!junit4dot9Present) { - throw new IllegalStateException(String.format( - "Failed to find class [%s]: SpringJUnit4ClassRunner requires JUnit 4.9 or higher.", - MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME)); + if (!ClassUtils.isPresent("org.junit.internal.Throwables", SpringJUnit4ClassRunner.class.getClassLoader())) { + throw new IllegalStateException("SpringJUnit4ClassRunner requires JUnit 4.12 or higher."); } withRulesMethod = ReflectionUtils.findMethod(SpringJUnit4ClassRunner.class, "withRules", FrameworkMethod.class, Object.class, Statement.class); if (withRulesMethod == null) { - throw new IllegalStateException( - "Failed to find withRules() method: SpringJUnit4ClassRunner requires JUnit 4.9 or higher."); + throw new IllegalStateException("SpringJUnit4ClassRunner requires JUnit 4.12 or higher."); } ReflectionUtils.makeAccessible(withRulesMethod); } + private final TestContextManager testContextManager; @@ -376,8 +374,7 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { statement = new SpringFailOnTimeout(next, springTimeout); } else if (junitTimeout > 0) { - // TODO Use FailOnTimeout.builder() once JUnit 4.12 is the minimum supported version. - statement = new FailOnTimeout(next, junitTimeout); + statement = FailOnTimeout.builder().withTimeout(junitTimeout, TimeUnit.MILLISECONDS).build(next); } else { statement = next; diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/SpringRunner.java b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringRunner.java new file mode 100644 index 00000000..37dad335 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringRunner.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2016 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; + +import org.junit.runners.model.InitializationError; + +/** + * {@code SpringRunner} is an <em>alias</em> for the {@link SpringJUnit4ClassRunner}. + * + * <p>To use this class, simply annotate a JUnit 4 based test class with + * {@code @RunWith(SpringRunner.class)}. + * + * <p>If you would like to use the Spring TestContext Framework with a runner other than + * this one, use {@link org.springframework.test.context.junit4.rules.SpringClassRule} + * and {@link org.springframework.test.context.junit4.rules.SpringMethodRule}. + * + * <p><strong>NOTE:</strong> This class requires JUnit 4.12 or higher. + * + * @author Sam Brannen + * @since 4.3 + * @see SpringJUnit4ClassRunner + * @see org.springframework.test.context.junit4.rules.SpringClassRule + * @see org.springframework.test.context.junit4.rules.SpringMethodRule + */ +public final class SpringRunner extends SpringJUnit4ClassRunner { + + /** + * Construct a new {@code SpringRunner} and initialize a + * {@link org.springframework.test.context.TestContextManager TestContextManager} + * to provide Spring testing functionality to standard JUnit 4 tests. + * @param clazz the test class to be run + * @see #createTestContextManager(Class) + */ + public SpringRunner(Class<?> clazz) throws InitializationError { + super(clazz); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java b/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java index d322cc36..0a93b268 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java @@ -1,4 +1,5 @@ /** - * Support classes for integrating the <em>Spring TestContext Framework</em> with JUnit. + * Support classes for integrating the <em>Spring TestContext Framework</em> + * with JUnit 4.12 or higher. */ package org.springframework.test.context.junit4; diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java index 904a3643..cbbe7437 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringClassRule.java @@ -23,6 +23,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.junit.Rule; import org.junit.rules.TestRule; import org.junit.runner.Description; @@ -76,7 +77,7 @@ import org.springframework.util.ClassUtils; * <li>{@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}</li> * </ul> * - * <p><strong>NOTE:</strong> This class requires JUnit 4.9 or higher. + * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher. * * @author Sam Brannen * @author Philippe Marschall @@ -96,16 +97,9 @@ public class SpringClassRule implements TestRule { private static final Map<Class<?>, TestContextManager> testContextManagerCache = new ConcurrentHashMap<Class<?>, TestContextManager>(64); - // Used by RunAfterTestClassCallbacks - private static final String MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME = "org.junit.runners.model.MultipleFailureException"; - static { - boolean junit4dot9Present = ClassUtils.isPresent(MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME, - SpringClassRule.class.getClassLoader()); - if (!junit4dot9Present) { - throw new IllegalStateException(String.format( - "Failed to find class [%s]: SpringClassRule requires JUnit 4.9 or higher.", - MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME)); + if (!ClassUtils.isPresent("org.junit.internal.Throwables", SpringClassRule.class.getClassLoader())) { + throw new IllegalStateException("SpringClassRule requires JUnit 4.12 or higher."); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java index 7bc200a0..89a97418 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/rules/SpringMethodRule.java @@ -20,6 +20,7 @@ import java.lang.reflect.Field; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.junit.ClassRule; import org.junit.rules.MethodRule; import org.junit.runners.model.FrameworkMethod; @@ -79,7 +80,7 @@ import org.springframework.util.ReflectionUtils; * <li>{@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}</li> * </ul> * - * <p><strong>NOTE:</strong> This class requires JUnit 4.9 or higher. + * <p><strong>NOTE:</strong> As of Spring Framework 4.3, this class requires JUnit 4.12 or higher. * * @author Sam Brannen * @author Philippe Marschall @@ -93,16 +94,9 @@ public class SpringMethodRule implements MethodRule { private static final Log logger = LogFactory.getLog(SpringMethodRule.class); - // Used by RunAfterTestMethodCallbacks - private static final String MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME = "org.junit.runners.model.MultipleFailureException"; - static { - boolean junit4dot9Present = ClassUtils.isPresent(MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME, - SpringMethodRule.class.getClassLoader()); - if (!junit4dot9Present) { - throw new IllegalStateException(String.format( - "Failed to find class [%s]: SpringMethodRule requires JUnit 4.9 or higher.", - MULTIPLE_FAILURE_EXCEPTION_CLASS_NAME)); + if (!ClassUtils.isPresent("org.junit.internal.Throwables", SpringMethodRule.class.getClassLoader())) { + throw new IllegalStateException("SpringMethodRule requires JUnit 4.12 or higher."); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/ProfileValueChecker.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/ProfileValueChecker.java index 144c4154..5d53bbff 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/ProfileValueChecker.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/ProfileValueChecker.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,10 @@ package org.springframework.test.context.junit4.statements; import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import org.junit.Assume; +import org.junit.AssumptionViolatedException; import org.junit.runners.model.Statement; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.test.annotation.IfProfileValue; import org.springframework.test.annotation.ProfileValueUtils; import org.springframework.util.Assert; @@ -64,6 +64,7 @@ public class ProfileValueChecker extends Statement { this.testMethod = testMethod; } + /** * Determine if the test specified by arguments to the * {@linkplain #ProfileValueChecker constructor} is <em>enabled</em> in @@ -76,27 +77,24 @@ public class ProfileValueChecker extends Statement { * will simply evaluate the next {@link Statement} in the execution chain. * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class) * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Method, Class) - * @see org.junit.Assume + * @throws AssumptionViolatedException if the test is disabled + * @throws Throwable if evaluation of the next statement fails */ @Override public void evaluate() throws Throwable { if (this.testMethod == null) { if (!ProfileValueUtils.isTestEnabledInThisEnvironment(this.testClass)) { - // Invoke assumeTrue() with false to avoid direct reference to JUnit's - // AssumptionViolatedException which exists in two packages as of JUnit 4.12. - Annotation ann = AnnotationUtils.findAnnotation(this.testClass, IfProfileValue.class); - Assume.assumeTrue(String.format( + Annotation ann = AnnotatedElementUtils.findMergedAnnotation(this.testClass, IfProfileValue.class); + throw new AssumptionViolatedException(String.format( "Profile configured via [%s] is not enabled in this environment for test class [%s].", - ann, this.testClass.getName()), false); + ann, this.testClass.getName())); } } else { if (!ProfileValueUtils.isTestEnabledInThisEnvironment(this.testMethod, this.testClass)) { - // Invoke assumeTrue() with false to avoid direct reference to JUnit's - // AssumptionViolatedException which exists in two packages as of JUnit 4.12. - Assume.assumeTrue(String.format( + throw new AssumptionViolatedException(String.format( "Profile configured via @IfProfileValue is not enabled in this environment for test method [%s].", - this.testMethod), false); + this.testMethod)); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestClassCallbacks.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestClassCallbacks.java index e7946dd6..bf05536a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestClassCallbacks.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestClassCallbacks.java @@ -80,13 +80,7 @@ public class RunAfterTestClassCallbacks extends Statement { errors.add(ex); } - if (errors.isEmpty()) { - return; - } - if (errors.size() == 1) { - throw errors.get(0); - } - throw new MultipleFailureException(errors); + MultipleFailureException.assertEmpty(errors); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestMethodCallbacks.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestMethodCallbacks.java index 1a1aa877..e0771264 100644 --- a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestMethodCallbacks.java +++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestMethodCallbacks.java @@ -97,13 +97,7 @@ public class RunAfterTestMethodCallbacks extends Statement { errors.add(ex); } - if (errors.isEmpty()) { - return; - } - if (errors.size() == 1) { - throw errors.get(0); - } - throw new MultipleFailureException(errors); + MultipleFailureException.assertEmpty(errors); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java index 6994c798..f17782f8 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java @@ -33,6 +33,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.SmartContextLoader; @@ -56,10 +57,13 @@ import org.springframework.util.ResourceUtils; * * @author Sam Brannen * @author Juergen Hoeller + * @author Phillip Webb * @since 2.5 * @see #generateDefaultLocations * @see #getResourceSuffixes * @see #modifyLocations + * @see #prepareContext + * @see #customizeContext */ public abstract class AbstractContextLoader implements SmartContextLoader { @@ -110,12 +114,13 @@ public abstract class AbstractContextLoader implements SmartContextLoader { * <li>Determines what (if any) context initializer classes have been supplied * via the {@code MergedContextConfiguration} and instantiates and * {@linkplain ApplicationContextInitializer#initialize invokes} each with the - * given application context.</li> + * given application context. * <ul> * <li>Any {@code ApplicationContextInitializers} implementing * {@link org.springframework.core.Ordered Ordered} or annotated with {@link * org.springframework.core.annotation.Order @Order} will be sorted appropriately.</li> * </ul> + * </li> * </ul> * @param context the newly created application context * @param mergedConfig the merged context configuration @@ -166,6 +171,23 @@ public abstract class AbstractContextLoader implements SmartContextLoader { } } + /** + * Customize the {@link ConfigurableApplicationContext} created by this + * {@code ContextLoader} <em>after</em> bean definitions have been loaded + * into the context but <em>before</em> the context has been refreshed. + * <p>The default implementation delegates to all + * {@link MergedContextConfiguration#getContextCustomizers context customizers} + * that have been registered with the supplied {@code mergedConfig}. + * @param context the newly created application context + * @param mergedConfig the merged context configuration + * @since 4.3 + */ + protected void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + for (ContextCustomizer contextCustomizer : mergedConfig.getContextCustomizers()) { + contextCustomizer.customizeContext(context, mergedConfig); + } + } + // --- ContextLoader ------------------------------------------------------- diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java index ca60c8dd..c0ae608e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,6 @@ import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.SmartContextLoader; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; /** * {@code AbstractDelegatingSmartContextLoader} serves as an abstract base class @@ -202,15 +201,6 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte name(getAnnotationConfigLoader()), configAttributes)); } - // If neither loader detected defaults and no initializers were declared, - // throw an exception. - if (!configAttributes.hasResources() && ObjectUtils.isEmpty(configAttributes.getInitializers())) { - throw new IllegalStateException(String.format( - "Neither %s nor %s was able to detect defaults, and no ApplicationContextInitializers " - + "were declared for context configuration %s", name(getXmlLoader()), - name(getAnnotationConfigLoader()), configAttributes)); - } - if (configAttributes.hasLocations() && configAttributes.hasClasses()) { String message = String.format( "Configuration error: both default locations AND default configuration classes " @@ -263,8 +253,9 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte } // If neither of the candidates supports the mergedConfig based on resources but - // ACIs were declared, then delegate to the annotation config loader. - if (!mergedConfig.getContextInitializerClasses().isEmpty()) { + // ACIs or customizers were declared, then delegate to the annotation config + // loader. + if (!mergedConfig.getContextInitializerClasses().isEmpty() || !mergedConfig.getContextCustomizers().isEmpty()) { return delegateLoading(getAnnotationConfigLoader(), mergedConfig); } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java index 41e42fcf..4bc65d29 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java @@ -16,7 +16,6 @@ package org.springframework.test.context.support; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,6 +52,7 @@ import org.springframework.util.StringUtils; * * @author Sam Brannen * @author Juergen Hoeller + * @author Phillip Webb * @since 2.5 * @see #loadContext(MergedContextConfiguration) * @see #loadContext(String...) @@ -92,6 +92,8 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * annotation configuration processors.</li> * <li>Calls {@link #customizeContext(GenericApplicationContext)} to allow for customizing the context * before it is refreshed.</li> + * <li>Calls {@link #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)} to + * allow for customizing the context before it is refreshed.</li> * <li>{@link ConfigurableApplicationContext#refresh Refreshes} the * context and registers a JVM shutdown hook for it.</li> * </ul> @@ -122,6 +124,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader loadBeanDefinitions(context, mergedConfig); AnnotationConfigUtils.registerAnnotationConfigProcessors(context); customizeContext(context); + customizeContext(context, mergedConfig); context.refresh(); context.registerShutdownHook(); return context; @@ -205,6 +208,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * @see GenericApplicationContext#setAllowBeanDefinitionOverriding * @see GenericApplicationContext#setResourceLoader * @see GenericApplicationContext#setId + * @see #prepareContext(ConfigurableApplicationContext, MergedContextConfiguration) * @since 2.5 */ protected void prepareContext(GenericApplicationContext context) { @@ -278,6 +282,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader * @param context the newly created application context * @see #loadContext(MergedContextConfiguration) * @see #loadContext(String...) + * @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration) * @since 2.5 */ protected void customizeContext(GenericApplicationContext context) { diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java index 7049115c..fb9e28c0 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java @@ -18,6 +18,7 @@ package org.springframework.test.context.support; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; @@ -30,8 +31,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanUtils; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.io.support.SpringFactoriesLoader; @@ -39,6 +38,8 @@ import org.springframework.test.context.BootstrapContext; import org.springframework.test.context.CacheAwareContextLoaderDelegate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; import org.springframework.test.context.ContextHierarchy; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; @@ -71,6 +72,7 @@ import org.springframework.util.StringUtils; * * @author Sam Brannen * @author Juergen Hoeller + * @author Phillip Webb * @since 4.1 */ public abstract class AbstractTestContextBootstrapper implements TestContextBootstrapper { @@ -272,11 +274,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot if (MetaAnnotationUtils.findAnnotationDescriptorForTypes( testClass, ContextConfiguration.class, ContextHierarchy.class) == null) { - if (logger.isInfoEnabled()) { - logger.info(String.format("Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]", - testClass.getName())); - } - return new MergedContextConfiguration(testClass, null, null, null, null); + return buildDefaultMergedContextConfiguration(testClass, cacheAwareContextLoaderDelegate); } if (AnnotationUtils.findAnnotation(testClass, ContextHierarchy.class) != null) { @@ -296,7 +294,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot Class<?> declaringClass = reversedList.get(0).getDeclaringClass(); mergedConfig = buildMergedContextConfiguration( - declaringClass, reversedList, parentConfig, cacheAwareContextLoaderDelegate); + declaringClass, reversedList, parentConfig, cacheAwareContextLoaderDelegate, true); parentConfig = mergedConfig; } @@ -306,8 +304,24 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot else { return buildMergedContextConfiguration(testClass, ContextLoaderUtils.resolveContextConfigurationAttributes(testClass), - null, cacheAwareContextLoaderDelegate); + null, cacheAwareContextLoaderDelegate, true); + } + } + + private MergedContextConfiguration buildDefaultMergedContextConfiguration(Class<?> testClass, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { + + List<ContextConfigurationAttributes> defaultConfigAttributesList = + Collections.singletonList(new ContextConfigurationAttributes(testClass)); + + ContextLoader contextLoader = resolveContextLoader(testClass, defaultConfigAttributesList); + if (logger.isInfoEnabled()) { + logger.info(String.format( + "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s], using %s", + testClass.getName(), contextLoader.getClass().getSimpleName())); } + return buildMergedContextConfiguration(testClass, defaultConfigAttributesList, null, + cacheAwareContextLoaderDelegate, false); } /** @@ -323,6 +337,9 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot * context in a context hierarchy, or {@code null} if there is no parent * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to * be passed to the {@code MergedContextConfiguration} constructor + * @param requireLocationsClassesOrInitializers whether locations, classes, or + * initializers are required; typically {@code true} but may be set to {@code false} + * if the configured loader supports empty configuration * @return the merged context configuration * @see #resolveContextLoader * @see ContextLoaderUtils#resolveContextConfigurationAttributes @@ -334,48 +351,90 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot */ private MergedContextConfiguration buildMergedContextConfiguration(Class<?> testClass, List<ContextConfigurationAttributes> configAttributesList, MergedContextConfiguration parentConfig, - CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, + boolean requireLocationsClassesOrInitializers) { + + Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be null or empty"); ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList); - List<String> locationsList = new ArrayList<String>(); - List<Class<?>> classesList = new ArrayList<Class<?>>(); + List<String> locations = new ArrayList<String>(); + List<Class<?>> classes = new ArrayList<Class<?>>(); + List<Class<?>> initializers = new ArrayList<Class<?>>(); for (ContextConfigurationAttributes configAttributes : configAttributesList) { if (logger.isTraceEnabled()) { logger.trace(String.format("Processing locations and classes for context configuration attributes %s", - configAttributes)); + configAttributes)); } if (contextLoader instanceof SmartContextLoader) { SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader; smartContextLoader.processContextConfiguration(configAttributes); - locationsList.addAll(0, Arrays.asList(configAttributes.getLocations())); - classesList.addAll(0, Arrays.asList(configAttributes.getClasses())); + locations.addAll(0, Arrays.asList(configAttributes.getLocations())); + classes.addAll(0, Arrays.asList(configAttributes.getClasses())); } else { - String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(), - configAttributes.getLocations()); - locationsList.addAll(0, Arrays.asList(processedLocations)); + String[] processedLocations = contextLoader.processLocations( + configAttributes.getDeclaringClass(), configAttributes.getLocations()); + locations.addAll(0, Arrays.asList(processedLocations)); // Legacy ContextLoaders don't know how to process classes } + initializers.addAll(0, Arrays.asList(configAttributes.getInitializers())); if (!configAttributes.isInheritLocations()) { break; } } - String[] locations = StringUtils.toStringArray(locationsList); - Class<?>[] classes = ClassUtils.toClassArray(classesList); - Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = // - ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList); - String[] activeProfiles = ActiveProfilesUtils.resolveActiveProfiles(testClass); - MergedTestPropertySources mergedTestPropertySources = TestPropertySourceUtils.buildMergedTestPropertySources(testClass); + Set<ContextCustomizer> contextCustomizers = getContextCustomizers(testClass, + Collections.unmodifiableList(configAttributesList)); - MergedContextConfiguration mergedConfig = new MergedContextConfiguration(testClass, locations, classes, - initializerClasses, activeProfiles, mergedTestPropertySources.getLocations(), - mergedTestPropertySources.getProperties(), contextLoader, cacheAwareContextLoaderDelegate, parentConfig); + if (requireLocationsClassesOrInitializers && + areAllEmpty(locations, classes, initializers, contextCustomizers)) { + throw new IllegalStateException(String.format( + "%s was unable to detect defaults, and no ApplicationContextInitializers " + + "or ContextCustomizers were declared for context configuration attributes %s", + contextLoader.getClass().getSimpleName(), configAttributesList)); + } + + MergedTestPropertySources mergedTestPropertySources = + TestPropertySourceUtils.buildMergedTestPropertySources(testClass); + MergedContextConfiguration mergedConfig = new MergedContextConfiguration(testClass, + StringUtils.toStringArray(locations), + ClassUtils.toClassArray(classes), + ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList), + ActiveProfilesUtils.resolveActiveProfiles(testClass), + mergedTestPropertySources.getLocations(), + mergedTestPropertySources.getProperties(), + contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parentConfig); return processMergedContextConfiguration(mergedConfig); } + private Set<ContextCustomizer> getContextCustomizers(Class<?> testClass, + List<ContextConfigurationAttributes> configAttributes) { + + List<ContextCustomizerFactory> factories = getContextCustomizerFactories(); + Set<ContextCustomizer> customizers = new LinkedHashSet<ContextCustomizer>(factories.size()); + for (ContextCustomizerFactory factory : factories) { + ContextCustomizer customizer = factory.createContextCustomizer(testClass, configAttributes); + if (customizer != null) { + customizers.add(customizer); + } + } + return customizers; + } + + /** + * Get the {@link ContextCustomizerFactory} instances for this bootstrapper. + * <p>The default implementation uses the {@link SpringFactoriesLoader} mechanism + * for loading factories configured in all {@code META-INF/spring.factories} + * files on the classpath. + * @since 4.3 + * @see SpringFactoriesLoader#loadFactories + */ + protected List<ContextCustomizerFactory> getContextCustomizerFactories() { + return SpringFactoriesLoader.loadFactories(ContextCustomizerFactory.class, getClass().getClassLoader()); + } + /** * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the * supplied list of {@link ContextConfigurationAttributes} and then instantiate @@ -388,7 +447,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot * @param testClass the test class for which the {@code ContextLoader} should be * resolved; must not be {@code null} * @param configAttributesList the list of configuration attributes to process; must - * not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em> + * not be {@code null}; must be ordered <em>bottom-up</em> * (i.e., as if we were traversing up the class hierarchy) * @return the resolved {@code ContextLoader} for the supplied {@code testClass} * (never {@code null}) @@ -399,7 +458,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot List<ContextConfigurationAttributes> configAttributesList) { Assert.notNull(testClass, "Class must not be null"); - Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); + Assert.notNull(configAttributesList, "ContextConfigurationAttributes list must not be null"); Class<? extends ContextLoader> contextLoaderClass = resolveExplicitContextLoaderClass(configAttributesList); if (contextLoaderClass == null) { @@ -428,7 +487,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot * step #1.</li> * </ol> * @param configAttributesList the list of configuration attributes to process; - * must not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em> + * must not be {@code null}; must be ordered <em>bottom-up</em> * (i.e., as if we were traversing up the class hierarchy) * @return the {@code ContextLoader} class to use for the supplied configuration * attributes, or {@code null} if no explicit loader is found @@ -438,7 +497,8 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot protected Class<? extends ContextLoader> resolveExplicitContextLoaderClass( List<ContextConfigurationAttributes> configAttributesList) { - Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); + Assert.notNull(configAttributesList, "ContextConfigurationAttributes list must not be null"); + for (ContextConfigurationAttributes configAttributes : configAttributesList) { if (logger.isTraceEnabled()) { logger.trace(String.format("Resolving ContextLoader for context configuration attributes %s", @@ -497,4 +557,14 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot return mergedConfig; } + + private static boolean areAllEmpty(Collection<?>... collections) { + for (Collection<?> collection : collections) { + if (!collection.isEmpty()) { + return false; + } + } + return true; + } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java index baa15001..968d2300 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.test.context.SmartContextLoader; import org.springframework.util.Assert; @@ -103,7 +103,7 @@ public abstract class AnnotationConfigContextLoaderUtils { */ private static boolean isDefaultConfigurationClassCandidate(Class<?> clazz) { return (clazz != null && isStaticNonPrivateAndNonFinal(clazz) && - (AnnotationUtils.findAnnotation(clazz, Configuration.class) != null)); + AnnotatedElementUtils.hasAnnotation(clazz, Configuration.class)); } private static boolean isStaticNonPrivateAndNonFinal(Class<?> clazz) { diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java index da3383cb..49819fd5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/ContextLoaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,13 +81,12 @@ abstract class ContextLoaderUtils { * (must not be {@code null}) * @return the list of lists of configuration attributes for the specified class; * never {@code null} - * @throws IllegalArgumentException if the supplied class is {@code null}; if + * @throws IllegalArgumentException if the supplied class is {@code null}; or if * neither {@code @ContextConfiguration} nor {@code @ContextHierarchy} is - * <em>present</em> on the supplied class; or if a test class or composed annotation + * <em>present</em> on the supplied class + * @throws IllegalStateException if a test class or composed annotation * in the class hierarchy declares both {@code @ContextConfiguration} and * {@code @ContextHierarchy} as top-level annotations. - * @throws IllegalStateException if no class in the class hierarchy declares - * {@code @ContextHierarchy}. * @since 3.2.2 * @see #buildContextHierarchyMap(Class) * @see #resolveContextConfigurationAttributes(Class) @@ -95,11 +94,10 @@ abstract class ContextLoaderUtils { @SuppressWarnings("unchecked") static List<List<ContextConfigurationAttributes>> resolveContextHierarchyAttributes(Class<?> testClass) { Assert.notNull(testClass, "Class must not be null"); - Assert.state(findAnnotation(testClass, ContextHierarchy.class) != null, "@ContextHierarchy must be present"); - final Class<ContextConfiguration> contextConfigType = ContextConfiguration.class; - final Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class; - final List<List<ContextConfigurationAttributes>> hierarchyAttributes = new ArrayList<List<ContextConfigurationAttributes>>(); + Class<ContextConfiguration> contextConfigType = ContextConfiguration.class; + Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class; + List<List<ContextConfigurationAttributes>> hierarchyAttributes = new ArrayList<List<ContextConfigurationAttributes>>(); UntypedAnnotationDescriptor desc = findAnnotationDescriptorForTypes(testClass, contextConfigType, contextHierarchyType); @@ -124,7 +122,7 @@ abstract class ContextLoaderUtils { throw new IllegalStateException(msg); } - final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>(); + List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>(); if (contextConfigDeclaredLocally) { ContextConfiguration contextConfiguration = AnnotationUtils.synthesizeAnnotation( diff --git a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java index 1619ea84..98e9c936 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/TestPropertySourceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,10 +35,11 @@ import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.util.TestContextResourceUtils; -import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.test.util.MetaAnnotationUtils.*; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -57,19 +58,15 @@ import static org.springframework.test.util.MetaAnnotationUtils.*; */ public abstract class TestPropertySourceUtils { - private static final Log logger = LogFactory.getLog(TestPropertySourceUtils.class); - /** * The name of the {@link MapPropertySource} created from <em>inlined properties</em>. * @since 4.1.5 - * @see {@link #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[])} + * @see #addInlinedPropertiesToEnvironment */ public static final String INLINED_PROPERTIES_PROPERTY_SOURCE_NAME = "Inlined Test Properties"; + private static final Log logger = LogFactory.getLog(TestPropertySourceUtils.class); - private TestPropertySourceUtils() { - /* no-op */ - } static MergedTestPropertySources buildMergedTestPropertySources(Class<?> testClass) { Class<TestPropertySource> annotationType = TestPropertySource.class; @@ -78,7 +75,6 @@ public abstract class TestPropertySourceUtils { return new MergedTestPropertySources(); } - // else... List<TestPropertySourceAttributes> attributesList = resolveTestPropertySourceAttributes(testClass); String[] locations = mergeLocations(attributesList); String[] properties = mergeProperties(attributesList); @@ -87,30 +83,27 @@ public abstract class TestPropertySourceUtils { private static List<TestPropertySourceAttributes> resolveTestPropertySourceAttributes(Class<?> testClass) { Assert.notNull(testClass, "Class must not be null"); + List<TestPropertySourceAttributes> attributesList = new ArrayList<TestPropertySourceAttributes>(); + Class<TestPropertySource> annotationType = TestPropertySource.class; - final List<TestPropertySourceAttributes> attributesList = new ArrayList<TestPropertySourceAttributes>(); - final Class<TestPropertySource> annotationType = TestPropertySource.class; AnnotationDescriptor<TestPropertySource> descriptor = findAnnotationDescriptor(testClass, annotationType); Assert.notNull(descriptor, String.format( - "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", - annotationType.getName(), testClass.getName())); + "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", + annotationType.getName(), testClass.getName())); while (descriptor != null) { TestPropertySource testPropertySource = descriptor.synthesizeAnnotation(); Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); - if (logger.isTraceEnabled()) { logger.trace(String.format("Retrieved @TestPropertySource [%s] for declaring class [%s].", testPropertySource, rootDeclaringClass.getName())); } - - TestPropertySourceAttributes attributes = new TestPropertySourceAttributes(rootDeclaringClass, - testPropertySource); + TestPropertySourceAttributes attributes = + new TestPropertySourceAttributes(rootDeclaringClass, testPropertySource); if (logger.isTraceEnabled()) { logger.trace("Resolved TestPropertySource attributes: " + attributes); } attributesList.add(attributes); - descriptor = findAnnotationDescriptor(rootDeclaringClass.getSuperclass(), annotationType); } @@ -119,74 +112,90 @@ public abstract class TestPropertySourceUtils { private static String[] mergeLocations(List<TestPropertySourceAttributes> attributesList) { final List<String> locations = new ArrayList<String>(); - for (TestPropertySourceAttributes attrs : attributesList) { if (logger.isTraceEnabled()) { logger.trace(String.format("Processing locations for TestPropertySource attributes %s", attrs)); } - String[] locationsArray = TestContextResourceUtils.convertToClasspathResourcePaths( - attrs.getDeclaringClass(), attrs.getLocations()); + attrs.getDeclaringClass(), attrs.getLocations()); locations.addAll(0, Arrays.<String> asList(locationsArray)); - if (!attrs.isInheritLocations()) { break; } } - return StringUtils.toStringArray(locations); } private static String[] mergeProperties(List<TestPropertySourceAttributes> attributesList) { final List<String> properties = new ArrayList<String>(); - for (TestPropertySourceAttributes attrs : attributesList) { if (logger.isTraceEnabled()) { logger.trace(String.format("Processing inlined properties for TestPropertySource attributes %s", attrs)); } - - properties.addAll(0, Arrays.<String> asList(attrs.getProperties())); - + properties.addAll(0, Arrays.<String>asList(attrs.getProperties())); if (!attrs.isInheritProperties()) { break; } } - return StringUtils.toStringArray(properties); } /** * Add the {@link Properties} files from the given resource {@code locations} * to the {@link Environment} of the supplied {@code context}. + * <p>This method simply delegates to + * {@link #addPropertiesFilesToEnvironment(ConfigurableEnvironment, ResourceLoader, String...)}. + * @param context the application context whose environment should be updated; + * never {@code null} + * @param locations the resource locations of {@code Properties} files to add + * to the environment; potentially empty but never {@code null} + * @since 4.1.5 + * @see ResourcePropertySource + * @see TestPropertySource#locations + * @see #addPropertiesFilesToEnvironment(ConfigurableEnvironment, ResourceLoader, String...) + * @throws IllegalStateException if an error occurs while processing a properties file + */ + public static void addPropertiesFilesToEnvironment(ConfigurableApplicationContext context, String... locations) { + Assert.notNull(context, "'context' must not be null"); + Assert.notNull(locations, "'locations' must not be null"); + addPropertiesFilesToEnvironment(context.getEnvironment(), context, locations); + } + + /** + * Add the {@link Properties} files from the given resource {@code locations} + * to the supplied {@link ConfigurableEnvironment environment}. * <p>Property placeholders in resource locations (i.e., <code>${...}</code>) * will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved} * against the {@code Environment}. * <p>Each properties file will be converted to a {@link ResourcePropertySource} * that will be added to the {@link PropertySources} of the environment with * highest precedence. - * @param context the application context whose environment should be updated; + * @param environment the environment to update; never {@code null} + * @param resourceLoader the {@code ResourceLoader} to use to load each resource; * never {@code null} * @param locations the resource locations of {@code Properties} files to add * to the environment; potentially empty but never {@code null} - * @since 4.1.5 + * @since 4.3 * @see ResourcePropertySource * @see TestPropertySource#locations + * @see #addPropertiesFilesToEnvironment(ConfigurableApplicationContext, String...) * @throws IllegalStateException if an error occurs while processing a properties file */ - public static void addPropertiesFilesToEnvironment(ConfigurableApplicationContext context, - String[] locations) { - Assert.notNull(context, "context must not be null"); - Assert.notNull(locations, "locations must not be null"); + public static void addPropertiesFilesToEnvironment(ConfigurableEnvironment environment, + ResourceLoader resourceLoader, String... locations) { + + Assert.notNull(environment, "'environment' must not be null"); + Assert.notNull(resourceLoader, "'resourceLoader' must not be null"); + Assert.notNull(locations, "'locations' must not be null"); try { - ConfigurableEnvironment environment = context.getEnvironment(); for (String location : locations) { String resolvedLocation = environment.resolveRequiredPlaceholders(location); - Resource resource = context.getResource(resolvedLocation); + Resource resource = resourceLoader.getResource(resolvedLocation); environment.getPropertySources().addFirst(new ResourcePropertySource(resource)); } } - catch (IOException e) { - throw new IllegalStateException("Failed to add PropertySource to Environment", e); + catch (IOException ex) { + throw new IllegalStateException("Failed to add PropertySource to Environment", ex); } } @@ -203,10 +212,9 @@ public abstract class TestPropertySourceUtils { * @see TestPropertySource#properties * @see #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[]) */ - public static void addInlinedPropertiesToEnvironment(ConfigurableApplicationContext context, - String[] inlinedProperties) { - Assert.notNull(context, "context must not be null"); - Assert.notNull(inlinedProperties, "inlinedProperties must not be null"); + public static void addInlinedPropertiesToEnvironment(ConfigurableApplicationContext context, String... inlinedProperties) { + Assert.notNull(context, "'context' must not be null"); + Assert.notNull(inlinedProperties, "'inlinedProperties' must not be null"); addInlinedPropertiesToEnvironment(context.getEnvironment(), inlinedProperties); } @@ -226,17 +234,22 @@ public abstract class TestPropertySourceUtils { * @see TestPropertySource#properties * @see #convertInlinedPropertiesToMap */ - public static void addInlinedPropertiesToEnvironment(ConfigurableEnvironment environment, String[] inlinedProperties) { - Assert.notNull(environment, "environment must not be null"); - Assert.notNull(inlinedProperties, "inlinedProperties must not be null"); + public static void addInlinedPropertiesToEnvironment(ConfigurableEnvironment environment, String... inlinedProperties) { + Assert.notNull(environment, "'environment' must not be null"); + Assert.notNull(inlinedProperties, "'inlinedProperties' must not be null"); if (!ObjectUtils.isEmpty(inlinedProperties)) { if (logger.isDebugEnabled()) { - logger.debug("Adding inlined properties to environment: " - + ObjectUtils.nullSafeToString(inlinedProperties)); + logger.debug("Adding inlined properties to environment: " + + ObjectUtils.nullSafeToString(inlinedProperties)); + } + MapPropertySource ps = (MapPropertySource) + environment.getPropertySources().get(INLINED_PROPERTIES_PROPERTY_SOURCE_NAME); + if (ps == null) { + ps = new MapPropertySource(INLINED_PROPERTIES_PROPERTY_SOURCE_NAME, + new LinkedHashMap<String, Object>()); + environment.getPropertySources().addFirst(ps); } - MapPropertySource ps = new MapPropertySource(INLINED_PROPERTIES_PROPERTY_SOURCE_NAME, - convertInlinedPropertiesToMap(inlinedProperties)); - environment.getPropertySources().addFirst(ps); + ps.getSource().putAll(convertInlinedPropertiesToMap(inlinedProperties)); } } @@ -257,24 +270,22 @@ public abstract class TestPropertySourceUtils { * a given inlined property contains multiple key-value pairs * @see #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[]) */ - public static Map<String, Object> convertInlinedPropertiesToMap(String[] inlinedProperties) { - Assert.notNull(inlinedProperties, "inlinedProperties must not be null"); + public static Map<String, Object> convertInlinedPropertiesToMap(String... inlinedProperties) { + Assert.notNull(inlinedProperties, "'inlinedProperties' must not be null"); Map<String, Object> map = new LinkedHashMap<String, Object>(); - Properties props = new Properties(); + for (String pair : inlinedProperties) { if (!StringUtils.hasText(pair)) { continue; } - try { props.load(new StringReader(pair)); } - catch (Exception e) { - throw new IllegalStateException("Failed to load test environment property from [" + pair + "].", e); + catch (Exception ex) { + throw new IllegalStateException("Failed to load test environment property from [" + pair + "]", ex); } - Assert.state(props.size() == 1, "Failed to load exactly one test environment property from [" + pair + "]."); - + Assert.state(props.size() == 1, "Failed to load exactly one test environment property from [" + pair + "]"); for (String name : props.stringPropertyNames()) { map.put(name, props.getProperty(name)); } diff --git a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java index 1fd08e32..0899e13b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java +++ b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,7 +67,6 @@ import org.springframework.transaction.annotation.Transactional; * @see org.springframework.test.context.TestExecutionListeners * @see org.springframework.test.context.transaction.TransactionalTestExecutionListener * @see org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener - * @see org.springframework.test.context.transaction.TransactionConfiguration * @see org.springframework.transaction.annotation.Transactional * @see org.springframework.test.annotation.Commit * @see org.springframework.test.annotation.Rollback diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java b/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java index 04ebe481..a7f6653e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * <p>Test annotation to indicate that the annotated {@code public void} method + * <p>Test annotation which indicates that the annotated {@code void} method * should be executed <em>after</em> a transaction is ended for a test method - * configured to run within a transaction via the {@code @Transactional} annotation. + * configured to run within a transaction via Spring's {@code @Transactional} + * annotation. * - * <p>The {@code @AfterTransaction} methods of superclasses will be executed - * after those of the current class. + * <p>As of Spring Framework 4.3, {@code @AfterTransaction} may be declared on + * Java 8 based interface default methods. + * + * <p>{@code @AfterTransaction} methods declared in superclasses or as interface + * default methods will be executed after those of the current test class. * * <p>As of Spring Framework 4.0, this annotation may be used as a * <em>meta-annotation</em> to create custom <em>composed annotations</em>. diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java b/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java index b7110015..217c7d17 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * <p>Test annotation to indicate that the annotated {@code public void} method + * <p>Test annotation which indicates that the annotated {@code void} method * should be executed <em>before</em> a transaction is started for a test method - * configured to run within a transaction via the {@code @Transactional} annotation. + * configured to run within a transaction via Spring's {@code @Transactional} + * annotation. * - * <p>The {@code @BeforeTransaction} methods of superclasses will be executed - * before those of the current class. + * <p>As of Spring Framework 4.3, {@code @BeforeTransaction} may be declared on + * Java 8 based interface default methods. + * + * <p>{@code @BeforeTransaction} methods declared in superclasses or as interface + * default methods will be executed before those of the current test class. * * <p>As of Spring Framework 4.0, this annotation may be used as a * <em>meta-annotation</em> to create custom <em>composed annotations</em>. diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java index 3b65d690..b00a9955 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.test.context.transaction; import java.util.Map; + import javax.sql.DataSource; import org.apache.commons.logging.Log; @@ -73,7 +74,8 @@ public abstract class TestContextTransactionUtils { * <li>Look up the {@code DataSource} by type and name, if the supplied * {@code name} is non-empty, throwing a {@link BeansException} if the named * {@code DataSource} does not exist. - * <li>Attempt to look up a single {@code DataSource} by type. + * <li>Attempt to look up the single {@code DataSource} by type. + * <li>Attempt to look up the <em>primary</em> {@code DataSource} by type. * <li>Attempt to look up the {@code DataSource} by type and the * {@linkplain #DEFAULT_DATA_SOURCE_NAME default data source name}. * @param testContext the test context for which the {@code DataSource} @@ -110,15 +112,21 @@ public abstract class TestContextTransactionUtils { if (dataSources.size() == 1) { return dataSources.values().iterator().next(); } + + try { + // look up single bean by type, with support for 'primary' beans + return bf.getBean(DataSource.class); + } + catch (BeansException ex) { + logBeansException(testContext, ex, PlatformTransactionManager.class); + } } // look up by type and default name return bf.getBean(DEFAULT_DATA_SOURCE_NAME, DataSource.class); } catch (BeansException ex) { - if (logger.isDebugEnabled()) { - logger.debug("Caught exception while retrieving DataSource for test context " + testContext, ex); - } + logBeansException(testContext, ex, DataSource.class); return null; } } @@ -133,7 +141,8 @@ public abstract class TestContextTransactionUtils { * <li>Look up the transaction manager by type and explicit name, if the supplied * {@code name} is non-empty, throwing a {@link BeansException} if the named * transaction manager does not exist. - * <li>Attempt to look up the transaction manager by type. + * <li>Attempt to look up the single transaction manager by type. + * <li>Attempt to look up the <em>primary</em> transaction manager by type. * <li>Attempt to look up the transaction manager via a * {@link TransactionManagementConfigurer}, if present. * <li>Attempt to look up the transaction manager by type and the @@ -176,6 +185,14 @@ public abstract class TestContextTransactionUtils { return txMgrs.values().iterator().next(); } + try { + // look up single bean by type, with support for 'primary' beans + return bf.getBean(PlatformTransactionManager.class); + } + catch (BeansException ex) { + logBeansException(testContext, ex, PlatformTransactionManager.class); + } + // look up single TransactionManagementConfigurer Map<String, TransactionManagementConfigurer> configurers = BeanFactoryUtils.beansOfTypeIncludingAncestors( lbf, TransactionManagementConfigurer.class); @@ -192,14 +209,18 @@ public abstract class TestContextTransactionUtils { return bf.getBean(DEFAULT_TRANSACTION_MANAGER_NAME, PlatformTransactionManager.class); } catch (BeansException ex) { - if (logger.isDebugEnabled()) { - logger.debug("Caught exception while retrieving transaction manager for test context " + testContext, - ex); - } + logBeansException(testContext, ex, PlatformTransactionManager.class); return null; } } + private static void logBeansException(TestContext testContext, BeansException ex, Class<?> beanType) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("Caught exception while retrieving %s for test context %s", + beanType.getSimpleName(), testContext), ex); + } + } + /** * Create a delegating {@link TransactionAttribute} for the supplied target * {@link TransactionAttribute} and {@link TestContext}, using the names of diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfiguration.java index 98857e5a..e35e862e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,8 +40,9 @@ import java.lang.annotation.Target; * @see org.springframework.test.context.jdbc.SqlConfig * @see org.springframework.test.context.jdbc.SqlConfig#transactionManager * @see org.springframework.test.context.ContextConfiguration - * @deprecated As of Spring Framework 4.2, use {@code @Rollback} at the class - * level and the {@code transactionManager} qualifier in {@code @Transactional}. + * @deprecated As of Spring Framework 4.2, use {@code @Rollback} or + * {@code @Commit} at the class level and the {@code transactionManager} + * qualifier in {@code @Transactional}. */ @Deprecated @Documented diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java index 6ae8d4bd..356eb3d0 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,8 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.test.annotation.Commit; import org.springframework.test.annotation.Rollback; import org.springframework.test.context.TestContext; import org.springframework.test.context.support.AbstractTestExecutionListener; @@ -43,8 +45,6 @@ import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; -import static org.springframework.core.annotation.AnnotationUtils.*; - /** * {@code TestExecutionListener} that provides support for executing tests * within <em>test-managed transactions</em> by honoring Spring's @@ -83,8 +83,9 @@ import static org.springframework.core.annotation.AnnotationUtils.*; * <h3>Declarative Rollback and Commit Behavior</h3> * <p>By default, test transactions will be automatically <em>rolled back</em> * after completion of the test; however, transactional commit and rollback - * behavior can be configured declaratively via the {@link Rollback @Rollback} - * annotation at the class level and at the method level. + * behavior can be configured declaratively via the {@link Commit @Commit} + * and {@link Rollback @Rollback} annotations at the class level and at the + * method level. * * <h3>Programmatic Transaction Management</h3> * <p>As of Spring Framework 4.1, it is possible to interact with test-managed @@ -96,9 +97,10 @@ import static org.springframework.core.annotation.AnnotationUtils.*; * <p>When executing transactional tests, it is sometimes useful to be able to * execute certain <em>set up</em> or <em>tear down</em> code outside of a * transaction. {@code TransactionalTestExecutionListener} provides such - * support for methods annotated with - * {@link BeforeTransaction @BeforeTransaction} or - * {@link AfterTransaction @AfterTransaction}. + * support for methods annotated with {@link BeforeTransaction @BeforeTransaction} + * or {@link AfterTransaction @AfterTransaction}. As of Spring Framework 4.3, + * {@code @BeforeTransaction} and {@code @AfterTransaction} may also be declared + * on Java 8 based interface default methods. * * <h3>Configuring a Transaction Manager</h3> * <p>{@code TransactionalTestExecutionListener} expects a @@ -133,7 +135,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis @SuppressWarnings("deprecation") private static final TransactionConfigurationAttributes defaultTxConfigAttributes = new TransactionConfigurationAttributes(); - protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource(); + // Do not require @Transactional test methods to be public. + protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource(false); @SuppressWarnings("deprecation") private TransactionConfigurationAttributes configurationAttributes; @@ -177,8 +180,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis transactionAttribute); if (logger.isDebugEnabled()) { - logger.debug("Explicit transaction definition [" + transactionAttribute + "] found for test context " - + testContext); + logger.debug("Explicit transaction definition [" + transactionAttribute + "] found for test context " + + testContext); } if (transactionAttribute.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { @@ -189,8 +192,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis if (tm == null) { throw new IllegalStateException(String.format( - "Failed to retrieve PlatformTransactionManager for @Transactional test for test context %s.", - testContext)); + "Failed to retrieve PlatformTransactionManager for @Transactional test for test context %s.", + testContext)); } } @@ -246,12 +249,15 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis if (logger.isDebugEnabled()) { logger.debug("Executing @BeforeTransaction method [" + method + "] for test context " + testContext); } + ReflectionUtils.makeAccessible(method); method.invoke(testContext.getTestInstance()); } } catch (InvocationTargetException ex) { - logger.error("Exception encountered while executing @BeforeTransaction methods for test context " - + testContext + ".", ex.getTargetException()); + if (logger.isErrorEnabled()) { + logger.error("Exception encountered while executing @BeforeTransaction methods for test context " + + testContext + ".", ex.getTargetException()); + } ReflectionUtils.rethrowException(ex.getTargetException()); } } @@ -273,6 +279,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis if (logger.isDebugEnabled()) { logger.debug("Executing @AfterTransaction method [" + method + "] for test context " + testContext); } + ReflectionUtils.makeAccessible(method); method.invoke(testContext.getTestInstance()); } catch (InvocationTargetException ex) { @@ -280,15 +287,15 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis if (afterTransactionException == null) { afterTransactionException = targetException; } - logger.error("Exception encountered while executing @AfterTransaction method [" + method - + "] for test context " + testContext, targetException); + logger.error("Exception encountered while executing @AfterTransaction method [" + method + + "] for test context " + testContext, targetException); } catch (Exception ex) { if (afterTransactionException == null) { afterTransactionException = ex; } - logger.error("Exception encountered while executing @AfterTransaction method [" + method - + "] for test context " + testContext, ex); + logger.error("Exception encountered while executing @AfterTransaction method [" + method + + "] for test context " + testContext, ex); } } @@ -311,20 +318,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis * @see #getTransactionManager(TestContext) */ protected PlatformTransactionManager getTransactionManager(TestContext testContext, String qualifier) { - // look up by type and qualifier from @Transactional + // Look up by type and qualifier from @Transactional if (StringUtils.hasText(qualifier)) { try { - // Use autowire-capable factory in order to support extended qualifier - // matching (only exposed on the internal BeanFactory, not on the - // ApplicationContext). + // Use autowire-capable factory in order to support extended qualifier matching + // (only exposed on the internal BeanFactory, not on the ApplicationContext). BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory(); return BeanFactoryAnnotationUtils.qualifiedBeanOfType(bf, PlatformTransactionManager.class, qualifier); } catch (RuntimeException ex) { if (logger.isWarnEnabled()) { - logger.warn( - String.format( + logger.warn(String.format( "Caught exception while retrieving transaction manager with qualifier '%s' for test context %s", qualifier, testContext), ex); } @@ -359,7 +364,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis /** * Determine whether or not to rollback transactions by default for the * supplied {@linkplain TestContext test context}. - * <p>Supports {@link Rollback @Rollback} or + * <p>Supports {@link Rollback @Rollback}, {@link Commit @Commit}, or * {@link TransactionConfiguration @TransactionConfiguration} at the * class-level. * @param testContext the test context for which the default rollback flag @@ -370,7 +375,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis @SuppressWarnings("deprecation") protected final boolean isDefaultRollback(TestContext testContext) throws Exception { Class<?> testClass = testContext.getTestClass(); - Rollback rollback = findAnnotation(testClass, Rollback.class); + Rollback rollback = AnnotatedElementUtils.findMergedAnnotation(testClass, Rollback.class); boolean rollbackPresent = (rollback != null); TransactionConfigurationAttributes txConfigAttributes = retrieveConfigurationAttributes(testContext); @@ -405,111 +410,45 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis */ protected final boolean isRollback(TestContext testContext) throws Exception { boolean rollback = isDefaultRollback(testContext); - Rollback rollbackAnnotation = findAnnotation(testContext.getTestMethod(), Rollback.class); + Rollback rollbackAnnotation = + AnnotatedElementUtils.findMergedAnnotation(testContext.getTestMethod(), Rollback.class); if (rollbackAnnotation != null) { boolean rollbackOverride = rollbackAnnotation.value(); if (logger.isDebugEnabled()) { logger.debug(String.format( - "Method-level @Rollback(%s) overrides default rollback [%s] for test context %s.", - rollbackOverride, rollback, testContext)); + "Method-level @Rollback(%s) overrides default rollback [%s] for test context %s.", + rollbackOverride, rollback, testContext)); } rollback = rollbackOverride; } else { if (logger.isDebugEnabled()) { logger.debug(String.format( - "No method-level @Rollback override: using default rollback [%s] for test context %s.", rollback, - testContext)); + "No method-level @Rollback override: using default rollback [%s] for test context %s.", + rollback, testContext)); } } return rollback; } /** - * Gets all superclasses of the supplied {@link Class class}, including the - * class itself. The ordering of the returned list will begin with the - * supplied class and continue up the class hierarchy, excluding {@link Object}. - * <p>Note: This code has been borrowed from - * {@link org.junit.internal.runners.TestClass#getSuperClasses(Class)} and - * adapted. - * @param clazz the class for which to retrieve the superclasses - * @return all superclasses of the supplied class, excluding {@code Object} - */ - private List<Class<?>> getSuperClasses(Class<?> clazz) { - List<Class<?>> results = new ArrayList<Class<?>>(); - Class<?> current = clazz; - while (current != null && Object.class != current) { - results.add(current); - current = current.getSuperclass(); - } - return results; - } - - /** - * Gets all methods in the supplied {@link Class class} and its superclasses + * Get all methods in the supplied {@link Class class} and its superclasses * which are annotated with the supplied {@code annotationType} but * which are not <em>shadowed</em> by methods overridden in subclasses. - * <p>Note: This code has been borrowed from - * {@link org.junit.internal.runners.TestClass#getAnnotatedMethods(Class)} - * and adapted. + * <p>Default methods on interfaces are also detected. * @param clazz the class for which to retrieve the annotated methods * @param annotationType the annotation type for which to search * @return all annotated methods in the supplied class and its superclasses + * as well as annotated interface default methods */ private List<Method> getAnnotatedMethods(Class<?> clazz, Class<? extends Annotation> annotationType) { - List<Method> results = new ArrayList<Method>(); - for (Class<?> current : getSuperClasses(clazz)) { - for (Method method : current.getDeclaredMethods()) { - Annotation annotation = getAnnotation(method, annotationType); - if (annotation != null && !isShadowed(method, results)) { - results.add(method); - } + List<Method> methods = new ArrayList<Method>(4); + for (Method method : ReflectionUtils.getUniqueDeclaredMethods(clazz)) { + if (AnnotationUtils.getAnnotation(method, annotationType) != null) { + methods.add(method); } } - return results; - } - - /** - * Determine if the supplied {@link Method method} is <em>shadowed</em> by - * a method in the supplied {@link List list} of previous methods. - * <p>Note: This code has been borrowed from - * {@link org.junit.internal.runners.TestClass#isShadowed(Method, List)}. - * @param method the method to check for shadowing - * @param previousMethods the list of methods which have previously been processed - * @return {@code true} if the supplied method is shadowed by a - * method in the {@code previousMethods} list - */ - private boolean isShadowed(Method method, List<Method> previousMethods) { - for (Method each : previousMethods) { - if (isShadowed(method, each)) { - return true; - } - } - return false; - } - - /** - * Determine if the supplied {@linkplain Method current method} is - * <em>shadowed</em> by a {@linkplain Method previous method}. - * <p>Note: This code has been borrowed from - * {@link org.junit.internal.runners.TestClass#isShadowed(Method, Method)}. - * @param current the current method - * @param previous the previous method - * @return {@code true} if the previous method shadows the current one - */ - private boolean isShadowed(Method current, Method previous) { - if (!previous.getName().equals(current.getName())) { - return false; - } - if (previous.getParameterTypes().length != current.getParameterTypes().length) { - return false; - } - for (int i = 0; i < previous.getParameterTypes().length; i++) { - if (!previous.getParameterTypes()[i].equals(current.getParameterTypes()[i])) { - return false; - } - } - return true; + return methods; } /** @@ -531,19 +470,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis if (this.configurationAttributes == null) { Class<?> clazz = testContext.getTestClass(); - TransactionConfiguration txConfig = AnnotatedElementUtils.findMergedAnnotation(clazz, - TransactionConfiguration.class); + TransactionConfiguration txConfig = + AnnotatedElementUtils.findMergedAnnotation(clazz, TransactionConfiguration.class); if (logger.isDebugEnabled()) { logger.debug(String.format("Retrieved @TransactionConfiguration [%s] for test class [%s].", - txConfig, clazz.getName())); + txConfig, clazz.getName())); } - TransactionConfigurationAttributes configAttributes = (txConfig == null ? defaultTxConfigAttributes - : new TransactionConfigurationAttributes(txConfig.transactionManager(), txConfig.defaultRollback())); - + TransactionConfigurationAttributes configAttributes = (txConfig == null ? defaultTxConfigAttributes : + new TransactionConfigurationAttributes(txConfig.transactionManager(), txConfig.defaultRollback())); if (logger.isDebugEnabled()) { logger.debug(String.format("Using TransactionConfigurationAttributes %s for test class [%s].", - configAttributes, clazz.getName())); + configAttributes, clazz.getName())); } this.configurationAttributes = configAttributes; } diff --git a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java index f6e1ac9e..a74ca6a2 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,6 +53,7 @@ import org.springframework.web.context.support.GenericWebApplicationContext; * {@link #loadBeanDefinitions}. * * @author Sam Brannen + * @author Phillip Webb * @since 3.2 * @see #loadContext(MergedContextConfiguration) * @see #loadContext(String...) @@ -256,15 +257,17 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa * loader <i>after</i> bean definitions have been loaded into the context but * <i>before</i> the context is refreshed. * - * <p>The default implementation is empty but can be overridden in subclasses - * to customize the web application context. + * <p>The default implementation simply delegates to + * {@link AbstractContextLoader#customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)}. * * @param context the newly created web application context * @param webMergedConfig the merged context configuration to use to load the * web application context * @see #loadContext(MergedContextConfiguration) + * @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration) */ protected void customizeContext(GenericWebApplicationContext context, WebMergedContextConfiguration webMergedConfig) { + super.customizeContext(context, webMergedConfig); } // --- ContextLoader ------------------------------------------------------- diff --git a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java index a1bdb5a2..32e79eb5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.Conventions; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletContext; @@ -33,7 +33,6 @@ import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.support.AbstractTestExecutionListener; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.util.Assert; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @@ -58,9 +57,10 @@ import org.springframework.web.context.request.ServletWebRequest; * <p>Note that {@code ServletTestExecutionListener} is enabled by default but * generally takes no action if the {@linkplain TestContext#getTestClass() test * class} is not annotated with {@link WebAppConfiguration @WebAppConfiguration}. - * See the Javadoc for individual methods in this class for details. + * See the javadocs for individual methods in this class for details. * * @author Sam Brannen + * @author Phillip Webb * @since 3.2 */ public class ServletTestExecutionListener extends AbstractTestExecutionListener { @@ -70,33 +70,42 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener * whether or not the {@code ServletTestExecutionListener} should {@linkplain * RequestContextHolder#resetRequestAttributes() reset} Spring Web's * {@code RequestContextHolder} in {@link #afterTestMethod(TestContext)}. - * * <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}. */ public static final String RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE = Conventions.getQualifiedAttributeName( - ServletTestExecutionListener.class, "resetRequestContextHolder"); + ServletTestExecutionListener.class, "resetRequestContextHolder"); /** * Attribute name for a {@link TestContext} attribute which indicates that * {@code ServletTestExecutionListener} has already populated Spring Web's * {@code RequestContextHolder}. - * * <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}. */ public static final String POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE = Conventions.getQualifiedAttributeName( - ServletTestExecutionListener.class, "populatedRequestContextHolder"); + ServletTestExecutionListener.class, "populatedRequestContextHolder"); /** * Attribute name for a request attribute which indicates that the * {@link MockHttpServletRequest} stored in the {@link RequestAttributes} * in Spring Web's {@link RequestContextHolder} was created by the TestContext * framework. - * * <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}. * @since 4.2 */ public static final String CREATED_BY_THE_TESTCONTEXT_FRAMEWORK = Conventions.getQualifiedAttributeName( - ServletTestExecutionListener.class, "createdByTheTestContextFramework"); + ServletTestExecutionListener.class, "createdByTheTestContextFramework"); + + /** + * Attribute name for a {@link TestContext} attribute which indicates that that + * the {@code ServletTestExecutionListener} should be activated. When not set to + * {@code true}, activation occurs when the {@linkplain TestContext#getTestClass() + * test class} is annotated with {@link WebAppConfiguration @WebAppConfiguration}. + * <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}. + * @since 4.3 + */ + public static final String ACTIVATE_LISTENER = Conventions.getQualifiedAttributeName( + ServletTestExecutionListener.class, "activateListener"); + private static final Log logger = LogFactory.getLog(ServletTestExecutionListener.class); @@ -114,7 +123,6 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener * callback phase via Spring Web's {@link RequestContextHolder}, but only if * the {@linkplain TestContext#getTestClass() test class} is annotated with * {@link WebAppConfiguration @WebAppConfiguration}. - * * @see TestExecutionListener#prepareTestInstance(TestContext) * @see #setUpRequestContextIfNecessary(TestContext) */ @@ -128,7 +136,6 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener * {@link RequestContextHolder}, but only if the * {@linkplain TestContext#getTestClass() test class} is annotated with * {@link WebAppConfiguration @WebAppConfiguration}. - * * @see TestExecutionListener#beforeTestMethod(TestContext) * @see #setUpRequestContextIfNecessary(TestContext) */ @@ -146,11 +153,9 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener * into the test instance for subsequent tests by setting the * {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE} * in the test context to {@code true}. - * * <p>The {@link #RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE} and * {@link #POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE} will be subsequently * removed from the test context, regardless of their values. - * * @see TestExecutionListener#afterTestMethod(TestContext) */ @Override @@ -167,8 +172,9 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener testContext.removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE); } - private boolean notAnnotatedWithWebAppConfiguration(TestContext testContext) { - return AnnotationUtils.findAnnotation(testContext.getTestClass(), WebAppConfiguration.class) == null; + private boolean isActivated(TestContext testContext) { + return (Boolean.TRUE.equals(testContext.getAttribute(ACTIVATE_LISTENER)) || + AnnotatedElementUtils.hasAnnotation(testContext.getTestClass(), WebAppConfiguration.class)); } private boolean alreadyPopulatedRequestContextHolder(TestContext testContext) { @@ -176,7 +182,7 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener } private void setUpRequestContextIfNecessary(TestContext testContext) { - if (notAnnotatedWithWebAppConfiguration(testContext) || alreadyPopulatedRequestContextHolder(testContext)) { + if (!isActivated(testContext) || alreadyPopulatedRequestContextHolder(testContext)) { return; } @@ -185,14 +191,16 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener if (context instanceof WebApplicationContext) { WebApplicationContext wac = (WebApplicationContext) context; ServletContext servletContext = wac.getServletContext(); - Assert.state(servletContext instanceof MockServletContext, String.format( - "The WebApplicationContext for test context %s must be configured with a MockServletContext.", - testContext)); + if (!(servletContext instanceof MockServletContext)) { + throw new IllegalStateException(String.format( + "The WebApplicationContext for test context %s must be configured with a MockServletContext.", + testContext)); + } if (logger.isDebugEnabled()) { logger.debug(String.format( - "Setting up MockHttpServletRequest, MockHttpServletResponse, ServletWebRequest, and RequestContextHolder for test context %s.", - testContext)); + "Setting up MockHttpServletRequest, MockHttpServletResponse, ServletWebRequest, and RequestContextHolder for test context %s.", + testContext)); } MockServletContext mockServletContext = (MockServletContext) servletContext; diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java index e9d17610..695d5f5a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java @@ -23,8 +23,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.springframework.test.context.BootstrapWith; - /** * {@code @WebAppConfiguration} is a class-level annotation that is used to * declare that the {@code ApplicationContext} loaded for an integration test @@ -53,7 +51,6 @@ import org.springframework.test.context.BootstrapWith; @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited -@BootstrapWith(WebTestContextBootstrapper.class) public @interface WebAppConfiguration { /** diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java index cd1187b5..98bbdad2 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java @@ -22,6 +22,7 @@ import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.style.ToStringCreator; import org.springframework.test.context.CacheAwareContextLoaderDelegate; +import org.springframework.test.context.ContextCustomizer; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.util.ObjectUtils; @@ -132,13 +133,48 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration { String resourceBasePath, ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { + this(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceLocations, + propertySourceProperties, null, resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, parent); + } + + /** + * Create a new {@code WebMergedContextConfiguration} instance for the + * supplied parameters. + * <p>If a {@code null} value is supplied for {@code locations}, + * {@code classes}, {@code activeProfiles}, {@code propertySourceLocations}, + * or {@code propertySourceProperties} an empty array will be stored instead. + * If a {@code null} value is supplied for {@code contextInitializerClasses} + * or {@code contextCustomizers}, an empty set will be stored instead. + * If an <em>empty</em> value is supplied for the {@code resourceBasePath} + * an empty string will be used. Furthermore, active profiles will be sorted, + * and duplicate profiles will be removed. + * @param testClass the test class for which the configuration was merged + * @param locations the merged context resource locations + * @param classes the merged annotated classes + * @param contextInitializerClasses the merged context initializer classes + * @param activeProfiles the merged active bean definition profiles + * @param propertySourceLocations the merged {@code PropertySource} locations + * @param propertySourceProperties the merged {@code PropertySource} properties + * @param contextCustomizers the context customizers + * @param resourceBasePath the resource path to the root directory of the web application + * @param contextLoader the resolved {@code ContextLoader} + * @param cacheAwareContextLoaderDelegate a cache-aware context loader + * delegate with which to retrieve the parent context + * @param parent the parent configuration or {@code null} if there is no parent + * @since 4.3 + */ + public WebMergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes, + Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses, + String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, + Set<ContextCustomizer> contextCustomizers, String resourceBasePath, ContextLoader contextLoader, + CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { + super(testClass, locations, classes, contextInitializerClasses, activeProfiles, propertySourceLocations, - propertySourceProperties, contextLoader, cacheAwareContextLoaderDelegate, parent); + propertySourceProperties, contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parent); - this.resourceBasePath = !StringUtils.hasText(resourceBasePath) ? "" : resourceBasePath; + this.resourceBasePath = (StringUtils.hasText(resourceBasePath) ? resourceBasePath : ""); } - /** * Get the resource path to the root directory of the web application for the * {@linkplain #getTestClass() test class}, configured via {@code @WebAppConfiguration}. @@ -182,6 +218,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration { * {@linkplain #getActiveProfiles() active profiles}, * {@linkplain #getPropertySourceLocations() property source locations}, * {@linkplain #getPropertySourceProperties() property source properties}, + * {@linkplain #getContextCustomizers() context customizers}, * {@linkplain #getResourceBasePath() resource base path}, the name of the * {@link #getContextLoader() ContextLoader}, and the * {@linkplain #getParent() parent configuration}. @@ -196,6 +233,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration { .append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles())) .append("propertySourceLocations", ObjectUtils.nullSafeToString(getPropertySourceLocations())) .append("propertySourceProperties", ObjectUtils.nullSafeToString(getPropertySourceProperties())) + .append("contextCustomizers", getContextCustomizers()) .append("resourceBasePath", getResourceBasePath()) .append("contextLoader", nullSafeToString(getContextLoader())) .append("parent", getParent()) diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java index 94741f5a..f779450c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/WebTestContextBootstrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package org.springframework.test.context.web; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.TestContextBootstrapper; @@ -45,12 +45,12 @@ public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper { */ @Override protected Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass) { - if (AnnotationUtils.findAnnotation(testClass, WebAppConfiguration.class) != null) { + if (AnnotatedElementUtils.findMergedAnnotation(testClass, WebAppConfiguration.class) != null) { return WebDelegatingSmartContextLoader.class; } - - // else... - return super.getDefaultContextLoaderClass(testClass); + else { + return super.getDefaultContextLoaderClass(testClass); + } } /** @@ -61,14 +61,14 @@ public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper { */ @Override protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) { - WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(mergedConfig.getTestClass(), - WebAppConfiguration.class); + WebAppConfiguration webAppConfiguration = + AnnotatedElementUtils.findMergedAnnotation(mergedConfig.getTestClass(), WebAppConfiguration.class); if (webAppConfiguration != null) { return new WebMergedContextConfiguration(mergedConfig, webAppConfiguration.value()); } - - // else... - return mergedConfig; + else { + return mergedConfig; + } } } diff --git a/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainer.java b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainer.java new file mode 100644 index 00000000..bd7c7257 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainer.java @@ -0,0 +1,135 @@ +/* + * Copyright 2002-2016 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.web.socket; + +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.Set; +import javax.websocket.ClientEndpointConfig; +import javax.websocket.DeploymentException; +import javax.websocket.Endpoint; +import javax.websocket.Extension; +import javax.websocket.Session; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; + +/** + * Mock implementation of the {@link javax.websocket.server.ServerContainer} interface. + * + * @author Sam Brannen + * @since 4.3.1 + */ +class MockServerContainer implements ServerContainer { + + private long defaultAsyncSendTimeout; + + private long defaultMaxSessionIdleTimeout; + + private int defaultMaxBinaryMessageBufferSize; + + private int defaultMaxTextMessageBufferSize; + + + // --- WebSocketContainer -------------------------------------------------- + + @Override + public long getDefaultAsyncSendTimeout() { + return this.defaultAsyncSendTimeout; + } + + @Override + public void setAsyncSendTimeout(long timeout) { + this.defaultAsyncSendTimeout = timeout; + } + + @Override + public long getDefaultMaxSessionIdleTimeout() { + return this.defaultMaxSessionIdleTimeout; + } + + @Override + public void setDefaultMaxSessionIdleTimeout(long timeout) { + this.defaultMaxSessionIdleTimeout = timeout; + } + + @Override + public int getDefaultMaxBinaryMessageBufferSize() { + return this.defaultMaxBinaryMessageBufferSize; + } + + @Override + public void setDefaultMaxBinaryMessageBufferSize(int max) { + this.defaultMaxBinaryMessageBufferSize = max; + } + + @Override + public int getDefaultMaxTextMessageBufferSize() { + return this.defaultMaxTextMessageBufferSize; + } + + @Override + public void setDefaultMaxTextMessageBufferSize(int max) { + this.defaultMaxTextMessageBufferSize = max; + } + + @Override + public Set<Extension> getInstalledExtensions() { + return Collections.emptySet(); + } + + @Override + public Session connectToServer(Object annotatedEndpointInstance, URI path) throws DeploymentException, IOException { + throw new UnsupportedOperationException("MockServerContainer does not support connectToServer(Object, URI)"); + } + + @Override + public Session connectToServer(Class<?> annotatedEndpointClass, URI path) throws DeploymentException, IOException { + throw new UnsupportedOperationException("MockServerContainer does not support connectToServer(Class, URI)"); + } + + @Override + public Session connectToServer(Endpoint endpointInstance, ClientEndpointConfig cec, URI path) + throws DeploymentException, IOException { + + throw new UnsupportedOperationException( + "MockServerContainer does not support connectToServer(Endpoint, ClientEndpointConfig, URI)"); + } + + @Override + public Session connectToServer(Class<? extends Endpoint> endpointClass, ClientEndpointConfig cec, URI path) + throws DeploymentException, IOException { + + throw new UnsupportedOperationException( + "MockServerContainer does not support connectToServer(Class, ClientEndpointConfig, URI)"); + } + + + // --- ServerContainer ----------------------------------------------------- + + @Override + public void addEndpoint(Class<?> endpointClass) throws DeploymentException { + throw new UnsupportedOperationException("MockServerContainer does not support addEndpoint(Class)"); + } + + @Override + public void addEndpoint(ServerEndpointConfig serverConfig) throws DeploymentException { + throw new UnsupportedOperationException( + "MockServerContainer does not support addEndpoint(ServerEndpointConfig)"); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizer.java b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizer.java new file mode 100644 index 00000000..e68914ee --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizer.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2016 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.web.socket; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.web.context.WebApplicationContext; + +/** + * {@link ContextCustomizer} that instantiates a new {@link MockServerContainer} + * and stores it in the {@code ServletContext} under the attribute named + * {@code "javax.websocket.server.ServerContainer"}. + * + * @author Sam Brannen + * @since 4.3.1 + */ +class MockServerContainerContextCustomizer implements ContextCustomizer { + + @Override + public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + if (context instanceof WebApplicationContext) { + WebApplicationContext wac = (WebApplicationContext) context; + wac.getServletContext().setAttribute("javax.websocket.server.ServerContainer", new MockServerContainer()); + } + } + + @Override + public boolean equals(Object other) { + return (this == other || (other != null && getClass() == other.getClass())); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizerFactory.java b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizerFactory.java new file mode 100644 index 00000000..c76959c3 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/web/socket/MockServerContainerContextCustomizerFactory.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2016 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.web.socket; + +import java.util.List; + +import org.springframework.beans.BeanUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; +import org.springframework.util.ClassUtils; + +/** + * {@link ContextCustomizerFactory} which creates a {@link MockServerContainerContextCustomizer} + * if WebSocket support is present in the classpath and the test class is annotated + * with {@code @WebAppConfiguration}. + * + * @author Sam Brannen + * @since 4.3.1 + */ +class MockServerContainerContextCustomizerFactory implements ContextCustomizerFactory { + + private static final String WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME = + "org.springframework.test.context.web.WebAppConfiguration"; + + private static final String MOCK_SERVER_CONTAINER_CONTEXT_CUSTOMIZER_CLASS_NAME = + "org.springframework.test.context.web.socket.MockServerContainerContextCustomizer"; + + private static final boolean webSocketPresent = ClassUtils.isPresent("javax.websocket.server.ServerContainer", + MockServerContainerContextCustomizerFactory.class.getClassLoader()); + + + @Override + public ContextCustomizer createContextCustomizer(Class<?> testClass, + List<ContextConfigurationAttributes> configAttributes) { + + if (webSocketPresent && isAnnotatedWithWebAppConfiguration(testClass)) { + try { + Class<?> clazz = ClassUtils.forName(MOCK_SERVER_CONTAINER_CONTEXT_CUSTOMIZER_CLASS_NAME, + getClass().getClassLoader()); + return (ContextCustomizer) BeanUtils.instantiateClass(clazz); + } + catch (Throwable ex) { + throw new IllegalStateException("Failed to enable WebSocket test support; could not load class: " + + MOCK_SERVER_CONTAINER_CONTEXT_CUSTOMIZER_CLASS_NAME, ex); + } + } + + // Else, nothing to customize + return null; + } + + private static boolean isAnnotatedWithWebAppConfiguration(Class<?> testClass) { + return (AnnotatedElementUtils.findMergedAnnotationAttributes(testClass, + WEB_APP_CONFIGURATION_ANNOTATION_CLASS_NAME, false, false) != null); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/web/socket/package-info.java b/spring-test/src/main/java/org/springframework/test/context/web/socket/package-info.java new file mode 100644 index 00000000..7b97278f --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/web/socket/package-info.java @@ -0,0 +1,4 @@ +/** + * WebSocket support classes for the <em>Spring TestContext Framework</em>. + */ +package org.springframework.test.context.web.socket; diff --git a/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java b/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java index 8e3e9a05..e0e1bc0e 100644 --- a/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java +++ b/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -124,6 +124,7 @@ public class JdbcTestUtils { */ public static int deleteFromTableWhere(JdbcTemplate jdbcTemplate, String tableName, String whereClause, Object... args) { + String sql = "DELETE FROM " + tableName; if (StringUtils.hasText(whereClause)) { sql += " WHERE " + whereClause; @@ -170,6 +171,7 @@ public class JdbcTestUtils { @Deprecated public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader, String sqlResourcePath, boolean continueOnError) throws DataAccessException { + Resource resource = resourceLoader.getResource(sqlResourcePath); executeSqlScript(jdbcTemplate, resource, continueOnError); } @@ -197,6 +199,7 @@ public class JdbcTestUtils { @Deprecated public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError) throws DataAccessException { + executeSqlScript(jdbcTemplate, new EncodedResource(resource), continueOnError); } @@ -220,6 +223,7 @@ public class JdbcTestUtils { @Deprecated public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource resource, boolean continueOnError) throws DataAccessException { + new ResourceDatabasePopulator(continueOnError, false, resource.getEncoding(), resource.getResource()).execute(jdbcTemplate.getDataSource()); } @@ -288,4 +292,5 @@ public class JdbcTestUtils { public static void splitSqlScript(String script, char delim, List<String> statements) { ScriptUtils.splitSqlScript(script, delim, statements); } + } diff --git a/spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java b/spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java index 4eda8a45..7660ee2c 100644 --- a/spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java +++ b/spring-test/src/main/java/org/springframework/test/util/AopTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import org.springframework.util.Assert; * @since 4.2 * @see org.springframework.aop.support.AopUtils * @see org.springframework.aop.framework.AopProxyUtils + * @see ReflectionTestUtils */ public class AopTestUtils { diff --git a/spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java b/spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java index 0302686b..f07574e8 100644 --- a/spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java +++ b/spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.test.util; import org.springframework.util.ObjectUtils; @@ -26,13 +27,8 @@ import org.springframework.util.ObjectUtils; */ public abstract class AssertionErrors { - - private AssertionErrors() { - } - /** * Fails a test with the given message. - * * @param message describes the reason for the failure */ public static void fail(String message) { @@ -42,7 +38,6 @@ public abstract class AssertionErrors { /** * Fails a test with the given message passing along expected and actual * values to be added to the message. - * * <p>For example given: * <pre class="code"> * assertEquals("Response header [" + name + "]", actual, expected); @@ -51,7 +46,6 @@ public abstract class AssertionErrors { * <pre class="code"> * Response header [Accept] expected:<application/json> but was:<text/plain> * </pre> - * * @param message describes the value that failed the match * @param expected expected value * @param actual actual value @@ -63,7 +57,6 @@ public abstract class AssertionErrors { /** * Assert the given condition is {@code true} and raise an * {@link AssertionError} if it is not. - * * @param message the message * @param condition the condition to test for */ @@ -79,14 +72,13 @@ public abstract class AssertionErrors { * <pre class="code"> * assertEquals("Response header [" + name + "]", actual, expected); * </pre> - * * @param message describes the value being checked * @param expected the expected value * @param actual the actual value */ public static void assertEquals(String message, Object expected, Object actual) { if (!ObjectUtils.nullSafeEquals(expected, actual)) { - fail(message, expected, actual); + fail(message, ObjectUtils.nullSafeToString(expected), ObjectUtils.nullSafeToString(actual)); } } diff --git a/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java b/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java index a82b8a40..cbb74dc6 100644 --- a/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java +++ b/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java @@ -16,27 +16,21 @@ package org.springframework.test.util; -import java.lang.reflect.Array; -import java.lang.reflect.Method; import java.text.ParseException; import java.util.List; import java.util.Map; +import com.jayway.jsonpath.InvalidPathException; +import com.jayway.jsonpath.JsonPath; import org.hamcrest.Matcher; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; -import com.jayway.jsonpath.InvalidPathException; -import com.jayway.jsonpath.JsonPath; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsInstanceOf.instanceOf; -import static org.springframework.test.util.AssertionErrors.assertEquals; -import static org.springframework.test.util.AssertionErrors.assertTrue; -import static org.springframework.test.util.AssertionErrors.fail; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.core.IsInstanceOf.*; +import static org.springframework.test.util.AssertionErrors.*; /** * A helper class for applying assertions via JSON path expressions. @@ -52,26 +46,6 @@ import static org.springframework.test.util.AssertionErrors.fail; */ public class JsonPathExpectationsHelper { - private static Method compileMethod; - - private static Object emptyFilters; - - static { - // Reflective bridging between JsonPath 0.9.x and 1.x - for (Method candidate : JsonPath.class.getMethods()) { - if (candidate.getName().equals("compile")) { - Class<?>[] paramTypes = candidate.getParameterTypes(); - if (paramTypes.length == 2 && String.class == paramTypes[0] && paramTypes[1].isArray()) { - compileMethod = candidate; - emptyFilters = Array.newInstance(paramTypes[1].getComponentType(), 0); - break; - } - } - } - Assert.state(compileMethod != null, "Unexpected JsonPath API - no compile(String, ...) method found"); - } - - private final String expression; private final JsonPath jsonPath; @@ -86,8 +60,7 @@ public class JsonPathExpectationsHelper { public JsonPathExpectationsHelper(String expression, Object... args) { Assert.hasText(expression, "expression must not be null or empty"); this.expression = String.format(expression, args); - this.jsonPath = (JsonPath) ReflectionUtils.invokeMethod( - compileMethod, null, this.expression, emptyFilters); + this.jsonPath = JsonPath.compile(this.expression); } diff --git a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java index 913986a4..9c203d74 100644 --- a/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java +++ b/spring-test/src/main/java/org/springframework/test/util/MetaAnnotationUtils.java @@ -57,8 +57,8 @@ public abstract class MetaAnnotationUtils { /** * Find the {@link AnnotationDescriptor} for the supplied {@code annotationType} - * on the supplied {@link Class}, traversing its annotations and superclasses - * if no annotation can be found on the given class itself. + * on the supplied {@link Class}, traversing its annotations, interfaces, and + * superclasses if no annotation can be found on the given class itself. * <p>This method explicitly handles class-level annotations which are not * declared as {@linkplain java.lang.annotation.Inherited inherited} <em>as * well as meta-annotations</em>. @@ -67,14 +67,12 @@ public abstract class MetaAnnotationUtils { * <li>Search for the annotation on the given class and return a corresponding * {@code AnnotationDescriptor} if found. * <li>Recursively search through all annotations that the given class declares. + * <li>Recursively search through all interfaces implemented by the given class. * <li>Recursively search through the superclass hierarchy of the given class. * </ol> * <p>In this context, the term <em>recursively</em> means that the search - * process continues by returning to step #1 with the current annotation or - * superclass as the class to look for annotations on. - * <p>If the supplied {@code clazz} is an interface, only the interface - * itself will be checked; the inheritance hierarchy for interfaces will not - * be traversed. + * process continues by returning to step #1 with the current annotation, + * interface, or superclass as the class to look for annotations on. * @param clazz the class to look for annotations on * @param annotationType the type of annotation to look for * @return the corresponding annotation descriptor if the annotation was found; @@ -123,6 +121,15 @@ public abstract class MetaAnnotationUtils { } } + // Declared on interface? + for (Class<?> ifc : clazz.getInterfaces()) { + AnnotationDescriptor<T> descriptor = findAnnotationDescriptor(ifc, visited, annotationType); + if (descriptor != null) { + return new AnnotationDescriptor<T>(clazz, descriptor.getDeclaringClass(), + descriptor.getComposedAnnotation(), descriptor.getAnnotation()); + } + } + // Declared on a superclass? return findAnnotationDescriptor(clazz.getSuperclass(), visited, annotationType); } @@ -132,8 +139,9 @@ public abstract class MetaAnnotationUtils { * in the inheritance hierarchy of the specified {@code clazz} (including * the specified {@code clazz} itself) which declares at least one of the * specified {@code annotationTypes}. - * <p>This method traverses the annotations and superclasses of the specified - * {@code clazz} if no annotation can be found on the given class itself. + * <p>This method traverses the annotations, interfaces, and superclasses + * of the specified {@code clazz} if no annotation can be found on the given + * class itself. * <p>This method explicitly handles class-level annotations which are not * declared as {@linkplain java.lang.annotation.Inherited inherited} <em>as * well as meta-annotations</em>. @@ -143,14 +151,12 @@ public abstract class MetaAnnotationUtils { * the given class and return a corresponding {@code UntypedAnnotationDescriptor} * if found. * <li>Recursively search through all annotations that the given class declares. + * <li>Recursively search through all interfaces implemented by the given class. * <li>Recursively search through the superclass hierarchy of the given class. * </ol> * <p>In this context, the term <em>recursively</em> means that the search - * process continues by returning to step #1 with the current annotation or - * superclass as the class to look for annotations on. - * <p>If the supplied {@code clazz} is an interface, only the interface - * itself will be checked; the inheritance hierarchy for interfaces will not - * be traversed. + * process continues by returning to step #1 with the current annotation, + * interface, or superclass as the class to look for annotations on. * @param clazz the class to look for annotations on * @param annotationTypes the types of annotations to look for * @return the corresponding annotation descriptor if one of the annotations @@ -203,6 +209,15 @@ public abstract class MetaAnnotationUtils { } } + // Declared on interface? + for (Class<?> ifc : clazz.getInterfaces()) { + UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(ifc, visited, annotationTypes); + if (descriptor != null) { + return new UntypedAnnotationDescriptor(clazz, descriptor.getDeclaringClass(), + descriptor.getComposedAnnotation(), descriptor.getAnnotation()); + } + } + // Declared on a superclass? return findAnnotationDescriptorForTypes(clazz.getSuperclass(), visited, annotationTypes); } diff --git a/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java b/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java index f40227fb..4f508511 100644 --- a/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java +++ b/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,7 @@ import org.springframework.util.StringUtils; * @author Juergen Hoeller * @since 2.5 * @see ReflectionUtils + * @see AopTestUtils */ public class ReflectionTestUtils { @@ -137,6 +138,9 @@ public class ReflectionTestUtils { * Set the {@linkplain Field field} with the given {@code name}/{@code type} * on the provided {@code targetObject}/{@code targetClass} to the supplied * {@code value}. + * <p>If the supplied {@code targetObject} is a <em>proxy</em>, it will + * be {@linkplain AopTestUtils#getUltimateTargetObject unwrapped} allowing + * the field to be set on the ultimate target of the proxy. * <p>This method traverses the class hierarchy in search of the desired * field. In addition, an attempt will be made to make non-{@code public} * fields <em>accessible</em>, thus allowing one to set {@code protected}, @@ -150,33 +154,36 @@ public class ReflectionTestUtils { * @param value the value to set * @param type the type of the field to set; may be {@code null} if * {@code name} is specified + * @since 4.2 * @see ReflectionUtils#findField(Class, String, Class) * @see ReflectionUtils#makeAccessible(Field) * @see ReflectionUtils#setField(Field, Object, Object) - * @since 4.2 + * @see AopTestUtils#getUltimateTargetObject(Object) */ public static void setField(Object targetObject, Class<?> targetClass, String name, Object value, Class<?> type) { Assert.isTrue(targetObject != null || targetClass != null, "Either targetObject or targetClass for the field must be specified"); + Object ultimateTarget = (targetObject != null ? AopTestUtils.getUltimateTargetObject(targetObject) : null); + if (targetClass == null) { - targetClass = targetObject.getClass(); + targetClass = ultimateTarget.getClass(); } Field field = ReflectionUtils.findField(targetClass, name, type); if (field == null) { throw new IllegalArgumentException(String.format( - "Could not find field '%s' of type [%s] on target object [%s] or target class [%s]", name, type, - targetObject, targetClass)); + "Could not find field '%s' of type [%s] on %s or target class [%s]", name, type, + safeToString(ultimateTarget), targetClass)); } if (logger.isDebugEnabled()) { logger.debug(String.format( - "Setting field '%s' of type [%s] on target object [%s] or target class [%s] to value [%s]", name, type, - targetObject, targetClass, value)); + "Setting field '%s' of type [%s] on %s or target class [%s] to value [%s]", name, type, + safeToString(ultimateTarget), targetClass, value)); } ReflectionUtils.makeAccessible(field); - ReflectionUtils.setField(field, targetObject, value); + ReflectionUtils.setField(field, ultimateTarget, value); } /** @@ -213,6 +220,9 @@ public class ReflectionTestUtils { /** * Get the value of the {@linkplain Field field} with the given {@code name} * from the provided {@code targetObject}/{@code targetClass}. + * <p>If the supplied {@code targetObject} is a <em>proxy</em>, it will + * be {@linkplain AopTestUtils#getUltimateTargetObject unwrapped} allowing + * the field to be retrieved from the ultimate target of the proxy. * <p>This method traverses the class hierarchy in search of the desired * field. In addition, an attempt will be made to make non-{@code public} * fields <em>accessible</em>, thus allowing one to get {@code protected}, @@ -229,28 +239,30 @@ public class ReflectionTestUtils { * @see ReflectionUtils#findField(Class, String, Class) * @see ReflectionUtils#makeAccessible(Field) * @see ReflectionUtils#getField(Field, Object) + * @see AopTestUtils#getUltimateTargetObject(Object) */ public static Object getField(Object targetObject, Class<?> targetClass, String name) { Assert.isTrue(targetObject != null || targetClass != null, "Either targetObject or targetClass for the field must be specified"); + Object ultimateTarget = (targetObject != null ? AopTestUtils.getUltimateTargetObject(targetObject) : null); + if (targetClass == null) { - targetClass = targetObject.getClass(); + targetClass = ultimateTarget.getClass(); } Field field = ReflectionUtils.findField(targetClass, name); if (field == null) { - throw new IllegalArgumentException( - String.format("Could not find field '%s' on target object [%s] or target class [%s]", name, - targetObject, targetClass)); + throw new IllegalArgumentException(String.format("Could not find field '%s' on %s or target class [%s]", + name, safeToString(ultimateTarget), targetClass)); } if (logger.isDebugEnabled()) { - logger.debug(String.format("Getting field '%s' from target object [%s] or target class [%s]", name, - targetObject, targetClass)); + logger.debug(String.format("Getting field '%s' from %s or target class [%s]", name, + safeToString(ultimateTarget), targetClass)); } ReflectionUtils.makeAccessible(field); - return ReflectionUtils.getField(field, targetObject); + return ReflectionUtils.getField(field, ultimateTarget); } /** @@ -314,13 +326,16 @@ public class ReflectionTestUtils { method = ReflectionUtils.findMethod(target.getClass(), setterMethodName, paramTypes); } if (method == null) { - throw new IllegalArgumentException("Could not find setter method '" + setterMethodName + - "' on target [" + target + "] with parameter type [" + type + "]"); + throw new IllegalArgumentException(String.format( + "Could not find setter method '%s' on %s with parameter type [%s]", setterMethodName, + safeToString(target), type)); } if (logger.isDebugEnabled()) { - logger.debug("Invoking setter method '" + setterMethodName + "' on target [" + target + "]"); + logger.debug(String.format("Invoking setter method '%s' on %s with value [%s]", setterMethodName, + safeToString(target), value)); } + ReflectionUtils.makeAccessible(method); ReflectionUtils.invokeMethod(method, target, value); } @@ -359,12 +374,12 @@ public class ReflectionTestUtils { method = ReflectionUtils.findMethod(target.getClass(), getterMethodName); } if (method == null) { - throw new IllegalArgumentException("Could not find getter method '" + getterMethodName + - "' on target [" + target + "]"); + throw new IllegalArgumentException(String.format( + "Could not find getter method '%s' on %s", getterMethodName, safeToString(target))); } if (logger.isDebugEnabled()) { - logger.debug("Invoking getter method '" + getterMethodName + "' on target [" + target + "]"); + logger.debug(String.format("Invoking getter method '%s' on %s", getterMethodName, safeToString(target))); } ReflectionUtils.makeAccessible(method); return ReflectionUtils.invokeMethod(method, target); @@ -399,8 +414,8 @@ public class ReflectionTestUtils { methodInvoker.prepare(); if (logger.isDebugEnabled()) { - logger.debug("Invoking method '" + name + "' on target [" + target + "] with arguments [" + - ObjectUtils.nullSafeToString(args) + "]"); + logger.debug(String.format("Invoking method '%s' on %s with arguments %s", name, safeToString(target), + ObjectUtils.nullSafeToString(args))); } return (T) methodInvoker.invoke(); @@ -411,4 +426,14 @@ public class ReflectionTestUtils { } } + private static String safeToString(Object target) { + try { + return String.format("target object [%s]", target); + } + catch (Exception ex) { + return String.format("target of type [%s] whose toString() method threw [%s]", + (target != null ? target.getClass().getName() : "unknown"), ex); + } + } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/client/AbstractRequestExpectationManager.java b/spring-test/src/main/java/org/springframework/test/web/client/AbstractRequestExpectationManager.java new file mode 100644 index 00000000..37139cb7 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/client/AbstractRequestExpectationManager.java @@ -0,0 +1,189 @@ +/* + * Copyright 2002-2016 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.web.client; + +import java.io.IOException; +import java.net.URI; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.Assert; + +/** + * Base class for {@code RequestExpectationManager} implementations responsible + * for storing expectations and actual requests, and checking for unsatisfied + * expectations at the end. + * + * <p>Subclasses are responsible for validating each request by matching it to + * to expectations following the order of declaration or not. + * + * @author Rossen Stoyanchev + * @since 4.3 + */ +public abstract class AbstractRequestExpectationManager implements RequestExpectationManager { + + private final List<RequestExpectation> expectations = new LinkedList<RequestExpectation>(); + + private final List<ClientHttpRequest> requests = new LinkedList<ClientHttpRequest>(); + + + protected List<RequestExpectation> getExpectations() { + return this.expectations; + } + + protected List<ClientHttpRequest> getRequests() { + return this.requests; + } + + + @Override + public ResponseActions expectRequest(ExpectedCount count, RequestMatcher matcher) { + Assert.state(getRequests().isEmpty(), "Cannot add more expectations after actual requests are made"); + RequestExpectation expectation = new DefaultRequestExpectation(count, matcher); + getExpectations().add(expectation); + return expectation; + } + + @Override + public ClientHttpResponse validateRequest(ClientHttpRequest request) throws IOException { + if (getRequests().isEmpty()) { + afterExpectationsDeclared(); + } + ClientHttpResponse response = validateRequestInternal(request); + getRequests().add(request); + return response; + } + + /** + * Invoked after the phase of declaring expected requests is over. This is + * detected from {@link #validateRequest} on the first actual request. + */ + protected void afterExpectationsDeclared() { + } + + /** + * Subclasses must implement the actual validation of the request + * matching it to a declared expectation. + */ + protected abstract ClientHttpResponse validateRequestInternal(ClientHttpRequest request) throws IOException; + + @Override + public void verify() { + if (getExpectations().isEmpty()) { + return; + } + int count = 0; + for (RequestExpectation expectation : getExpectations()) { + if (!expectation.isSatisfied()) { + count++; + } + } + if (count > 0) { + String message = "Further request(s) expected leaving " + count + " unsatisfied expectation(s).\n"; + throw new AssertionError(message + getRequestDetails()); + } + } + + /** + * Return details of executed requests. + */ + protected String getRequestDetails() { + StringBuilder sb = new StringBuilder(); + sb.append(getRequests().size()).append(" request(s) executed"); + if (!getRequests().isEmpty()) { + sb.append(":\n"); + for (ClientHttpRequest request : getRequests()) { + sb.append(request.toString()).append("\n"); + } + } + else { + sb.append(".\n"); + } + return sb.toString(); + } + + /** + * Return an {@code AssertionError} that a sub-class can raise for an + * unexpected request. + */ + protected AssertionError createUnexpectedRequestError(ClientHttpRequest request) { + HttpMethod method = request.getMethod(); + URI uri = request.getURI(); + String message = "No further requests expected: HTTP " + method + " " + uri + "\n"; + return new AssertionError(message + getRequestDetails()); + } + + @Override + public void reset() { + this.expectations.clear(); + this.requests.clear(); + } + + + /** + * Helper class to manage a group of request expectations. It helps with + * operations against the entire group such as finding a match and updating + * (add or remove) based on expected request count. + */ + protected static class RequestExpectationGroup { + + private final Set<RequestExpectation> expectations = new LinkedHashSet<RequestExpectation>(); + + public Set<RequestExpectation> getExpectations() { + return this.expectations; + } + + public void update(RequestExpectation expectation) { + if (expectation.hasRemainingCount()) { + getExpectations().add(expectation); + } + else { + getExpectations().remove(expectation); + } + } + + public void updateAll(Collection<RequestExpectation> expectations) { + for (RequestExpectation expectation : expectations) { + update(expectation); + } + } + + public RequestExpectation findExpectation(ClientHttpRequest request) throws IOException { + for (RequestExpectation expectation : getExpectations()) { + try { + expectation.match(request); + return expectation; + } + catch (AssertionError error) { + // Ignore + } + } + return null; + } + + public void reset() { + this.expectations.clear(); + } + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/client/DefaultRequestExpectation.java b/spring-test/src/main/java/org/springframework/test/web/client/DefaultRequestExpectation.java new file mode 100644 index 00000000..93a4752a --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/client/DefaultRequestExpectation.java @@ -0,0 +1,146 @@ +/* + * Copyright 2002-2016 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.web.client; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.Assert; + +/** + * Default implementation of {@code RequestExpectation} that simply delegates + * to the request matchers and the response creator it contains. + * + * @author Rossen Stoyanchev + * @since 4.3 + */ +public class DefaultRequestExpectation implements RequestExpectation { + + private final RequestCount requestCount; + + private final List<RequestMatcher> requestMatchers = new LinkedList<RequestMatcher>(); + + private ResponseCreator responseCreator; + + + /** + * Create a new request expectation that should be called a number of times + * as indicated by {@code RequestCount}. + * @param expectedCount the expected request expectedCount + */ + public DefaultRequestExpectation(ExpectedCount expectedCount, RequestMatcher requestMatcher) { + Assert.notNull(expectedCount, "'expectedCount' is required"); + Assert.notNull(requestMatcher, "'requestMatcher' is required"); + this.requestCount = new RequestCount(expectedCount); + this.requestMatchers.add(requestMatcher); + } + + + protected RequestCount getRequestCount() { + return this.requestCount; + } + + protected List<RequestMatcher> getRequestMatchers() { + return this.requestMatchers; + } + + protected ResponseCreator getResponseCreator() { + return this.responseCreator; + } + + @Override + public ResponseActions andExpect(RequestMatcher requestMatcher) { + Assert.notNull(requestMatcher, "RequestMatcher is required"); + this.requestMatchers.add(requestMatcher); + return this; + } + + @Override + public void andRespond(ResponseCreator responseCreator) { + Assert.notNull(responseCreator, "ResponseCreator is required"); + this.responseCreator = responseCreator; + } + + @Override + public void match(ClientHttpRequest request) throws IOException { + for (RequestMatcher matcher : getRequestMatchers()) { + matcher.match(request); + } + } + + @Override + public ClientHttpResponse createResponse(ClientHttpRequest request) throws IOException { + ResponseCreator responseCreator = getResponseCreator(); + if (responseCreator == null) { + throw new IllegalStateException("createResponse called before ResponseCreator was set"); + } + getRequestCount().incrementAndValidate(); + return responseCreator.createResponse(request); + } + + @Override + public boolean hasRemainingCount() { + return getRequestCount().hasRemainingCount(); + } + + @Override + public boolean isSatisfied() { + return getRequestCount().isSatisfied(); + } + + + /** + * Helper class that keeps track of actual vs expected request count. + */ + protected static class RequestCount { + + private final ExpectedCount expectedCount; + + private int matchedRequestCount; + + public RequestCount(ExpectedCount expectedCount) { + this.expectedCount = expectedCount; + } + + public ExpectedCount getExpectedCount() { + return this.expectedCount; + } + + public int getMatchedRequestCount() { + return this.matchedRequestCount; + } + + public void incrementAndValidate() { + this.matchedRequestCount++; + if (getMatchedRequestCount() > getExpectedCount().getMaxCount()) { + throw new AssertionError("No more calls expected."); + } + } + + public boolean hasRemainingCount() { + return (getMatchedRequestCount() < getExpectedCount().getMaxCount()); + } + + public boolean isSatisfied() { + return (getMatchedRequestCount() >= getExpectedCount().getMinCount()); + } + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/client/ExpectedCount.java b/spring-test/src/main/java/org/springframework/test/web/client/ExpectedCount.java new file mode 100644 index 00000000..09f3077c --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/client/ExpectedCount.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2016 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.web.client; + +import org.springframework.util.Assert; + +/** + * A simple type representing a range for an expected count. + * + * <p>Examples: + * <pre> + * import static org.springframework.test.web.client.ExpectedCount.* + * + * once() + * manyTimes() + * times(5) + * min(2) + * max(4) + * between(2, 4) + * </pre> + * + * @author Rossen Stoyanchev + * @since 4.3 + */ +public class ExpectedCount { + + private final int minCount; + + private final int maxCount; + + + /** + * Private constructor. + * See static factory methods in this class. + */ + private ExpectedCount(int minCount, int maxCount) { + Assert.isTrue(minCount >= 1, "minCount >= 0 is required"); + Assert.isTrue(maxCount >= minCount, "maxCount >= minCount is required"); + this.minCount = minCount; + this.maxCount = maxCount; + } + + + /** + * Return the {@code min} boundary of the expected count range. + */ + public int getMinCount() { + return this.minCount; + } + + /** + * Return the {@code max} boundary of the expected count range. + */ + public int getMaxCount() { + return this.maxCount; + } + + + /** + * Exactly once. + */ + public static ExpectedCount once() { + return new ExpectedCount(1, 1); + } + + /** + * Many times (range of 1..Integer.MAX_VALUE). + */ + public static ExpectedCount manyTimes() { + return new ExpectedCount(1, Integer.MAX_VALUE); + } + + /** + * Exactly N times. + */ + public static ExpectedCount times(int count) { + Assert.isTrue(count >= 1, "'count' must be >= 1"); + return new ExpectedCount(count, count); + } + + /** + * At least {@code min} number of times. + */ + public static ExpectedCount min(int min) { + Assert.isTrue(min >= 1, "'min' must be >= 1"); + return new ExpectedCount(min, Integer.MAX_VALUE); + } + + /** + * At most {@code max} number of times. + */ + public static ExpectedCount max(int max) { + Assert.isTrue(max >= 1, "'max' must be >= 1"); + return new ExpectedCount(1, max); + } + + /** + * Between {@code min} and {@code max} number of times. + */ + public static ExpectedCount between(int min, int max) { + return new ExpectedCount(min, max); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/client/MockMvcClientHttpRequestFactory.java b/spring-test/src/main/java/org/springframework/test/web/client/MockMvcClientHttpRequestFactory.java index 7c7b6115..d1086a53 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/MockMvcClientHttpRequestFactory.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/MockMvcClientHttpRequestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.test.web.client; import java.io.IOException; import java.net.URI; +import java.nio.charset.Charset; import java.util.List; import org.springframework.http.HttpHeaders; @@ -43,6 +44,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder */ public class MockMvcClientHttpRequestFactory implements ClientHttpRequestFactory { + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + private final MockMvc mockMvc; @@ -50,6 +53,7 @@ public class MockMvcClientHttpRequestFactory implements ClientHttpRequestFactory this.mockMvc = mockMvc; } + @Override public ClientHttpRequest createRequest(final URI uri, final HttpMethod httpMethod) throws IOException { return new MockClientHttpRequest(httpMethod, uri) { @@ -73,7 +77,7 @@ public class MockMvcClientHttpRequestFactory implements ClientHttpRequestFactory return clientResponse; } catch (Exception ex) { - byte[] body = ex.toString().getBytes("UTF-8"); + byte[] body = ex.toString().getBytes(UTF8_CHARSET); return new MockClientHttpResponse(body, HttpStatus.INTERNAL_SERVER_ERROR); } } diff --git a/spring-test/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java b/spring-test/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java index e1376041..09207f8c 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,17 +18,14 @@ package org.springframework.test.web.client; import java.io.IOException; import java.net.URI; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; import org.springframework.http.HttpMethod; import org.springframework.http.client.AsyncClientHttpRequest; import org.springframework.http.client.AsyncClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.test.web.client.match.MockRestRequestMatchers; -import org.springframework.test.web.client.response.MockRestResponseCreators; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.mock.http.client.MockAsyncClientHttpRequest; import org.springframework.util.Assert; import org.springframework.web.client.AsyncRestTemplate; import org.springframework.web.client.RestTemplate; @@ -36,53 +33,33 @@ import org.springframework.web.client.support.RestGatewaySupport; /** * <strong>Main entry point for client-side REST testing</strong>. Used for tests - * that involve direct or indirect (through client code) use of the - * {@link RestTemplate}. Provides a way to set up fine-grained expectations - * on the requests that will be performed through the {@code RestTemplate} and - * a way to define the responses to send back removing the need for an - * actual running server. + * that involve direct or indirect use of the {@link RestTemplate}. Provides a + * way to set up expected requests that will be performed through the + * {@code RestTemplate} as well as mock responses to send back thus removing the + * need for an actual server. * - * <p>Below is an example: - * <pre class="code"> - * import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; - * import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; - * import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; - * - * ... + * <p>Below is an example that assumes static imports from + * {@code MockRestRequestMatchers}, {@code MockRestResponseCreators}, + * and {@code ExpectedCount}: * + * <pre class="code"> * RestTemplate restTemplate = new RestTemplate() - * MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate); + * MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate).build(); * - * mockServer.expect(requestTo("/hotels/42")).andExpect(method(HttpMethod.GET)) + * server.expect(manyTimes(), requestTo("/hotels/42")).andExpect(method(HttpMethod.GET)) * .andRespond(withSuccess("{ \"id\" : \"42\", \"name\" : \"Holiday Inn\"}", MediaType.APPLICATION_JSON)); * * Hotel hotel = restTemplate.getForObject("/hotels/{id}", Hotel.class, 42); * // Use the hotel instance... * - * mockServer.verify(); + * // Verify all expectations met + * server.verify(); * </pre> * - * <p>To create an instance of this class, use {@link #createServer(RestTemplate)} - * and provide the {@code RestTemplate} to set up for the mock testing. - * - * <p>After that use {@link #expect(RequestMatcher)} and fluent API methods - * {@link ResponseActions#andExpect(RequestMatcher) andExpect(RequestMatcher)} and - * {@link ResponseActions#andRespond(ResponseCreator) andRespond(ResponseCreator)} - * to set up request expectations and responses, most likely relying on the default - * {@code RequestMatcher} implementations provided in {@link MockRestRequestMatchers} - * and the {@code ResponseCreator} implementations provided in - * {@link MockRestResponseCreators} both of which can be statically imported. - * - * <p>At the end of the test use {@link #verify()} to ensure all expected - * requests were actually performed. - * - * <p>Note that because of the fluent API offered by this class (and related - * classes), you can typically use the Code Completion features (i.e. - * ctrl-space) in your IDE to set up the mocks. - * - * <p><strong>Credits:</strong> The client-side REST testing support was - * inspired by and initially based on similar code in the Spring WS project for - * client-side tests involving the {@code WebServiceTemplate}. + * <p>Note that as an alternative to the above you can also set the + * {@link MockMvcClientHttpRequestFactory} on a {@code RestTemplate} which + * allows executing requests against an instance of + * {@link org.springframework.test.web.servlet.MockMvc MockMvc}. * * @author Craig Walls * @author Rossen Stoyanchev @@ -90,143 +67,230 @@ import org.springframework.web.client.support.RestGatewaySupport; */ public class MockRestServiceServer { - private final List<RequestMatcherClientHttpRequest> expectedRequests = - new LinkedList<RequestMatcherClientHttpRequest>(); + private final RequestExpectationManager expectationManager; + + + /** + * Private constructor with {@code RequestExpectationManager}. + * See static builder methods and {@code createServer} shortcut methods. + */ + private MockRestServiceServer(RequestExpectationManager expectationManager) { + this.expectationManager = expectationManager; + } + + + /** + * Set up an expectation for a single HTTP request. The returned + * {@link ResponseActions} can be used to set up further expectations as + * well as to define the response. + * <p>This method may be invoked any number times before starting to make + * request through the underlying {@code RestTemplate} in order to set up + * all expected requests. + * @param matcher request matcher + * @return a representation of the expectation + */ + public ResponseActions expect(RequestMatcher matcher) { + return expect(ExpectedCount.once(), matcher); + } + + /** + * An alternative to {@link #expect(RequestMatcher)} with an indication how + * many times the request is expected to be executed. + * <p>When request expectations have an expected count greater than one, only + * the first execution is expected to match the order of declaration. Subsequent + * request executions may be inserted anywhere thereafter. + * @param count the expected count + * @param matcher request matcher + * @return a representation of the expectation + * @since 4.3 + */ + public ResponseActions expect(ExpectedCount count, RequestMatcher matcher) { + return this.expectationManager.expectRequest(count, matcher); + } + + /** + * Verify that all expected requests set up via + * {@link #expect(RequestMatcher)} were indeed performed. + * @throws AssertionError when some expectations were not met + */ + public void verify() { + this.expectationManager.verify(); + } - private final List<RequestMatcherClientHttpRequest> actualRequests = - new LinkedList<RequestMatcherClientHttpRequest>(); + /** + * Reset the internal state removing all expectations and recorded requests. + */ + public void reset() { + this.expectationManager.reset(); + } + + + /** + * Return a builder for a {@code MockRestServiceServer} that should be used + * to reply to the given {@code RestTemplate}. + * @since 4.3 + */ + public static MockRestServiceServerBuilder bindTo(RestTemplate restTemplate) { + return new DefaultBuilder(restTemplate); + } + /** + * Return a builder for a {@code MockRestServiceServer} that should be used + * to reply to the given {@code AsyncRestTemplate}. + * @since 4.3 + */ + public static MockRestServiceServerBuilder bindTo(AsyncRestTemplate asyncRestTemplate) { + return new DefaultBuilder(asyncRestTemplate); + } /** - * Private constructor. - * @see #createServer(RestTemplate) - * @see #createServer(RestGatewaySupport) + * Return a builder for a {@code MockRestServiceServer} that should be used + * to reply to the given {@code RestGatewaySupport}. + * @since 4.3 */ - private MockRestServiceServer() { + public static MockRestServiceServerBuilder bindTo(RestGatewaySupport restGateway) { + Assert.notNull(restGateway, "'gatewaySupport' must not be null"); + return new DefaultBuilder(restGateway.getRestTemplate()); } /** - * Create a {@code MockRestServiceServer} and set up the given - * {@code RestTemplate} with a mock {@link ClientHttpRequestFactory}. + * A shortcut for {@code bindTo(restTemplate).build()}. * @param restTemplate the RestTemplate to set up for mock testing - * @return the created mock server + * @return the mock server */ public static MockRestServiceServer createServer(RestTemplate restTemplate) { - Assert.notNull(restTemplate, "'restTemplate' must not be null"); - MockRestServiceServer mockServer = new MockRestServiceServer(); - RequestMatcherClientHttpRequestFactory factory = mockServer.new RequestMatcherClientHttpRequestFactory(); - restTemplate.setRequestFactory(factory); - return mockServer; + return bindTo(restTemplate).build(); } /** - * Create a {@code MockRestServiceServer} and set up the given - * {@code AsyRestTemplate} with a mock {@link AsyncClientHttpRequestFactory}. + * A shortcut for {@code bindTo(asyncRestTemplate).build()}. * @param asyncRestTemplate the AsyncRestTemplate to set up for mock testing * @return the created mock server */ public static MockRestServiceServer createServer(AsyncRestTemplate asyncRestTemplate) { - Assert.notNull(asyncRestTemplate, "'asyncRestTemplate' must not be null"); - MockRestServiceServer mockServer = new MockRestServiceServer(); - RequestMatcherClientHttpRequestFactory factory = mockServer.new RequestMatcherClientHttpRequestFactory(); - asyncRestTemplate.setAsyncRequestFactory(factory); - return mockServer; + return bindTo(asyncRestTemplate).build(); } /** - * Create a {@code MockRestServiceServer} and set up the given - * {@code RestGatewaySupport} with a mock {@link ClientHttpRequestFactory}. + * A shortcut for {@code bindTo(restGateway).build()}. * @param restGateway the REST gateway to set up for mock testing * @return the created mock server */ public static MockRestServiceServer createServer(RestGatewaySupport restGateway) { - Assert.notNull(restGateway, "'gatewaySupport' must not be null"); - return createServer(restGateway.getRestTemplate()); + return bindTo(restGateway).build(); } /** - * Set up a new HTTP request expectation. The returned {@link ResponseActions} - * is used to set up further expectations and to define the response. - * <p>This method may be invoked multiple times before starting the test, i.e. before - * using the {@code RestTemplate}, to set up expectations for multiple requests. - * @param requestMatcher a request expectation, see {@link MockRestRequestMatchers} - * @return used to set up further expectations or to define a response + * Builder to create a {@code MockRestServiceServer}. */ - public ResponseActions expect(RequestMatcher requestMatcher) { - Assert.state(this.actualRequests.isEmpty(), "Can't add more expected requests with test already underway"); - RequestMatcherClientHttpRequest request = new RequestMatcherClientHttpRequest(requestMatcher); - this.expectedRequests.add(request); - return request; + public interface MockRestServiceServerBuilder { + + /** + * Whether to allow expected requests to be executed in any order not + * necessarily matching the order of declaration. + * <p>When set to "true" this is effectively a shortcut for:<br> + * {@code builder.build(new UnorderedRequestExpectationManager)}. + * @param ignoreExpectOrder whether to ignore the order of expectations + */ + MockRestServiceServerBuilder ignoreExpectOrder(boolean ignoreExpectOrder); + + /** + * Build the {@code MockRestServiceServer} and set up the underlying + * {@code RestTemplate} or {@code AsyncRestTemplate} with a + * {@link ClientHttpRequestFactory} that creates mock requests. + */ + MockRestServiceServer build(); + + /** + * An overloaded build alternative that accepts a custom + * {@link RequestExpectationManager}. + */ + MockRestServiceServer build(RequestExpectationManager manager); } - /** - * Verify that all expected requests set up via - * {@link #expect(RequestMatcher)} were indeed performed. - * @throws AssertionError when some expectations were not met - */ - public void verify() { - if (this.expectedRequests.isEmpty() || this.expectedRequests.equals(this.actualRequests)) { - return; + + private static class DefaultBuilder implements MockRestServiceServerBuilder { + + private final RestTemplate restTemplate; + + private final AsyncRestTemplate asyncRestTemplate; + + private boolean ignoreExpectOrder; + + public DefaultBuilder(RestTemplate restTemplate) { + Assert.notNull(restTemplate, "RestTemplate must not be null"); + this.restTemplate = restTemplate; + this.asyncRestTemplate = null; + } + + public DefaultBuilder(AsyncRestTemplate asyncRestTemplate) { + Assert.notNull(asyncRestTemplate, "AsyncRestTemplate must not be null"); + this.restTemplate = null; + this.asyncRestTemplate = asyncRestTemplate; + } + + @Override + public MockRestServiceServerBuilder ignoreExpectOrder(boolean ignoreExpectOrder) { + this.ignoreExpectOrder = ignoreExpectOrder; + return this; } - throw new AssertionError(getVerifyMessage()); - } - private String getVerifyMessage() { - StringBuilder sb = new StringBuilder("Further request(s) expected\n"); - if (this.actualRequests.size() > 0) { - sb.append("The following "); + @Override + public MockRestServiceServer build() { + if (this.ignoreExpectOrder) { + return build(new UnorderedRequestExpectationManager()); + } + else { + return build(new SimpleRequestExpectationManager()); + } } - sb.append(this.actualRequests.size()).append(" out of "); - sb.append(this.expectedRequests.size()).append(" were executed"); - if (this.actualRequests.size() > 0) { - sb.append(":\n"); - for (RequestMatcherClientHttpRequest request : this.actualRequests) { - sb.append(request.toString()).append("\n"); + @Override + public MockRestServiceServer build(RequestExpectationManager manager) { + MockRestServiceServer server = new MockRestServiceServer(manager); + MockClientHttpRequestFactory factory = server.new MockClientHttpRequestFactory(); + if (this.restTemplate != null) { + this.restTemplate.setRequestFactory(factory); + } + if (this.asyncRestTemplate != null) { + this.asyncRestTemplate.setAsyncRequestFactory(factory); } + return server; } - return sb.toString(); } /** * Mock ClientHttpRequestFactory that creates requests by iterating - * over the list of expected {@link RequestMatcherClientHttpRequest}'s. + * over the list of expected {@link DefaultRequestExpectation}'s. */ - private class RequestMatcherClientHttpRequestFactory - implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory { - - private Iterator<RequestMatcherClientHttpRequest> requestIterator; + private class MockClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory { @Override - public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) { return createRequestInternal(uri, httpMethod); } @Override - public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException { + public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) { return createRequestInternal(uri, httpMethod); } - private RequestMatcherClientHttpRequest createRequestInternal(URI uri, HttpMethod httpMethod) { + private MockAsyncClientHttpRequest createRequestInternal(URI uri, HttpMethod method) { Assert.notNull(uri, "'uri' must not be null"); - Assert.notNull(httpMethod, "'httpMethod' must not be null"); - - if (this.requestIterator == null) { - this.requestIterator = MockRestServiceServer.this.expectedRequests.iterator(); - } - if (!this.requestIterator.hasNext()) { - throw new AssertionError("No further requests expected: HTTP " + httpMethod + " " + uri); - } + Assert.notNull(method, "'httpMethod' must not be null"); - RequestMatcherClientHttpRequest request = this.requestIterator.next(); - request.setURI(uri); - request.setMethod(httpMethod); + return new MockAsyncClientHttpRequest(method, uri) { - MockRestServiceServer.this.actualRequests.add(request); - return request; + @Override + protected ClientHttpResponse executeInternal() throws IOException { + ClientHttpResponse response = expectationManager.validateRequest(this); + setResponse(response); + return response; + } + }; } } diff --git a/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectation.java b/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectation.java new file mode 100644 index 00000000..b2d8f144 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectation.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2016 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.web.client; + +/** + * An extension of {@code ResponseActions} that also implements + * {@code RequestMatcher} and {@code ResponseCreator} + * + * <p>While {@code ResponseActions} is the API for defining expectations this + * sub-interface is the internal SPI for matching these expectations to actual + * requests and for creating responses. + * + * @author Rossen Stoyanchev + * @since 4.3 + */ +public interface RequestExpectation extends ResponseActions, RequestMatcher, ResponseCreator { + + /** + * Whether there is a remaining count of invocations for this expectation. + */ + boolean hasRemainingCount(); + + /** + * Whether the requirements for this request expectation have been met. + */ + boolean isSatisfied(); + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectationManager.java b/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectationManager.java new file mode 100644 index 00000000..a79f9210 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/client/RequestExpectationManager.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2016 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.web.client; + +import java.io.IOException; + +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; + +/** + * Abstraction for creating HTTP request expectations, applying them to actual + * requests (in strict or random order), and verifying whether expectations + * have been met. + * + * @author Rossen Stoyanchev + * @since 4.3 + */ +public interface RequestExpectationManager { + + /** + * Set up a new request expectation. The returned {@link ResponseActions} is + * used to add more expectations and define a response. + * @param requestMatcher a request expectation + * @return for setting up further expectations and define a response + */ + ResponseActions expectRequest(ExpectedCount count, RequestMatcher requestMatcher); + + /** + * Validate the given actual request against the declared expectations. + * Is successful return the mock response to use or raise an error. + * @param request the request + * @return the response to return if the request was validated. + * @throws AssertionError when some expectations were not met + * @throws IOException + */ + ClientHttpResponse validateRequest(ClientHttpRequest request) throws IOException; + + /** + * Verify that all expectations have been met. + * @throws AssertionError when some expectations were not met + */ + void verify(); + + /** + * Reset the internal state removing all expectations and recorded requests. + */ + void reset(); + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcher.java b/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcher.java index bc169f48..14237729 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcher.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcher.java @@ -23,6 +23,9 @@ import org.springframework.http.client.ClientHttpRequest; /** * A contract for matching requests to expectations. * + * <p>See {@link org.springframework.test.web.client.match.MockRestRequestMatchers + * MockRestRequestMatchers} for static factory methods. + * * @author Craig Walls * @since 3.2 */ diff --git a/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcherClientHttpRequest.java b/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcherClientHttpRequest.java deleted file mode 100644 index 1a01328b..00000000 --- a/spring-test/src/main/java/org/springframework/test/web/client/RequestMatcherClientHttpRequest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2002-2015 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.web.client; - -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; - -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.mock.http.client.MockAsyncClientHttpRequest; -import org.springframework.util.Assert; - -/** - * A specialization of {@code MockClientHttpRequest} that matches the request - * against a set of expectations, via {@link RequestMatcher} instances. The - * expectations are checked when the request is executed. This class also uses a - * {@link ResponseCreator} to create the response. - * - * @author Craig Walls - * @author Rossen Stoyanchev - * @since 3.2 - */ -class RequestMatcherClientHttpRequest extends MockAsyncClientHttpRequest implements ResponseActions { - - private final List<RequestMatcher> requestMatchers = new LinkedList<RequestMatcher>(); - - private ResponseCreator responseCreator; - - - public RequestMatcherClientHttpRequest(RequestMatcher requestMatcher) { - Assert.notNull(requestMatcher, "RequestMatcher is required"); - this.requestMatchers.add(requestMatcher); - } - - - @Override - public ResponseActions andExpect(RequestMatcher requestMatcher) { - Assert.notNull(requestMatcher, "RequestMatcher is required"); - this.requestMatchers.add(requestMatcher); - return this; - } - - @Override - public void andRespond(ResponseCreator responseCreator) { - Assert.notNull(responseCreator, "ResponseCreator is required"); - this.responseCreator = responseCreator; - } - - @Override - public ClientHttpResponse executeInternal() throws IOException { - if (this.requestMatchers.isEmpty()) { - throw new AssertionError("No request expectations to execute"); - } - - if (this.responseCreator == null) { - throw new AssertionError("No ResponseCreator was set up. Add it after request expectations, " + - "e.g. MockRestServiceServer.expect(requestTo(\"/foo\")).andRespond(withSuccess())"); - } - - for (RequestMatcher requestMatcher : this.requestMatchers) { - requestMatcher.match(this); - } - setResponse(this.responseCreator.createResponse(this)); - return super.executeInternal(); - } - -} diff --git a/spring-test/src/main/java/org/springframework/test/web/client/SimpleRequestExpectationManager.java b/spring-test/src/main/java/org/springframework/test/web/client/SimpleRequestExpectationManager.java new file mode 100644 index 00000000..dbcf3852 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/client/SimpleRequestExpectationManager.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2016 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.web.client; + +import java.io.IOException; +import java.util.Iterator; + +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.Assert; + +/** + * Simple {@code RequestExpectationManager} that matches requests to expectations + * sequentially, i.e. in the order of declaration of expectations. + * + * <p>When request expectations have an expected count greater than one, + * only the first execution is expected to match the order of declaration. + * Subsequent request executions may be inserted anywhere thereafter. + * + * @author Rossen Stoyanchev + * @since 4.3 + */ +public class SimpleRequestExpectationManager extends AbstractRequestExpectationManager { + + private Iterator<RequestExpectation> expectationIterator; + + private final RequestExpectationGroup repeatExpectations = new RequestExpectationGroup(); + + + @Override + protected void afterExpectationsDeclared() { + Assert.state(this.expectationIterator == null); + this.expectationIterator = getExpectations().iterator(); + } + + @Override + public ClientHttpResponse validateRequestInternal(ClientHttpRequest request) throws IOException { + RequestExpectation expectation; + try { + expectation = next(request); + expectation.match(request); + } + catch (AssertionError error) { + expectation = this.repeatExpectations.findExpectation(request); + if (expectation == null) { + throw error; + } + } + ClientHttpResponse response = expectation.createResponse(request); + this.repeatExpectations.update(expectation); + return response; + } + + private RequestExpectation next(ClientHttpRequest request) { + if (this.expectationIterator.hasNext()) { + return this.expectationIterator.next(); + } + throw createUnexpectedRequestError(request); + } + + @Override + public void reset() { + super.reset(); + this.expectationIterator = null; + this.repeatExpectations.reset(); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/client/UnorderedRequestExpectationManager.java b/spring-test/src/main/java/org/springframework/test/web/client/UnorderedRequestExpectationManager.java new file mode 100644 index 00000000..42c1a6aa --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/client/UnorderedRequestExpectationManager.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2016 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.web.client; + +import java.io.IOException; + +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; + +/** + * {@code RequestExpectationManager} that matches requests to expectations + * regardless of the order of declaration of expected requests. + * + * @author Rossen Stoyanchev + * @since 4.3 + */ +public class UnorderedRequestExpectationManager extends AbstractRequestExpectationManager { + + private final RequestExpectationGroup remainingExpectations = new RequestExpectationGroup(); + + + @Override + protected void afterExpectationsDeclared() { + this.remainingExpectations.updateAll(getExpectations()); + } + + @Override + public ClientHttpResponse validateRequestInternal(ClientHttpRequest request) throws IOException { + RequestExpectation expectation = this.remainingExpectations.findExpectation(request); + if (expectation != null) { + ClientHttpResponse response = expectation.createResponse(request); + this.remainingExpectations.update(expectation); + return response; + } + throw createUnexpectedRequestError(request); + } + + @Override + public void reset() { + super.reset(); + this.remainingExpectations.reset(); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java index 4acbf82e..e32a68aa 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,24 @@ package org.springframework.test.web.client.match; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import org.hamcrest.Matcher; import org.w3c.dom.Node; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.mock.http.client.MockClientHttpRequest; import org.springframework.test.util.XmlExpectationsHelper; import org.springframework.test.web.client.RequestMatcher; +import org.springframework.util.MultiValueMap; import static org.hamcrest.MatcherAssert.*; import static org.springframework.test.util.AssertionErrors.*; @@ -137,13 +143,36 @@ public class ContentRequestMatchers { } /** + * Parse the body as form data and compare to the given {@code MultiValueMap}. + * @since 4.3 + */ + public RequestMatcher formData(final MultiValueMap<String, String> expectedContent) { + return new RequestMatcher() { + @Override + public void match(final ClientHttpRequest request) throws IOException, AssertionError { + HttpInputMessage inputMessage = new HttpInputMessage() { + @Override + public InputStream getBody() throws IOException { + MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; + return new ByteArrayInputStream(mockRequest.getBodyAsBytes()); + } + @Override + public HttpHeaders getHeaders() { + return request.getHeaders(); + } + }; + FormHttpMessageConverter converter = new FormHttpMessageConverter(); + assertEquals("Request content", expectedContent, converter.read(null, inputMessage)); + } + }; + } + + /** * Parse the request body and the given String as XML and assert that the * two are "similar" - i.e. they contain the same elements and attributes * regardless of order. - * * <p>Use of this matcher assumes the * <a href="http://xmlunit.sourceforge.net/">XMLUnit<a/> library is available. - * * @param expectedXmlContent the expected XML content */ public RequestMatcher xml(final String expectedXmlContent) { @@ -180,6 +209,7 @@ public class ContentRequestMatchers { }; } + /** * Abstract base class for XML {@link RequestMatcher}'s. */ @@ -191,12 +221,13 @@ public class ContentRequestMatchers { MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; matchInternal(mockRequest); } - catch (Exception e) { - throw new AssertionError("Failed to parse expected or actual XML request content: " + e.getMessage()); + catch (Exception ex) { + throw new AssertionError("Failed to parse expected or actual XML request content: " + ex.getMessage()); } } protected abstract void matchInternal(MockClientHttpRequest request) throws Exception; - } + } + diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java index 318e9d88..d9d78bb9 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/match/MockRestRequestMatchers.java @@ -123,7 +123,7 @@ public abstract class MockRestRequestMatchers { /** * Assert request header values with the given Hamcrest matcher. */ - @SuppressWarnings("unchecked") + @SafeVarargs public static RequestMatcher header(final String name, final Matcher<? super String>... matchers) { return new RequestMatcher() { @Override diff --git a/spring-test/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java b/spring-test/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java index 3f2be1ae..213bc5d7 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.test.web.client.response; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.net.URI; +import java.nio.charset.Charset; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; @@ -38,6 +39,9 @@ import org.springframework.util.Assert; */ public class DefaultResponseCreator implements ResponseCreator { + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + + private byte[] content; private Resource contentResource; @@ -56,6 +60,7 @@ public class DefaultResponseCreator implements ResponseCreator { this.statusCode = statusCode; } + @Override public ClientHttpResponse createResponse(ClientHttpRequest request) throws IOException { MockClientHttpResponse response; @@ -74,13 +79,7 @@ public class DefaultResponseCreator implements ResponseCreator { * Set the body as a UTF-8 String. */ public DefaultResponseCreator body(String content) { - try { - this.content = content.getBytes("UTF-8"); - } - catch (UnsupportedEncodingException e) { - // should not happen, UTF-8 is always supported - throw new IllegalStateException(e); - } + this.content = content.getBytes(UTF8_CHARSET); return this; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java index 3db5711d..1f64c74e 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java @@ -37,8 +37,7 @@ import org.springframework.util.Assert; * WebClient webClient = new WebClient(); * * MockMvc mockMvc = ... - * MockMvcWebConnection mockConnection = new MockMvcWebConnection(mockMvc); - * mockConnection.setWebClient(webClient); + * MockMvcWebConnection mockConnection = new MockMvcWebConnection(mockMvc, webClient); * * WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*"); * WebConnection httpConnection = new HttpWebConnection(webClient); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java index 497bf4ab..597ac8f4 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java @@ -106,7 +106,7 @@ public class MockMvcWebClientBuilder extends MockMvcWebConnectionBuilderSupport< */ public MockMvcWebClientBuilder withDelegate(WebClient webClient) { Assert.notNull(webClient, "WebClient must not be null"); - webClient.setWebConnection(createConnection(webClient.getWebConnection())); + webClient.setWebConnection(createConnection(webClient)); this.webClient = webClient; return this; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java index 2f19cea7..fd4ef702 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,16 @@ package org.springframework.test.web.servlet.htmlunit; import java.io.IOException; +import java.util.Date; import java.util.HashMap; import java.util.Map; +import com.gargoylesoftware.htmlunit.CookieManager; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.WebConnection; import com.gargoylesoftware.htmlunit.WebRequest; import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.util.Cookie; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; @@ -41,8 +44,7 @@ import org.springframework.util.Assert; * <pre class="code"> * WebClient webClient = new WebClient(); * MockMvc mockMvc = ... - * MockMvcWebConnection webConnection = new MockMvcWebConnection(mockMvc); - * mockConnection.setWebClient(webClient); + * MockMvcWebConnection webConnection = new MockMvcWebConnection(mockMvc, webClient); * webClient.setWebConnection(webConnection); * * // Use webClient as normal ... @@ -70,9 +72,10 @@ public final class MockMvcWebConnection implements WebConnection { * <p>For example, the URL {@code http://localhost/test/this} would use * {@code ""} as the context path. * @param mockMvc the {@code MockMvc} instance to use; never {@code null} + * @param webClient the {@link WebClient} to use. never {@code null} */ - public MockMvcWebConnection(MockMvc mockMvc) { - this(mockMvc, ""); + public MockMvcWebConnection(MockMvc mockMvc, WebClient webClient) { + this(mockMvc, webClient, ""); } /** @@ -83,17 +86,68 @@ public final class MockMvcWebConnection implements WebConnection { * which states that it can be an empty string and otherwise must start * with a "/" character and not end with a "/" character. * @param mockMvc the {@code MockMvc} instance to use; never {@code null} + * @param webClient the {@link WebClient} to use. never {@code null} * @param contextPath the contextPath to use */ - public MockMvcWebConnection(MockMvc mockMvc, String contextPath) { + public MockMvcWebConnection(MockMvc mockMvc, WebClient webClient, String contextPath) { Assert.notNull(mockMvc, "MockMvc must not be null"); + Assert.notNull(webClient, "WebClient must not be null"); validateContextPath(contextPath); - this.webClient = new WebClient(); + this.webClient = webClient; this.mockMvc = mockMvc; this.contextPath = contextPath; } + /** + * Create a new instance that assumes the context path of the application + * is {@code ""} (i.e., the root context). + * <p>For example, the URL {@code http://localhost/test/this} would use + * {@code ""} as the context path. + * @param mockMvc the {@code MockMvc} instance to use; never {@code null} + * @deprecated Use {@link #MockMvcWebConnection(MockMvc, WebClient)} + */ + @Deprecated + public MockMvcWebConnection(MockMvc mockMvc) { + this(mockMvc, ""); + } + + /** + * Create a new instance with the specified context path. + * <p>The path may be {@code null} in which case the first path segment + * of the URL is turned into the contextPath. Otherwise it must conform + * to {@link javax.servlet.http.HttpServletRequest#getContextPath()} + * which states that it can be an empty string and otherwise must start + * with a "/" character and not end with a "/" character. + * @param mockMvc the {@code MockMvc} instance to use; never {@code null} + * @param contextPath the contextPath to use + * @deprecated use {@link #MockMvcWebConnection(MockMvc, WebClient, String)} + */ + @Deprecated + public MockMvcWebConnection(MockMvc mockMvc, String contextPath) { + this(mockMvc, new WebClient(), contextPath); + } + + /** + * Validate the supplied {@code contextPath}. + * <p>If the value is not {@code null}, it must conform to + * {@link javax.servlet.http.HttpServletRequest#getContextPath()} which + * states that it can be an empty string and otherwise must start with + * a "/" character and not end with a "/" character. + * @param contextPath the path to validate + */ + static void validateContextPath(String contextPath) { + if (contextPath == null || "".equals(contextPath)) { + return; + } + if (!contextPath.startsWith("/")) { + throw new IllegalArgumentException("contextPath '" + contextPath + "' must start with '/'."); + } + if (contextPath.endsWith("/")) { + throw new IllegalArgumentException("contextPath '" + contextPath + "' must not end with '/'."); + } + } + public void setWebClient(WebClient webClient) { Assert.notNull(webClient, "WebClient must not be null"); @@ -113,6 +167,7 @@ public final class MockMvcWebConnection implements WebConnection { httpServletResponse = getResponse(requestBuilder); forwardedUrl = httpServletResponse.getForwardedUrl(); } + storeCookies(webRequest, httpServletResponse.getCookies()); return new MockWebResponseBuilder(startTime, webRequest, httpServletResponse).build(); } @@ -129,29 +184,29 @@ public final class MockMvcWebConnection implements WebConnection { return resultActions.andReturn().getResponse(); } - @Override - public void close() { - } - - - /** - * Validate the supplied {@code contextPath}. - * <p>If the value is not {@code null}, it must conform to - * {@link javax.servlet.http.HttpServletRequest#getContextPath()} which - * states that it can be an empty string and otherwise must start with - * a "/" character and not end with a "/" character. - * @param contextPath the path to validate - */ - static void validateContextPath(String contextPath) { - if (contextPath == null || "".equals(contextPath)) { + private void storeCookies(WebRequest webRequest, javax.servlet.http.Cookie[] cookies) { + if (cookies == null) { return; } - if (!contextPath.startsWith("/")) { - throw new IllegalArgumentException("contextPath '" + contextPath + "' must start with '/'."); - } - if (contextPath.endsWith("/")) { - throw new IllegalArgumentException("contextPath '" + contextPath + "' must not end with '/'."); + Date now = new Date(); + CookieManager cookieManager = this.webClient.getCookieManager(); + for (javax.servlet.http.Cookie cookie : cookies) { + if (cookie.getDomain() == null) { + cookie.setDomain(webRequest.getUrl().getHost()); + } + Cookie toManage = MockWebResponseBuilder.createCookie(cookie); + Date expires = toManage.getExpires(); + if (expires == null || expires.after(now)) { + cookieManager.addCookie(toManage); + } + else { + cookieManager.removeCookie(toManage); + } } } + @Override + public void close() { + } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java index cc957dca..c173fdfb 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,11 @@ package org.springframework.test.web.servlet.htmlunit; import java.util.ArrayList; import java.util.List; +import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.WebConnection; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection.DelegateWebConnection; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; import org.springframework.util.Assert; @@ -43,7 +45,7 @@ public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebCon private final MockMvc mockMvc; - private final List<WebRequestMatcher> mockMvcRequestMatchers = new ArrayList<WebRequestMatcher>(); + private final List<WebRequestMatcher> requestMatchers = new ArrayList<WebRequestMatcher>(); private String contextPath = ""; @@ -57,7 +59,7 @@ public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebCon protected MockMvcWebConnectionBuilderSupport(MockMvc mockMvc) { Assert.notNull(mockMvc, "MockMvc must not be null"); this.mockMvc = mockMvc; - this.mockMvcRequestMatchers.add(new HostRequestMatcher("localhost")); + this.requestMatchers.add(new HostRequestMatcher("localhost")); } /** @@ -116,7 +118,7 @@ public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebCon @SuppressWarnings("unchecked") public T useMockMvc(WebRequestMatcher... matchers) { for (WebRequestMatcher matcher : matchers) { - this.mockMvcRequestMatchers.add(matcher); + this.requestMatchers.add(matcher); } return (T) this; } @@ -130,7 +132,7 @@ public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebCon */ @SuppressWarnings("unchecked") public T useMockMvcForHosts(String... hosts) { - this.mockMvcRequestMatchers.add(new HostRequestMatcher(hosts)); + this.requestMatchers.add(new HostRequestMatcher(hosts)); return (T) this; } @@ -145,21 +147,41 @@ public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebCon * @see #alwaysUseMockMvc() * @see #useMockMvc(WebRequestMatcher...) * @see #useMockMvcForHosts(String...) + * @deprecated Use {@link #createConnection(WebClient)} instead */ + @Deprecated protected final WebConnection createConnection(WebConnection defaultConnection) { Assert.notNull(defaultConnection, "Default WebConnection must not be null"); - MockMvcWebConnection mockMvcWebConnection = new MockMvcWebConnection(this.mockMvc, this.contextPath); + return createConnection(new WebClient(), defaultConnection); + } + /** + * Create a new {@link WebConnection} that will use a {@link MockMvc} + * instance if one of the specified {@link WebRequestMatcher} instances + * matches. + * @param webClient the WebClient to use if none of the specified + * {@code WebRequestMatcher} instances matches (never {@code null}) + * @return a new {@code WebConnection} that will use a {@code MockMvc} + * instance if one of the specified {@code WebRequestMatcher} matches + * @see #alwaysUseMockMvc() + * @see #useMockMvc(WebRequestMatcher...) + * @see #useMockMvcForHosts(String...) + * @since 4.3 + */ + protected final WebConnection createConnection(WebClient webClient) { + Assert.notNull(webClient, "WebClient must not be null"); + return createConnection(webClient, webClient.getWebConnection()); + } + + private WebConnection createConnection(WebClient webClient, WebConnection defaultConnection) { + WebConnection connection = new MockMvcWebConnection(this.mockMvc, webClient, this.contextPath); if (this.alwaysUseMockMvc) { - return mockMvcWebConnection; + return connection; } - - List<DelegatingWebConnection.DelegateWebConnection> delegates = new ArrayList<DelegatingWebConnection.DelegateWebConnection>( - this.mockMvcRequestMatchers.size()); - for (WebRequestMatcher matcher : this.mockMvcRequestMatchers) { - delegates.add(new DelegatingWebConnection.DelegateWebConnection(matcher, mockMvcWebConnection)); + List<DelegateWebConnection> delegates = new ArrayList<DelegateWebConnection>(this.requestMatchers.size()); + for (WebRequestMatcher matcher : this.requestMatchers) { + delegates.add(new DelegateWebConnection(matcher, connection)); } - return new DelegatingWebConnection(defaultConnection, delegates); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java index 7ead8e13..fd716f47 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,17 @@ package org.springframework.test.web.servlet.htmlunit; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.List; +import javax.servlet.http.Cookie; + import com.gargoylesoftware.htmlunit.WebRequest; import com.gargoylesoftware.htmlunit.WebResponse; import com.gargoylesoftware.htmlunit.WebResponseData; import com.gargoylesoftware.htmlunit.util.NameValuePair; +import org.apache.http.impl.cookie.BasicClientCookie; import org.springframework.http.HttpStatus; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.util.Assert; @@ -34,6 +38,7 @@ import org.springframework.util.StringUtils; /** * @author Rob Winch * @author Sam Brannen + * @author Rossen Stoyanchev * @since 4.2 */ final class MockWebResponseBuilder { @@ -100,7 +105,30 @@ final class MockWebResponseBuilder { if (location != null) { responseHeaders.add(new NameValuePair("Location", location)); } + for (Cookie cookie : this.response.getCookies()) { + responseHeaders.add(new NameValuePair("Set-Cookie", valueOfCookie(cookie))); + } return responseHeaders; } + private String valueOfCookie(Cookie cookie) { + return createCookie(cookie).toString(); + } + + static com.gargoylesoftware.htmlunit.util.Cookie createCookie(Cookie cookie) { + Date expires = null; + if (cookie.getMaxAge() > -1) { + expires = new Date(System.currentTimeMillis() + cookie.getMaxAge() * 1000); + } + BasicClientCookie result = new BasicClientCookie(cookie.getName(), cookie.getValue()); + result.setDomain(cookie.getDomain()); + result.setComment(cookie.getComment()); + result.setExpiryDate(expires); + result.setPath(cookie.getPath()); + result.setSecure(cookie.getSecure()); + if(cookie.isHttpOnly()) { + result.setAttribute("httponly", "true"); + } + return new com.gargoylesoftware.htmlunit.util.Cookie(result); + } } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java index 9454c0a3..9a5ab8ac 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java @@ -129,7 +129,7 @@ public class MockMvcHtmlUnitDriverBuilder extends MockMvcWebConnectionBuilderSup public MockMvcHtmlUnitDriverBuilder withDelegate(WebConnectionHtmlUnitDriver driver) { Assert.notNull(driver, "HtmlUnitDriver must not be null"); driver.setJavascriptEnabled(this.javascriptEnabled); - driver.setWebConnection(createConnection(driver.getWebConnection())); + driver.setWebConnection(createConnection(driver.getWebClient())); this.driver = driver; return this; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java index bdb67b87..e8fc5f18 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java @@ -88,6 +88,14 @@ public class WebConnectionHtmlUnitDriver extends HtmlUnitDriver { } /** + * Return the current {@link WebClient}. + * @since 4.3 + */ + public WebClient getWebClient() { + return this.webClient; + } + + /** * Set the {@link WebConnection} to be used with the {@link WebClient}. * @param webConnection the {@code WebConnection} to use (never {@code null}) */ diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java index 1663bd2f..d181a46f 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,12 @@ package org.springframework.test.web.servlet.request; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URI; +import java.nio.charset.Charset; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; @@ -33,8 +37,10 @@ import javax.servlet.http.Cookie; import org.springframework.beans.Mergeable; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; @@ -60,16 +66,23 @@ import org.springframework.web.util.UriUtils; * * <p>Application tests will typically access this builder through the static factory * methods in {@link MockMvcRequestBuilders}. + * <p>Although this class cannot be extended, additional ways to initialize + * the {@code MockHttpServletRequest} can be plugged in via + * {@link #with(RequestPostProcessor)}. * * @author Rossen Stoyanchev * @author Arjen Poutsma * @author Sam Brannen + * @author Kamill Sokol * @since 3.2 */ public class MockHttpServletRequestBuilder implements ConfigurableSmartRequestBuilder<MockHttpServletRequestBuilder>, Mergeable { - private final HttpMethod method; + private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + + + private final String method; private final URI url; @@ -119,27 +132,33 @@ public class MockHttpServletRequestBuilder * @param vars zero or more URL variables */ MockHttpServletRequestBuilder(HttpMethod httpMethod, String url, Object... vars) { - this(httpMethod, UriComponentsBuilder.fromUriString(url).buildAndExpand(vars).encode().toUri()); + this(httpMethod.name(), UriComponentsBuilder.fromUriString(url).buildAndExpand(vars).encode().toUri()); } /** - * Package private constructor. To get an instance, use static factory - * methods in {@link MockMvcRequestBuilders}. - * <p>Although this class cannot be extended, additional ways to initialize - * the {@code MockHttpServletRequest} can be plugged in via - * {@link #with(RequestPostProcessor)}. + * Alternative to {@link #MockHttpServletRequestBuilder(HttpMethod, String, Object...)} + * with a pre-built URI. * @param httpMethod the HTTP method (GET, POST, etc) * @param url the URL * @since 4.0.3 */ MockHttpServletRequestBuilder(HttpMethod httpMethod, URI url) { - Assert.notNull(httpMethod, "httpMethod is required"); - Assert.notNull(url, "url is required"); + this(httpMethod.name(), url); + } + + /** + * Alternative constructor for custom HTTP methods. + * @param httpMethod the HTTP method (GET, POST, etc) + * @param url the URL + * @since 4.3 + */ + MockHttpServletRequestBuilder(String httpMethod, URI url) { + Assert.notNull(httpMethod, "'httpMethod' is required"); + Assert.notNull(url, "'url' is required"); this.method = httpMethod; this.url = url; } - /** * Add a request parameter to the {@link MockHttpServletRequest}. * <p>If called more than once, new values get added to existing ones. @@ -257,12 +276,7 @@ public class MockHttpServletRequestBuilder * @param content the body content */ public MockHttpServletRequestBuilder content(String content) { - try { - this.content = content.getBytes("UTF-8"); - } - catch (UnsupportedEncodingException e) { - // should never happen - } + this.content = content.getBytes(UTF8_CHARSET); return this; } @@ -320,7 +334,7 @@ public class MockHttpServletRequestBuilder * @param sessionAttributes the session attributes */ public MockHttpServletRequestBuilder sessionAttrs(Map<String, Object> sessionAttributes) { - Assert.notEmpty(sessionAttributes, "'sessionAttrs' must not be empty"); + Assert.notEmpty(sessionAttributes, "'sessionAttributes' must not be empty"); for (String name : sessionAttributes.keySet()) { sessionAttr(name, sessionAttributes.get(name)); } @@ -342,7 +356,7 @@ public class MockHttpServletRequestBuilder * @param flashAttributes the flash attributes */ public MockHttpServletRequestBuilder flashAttrs(Map<String, Object> flashAttributes) { - Assert.notEmpty(flashAttributes, "'flashAttrs' must not be empty"); + Assert.notEmpty(flashAttributes, "'flashAttributes' must not be empty"); for (String name : flashAttributes.keySet()) { flashAttr(name, flashAttributes.get(name)); } @@ -585,7 +599,7 @@ public class MockHttpServletRequestBuilder request.setServerPort(this.url.getPort()); } - request.setMethod(this.method.name()); + request.setMethod(this.method); for (String name : this.headers.keySet()) { for (Object value : this.headers.get(name)) { @@ -593,24 +607,10 @@ public class MockHttpServletRequestBuilder } } - try { - if (this.url.getRawQuery() != null) { - request.setQueryString(this.url.getRawQuery()); - } - - MultiValueMap<String, String> queryParams = - UriComponentsBuilder.fromUri(this.url).build().getQueryParams(); - - for (Entry<String, List<String>> entry : queryParams.entrySet()) { - for (String value : entry.getValue()) { - value = (value != null) ? UriUtils.decode(value, "UTF-8") : null; - request.addParameter(UriUtils.decode(entry.getKey(), "UTF-8"), value); - } - } - } - catch (UnsupportedEncodingException ex) { - // shouldn't happen + if (this.url.getRawQuery() != null) { + request.setQueryString(this.url.getRawQuery()); } + addRequestParams(request, UriComponentsBuilder.fromUri(this.url).build().getQueryParams()); for (String name : this.parameters.keySet()) { for (String value : this.parameters.get(name)) { @@ -622,6 +622,13 @@ public class MockHttpServletRequestBuilder request.setContent(this.content); request.setCharacterEncoding(this.characterEncoding); + if (this.content != null && this.contentType != null) { + MediaType mediaType = MediaType.parseMediaType(this.contentType); + if (MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)) { + addRequestParams(request, parseFormData(mediaType)); + } + } + if (!ObjectUtils.isEmpty(this.cookies)) { request.setCookies(this.cookies.toArray(new Cookie[this.cookies.size()])); } @@ -685,6 +692,44 @@ public class MockHttpServletRequestBuilder request.setPathInfo(this.pathInfo); } + private void addRequestParams(MockHttpServletRequest request, MultiValueMap<String, String> map) { + try { + for (Entry<String, List<String>> entry : map.entrySet()) { + for (String value : entry.getValue()) { + value = (value != null) ? UriUtils.decode(value, "UTF-8") : null; + request.addParameter(UriUtils.decode(entry.getKey(), "UTF-8"), value); + } + } + } + catch (UnsupportedEncodingException ex) { + // shouldn't happen + } + } + + private MultiValueMap<String, String> parseFormData(final MediaType mediaType) { + MultiValueMap<String, String> map; + HttpInputMessage message = new HttpInputMessage() { + @Override + public InputStream getBody() throws IOException { + return new ByteArrayInputStream(content); + } + + @Override + public HttpHeaders getHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(mediaType); + return headers; + } + }; + try { + map = new FormHttpMessageConverter().read(null, message); + } + catch (IOException ex) { + throw new IllegalStateException("Failed to parse form data in request body", ex); + } + return map; + } + private FlashMapManager getFlashMapManager(MockHttpServletRequest request) { FlashMapManager flashMapManager = null; try { diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java index 571699e6..f0e21adb 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import org.springframework.test.web.servlet.RequestBuilder; * @author Greg Turnquist * @author Sebastien Deleuze * @author Sam Brannen + * @author Kamill Sokol * @since 3.2 */ public abstract class MockMvcRequestBuilders { @@ -49,10 +50,10 @@ public abstract class MockMvcRequestBuilders { /** * Create a {@link MockHttpServletRequestBuilder} for a GET request. * @param urlTemplate a URL template; the resulting URL will be encoded - * @param urlVariables zero or more URL variables + * @param urlVars zero or more URL variables */ - public static MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables) { - return new MockHttpServletRequestBuilder(HttpMethod.GET, urlTemplate, urlVariables); + public static MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVars) { + return new MockHttpServletRequestBuilder(HttpMethod.GET, urlTemplate, urlVars); } /** @@ -67,10 +68,10 @@ public abstract class MockMvcRequestBuilders { /** * Create a {@link MockHttpServletRequestBuilder} for a POST request. * @param urlTemplate a URL template; the resulting URL will be encoded - * @param urlVariables zero or more URL variables + * @param urlVars zero or more URL variables */ - public static MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables) { - return new MockHttpServletRequestBuilder(HttpMethod.POST, urlTemplate, urlVariables); + public static MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVars) { + return new MockHttpServletRequestBuilder(HttpMethod.POST, urlTemplate, urlVars); } /** @@ -85,10 +86,10 @@ public abstract class MockMvcRequestBuilders { /** * Create a {@link MockHttpServletRequestBuilder} for a PUT request. * @param urlTemplate a URL template; the resulting URL will be encoded - * @param urlVariables zero or more URL variables + * @param urlVars zero or more URL variables */ - public static MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables) { - return new MockHttpServletRequestBuilder(HttpMethod.PUT, urlTemplate, urlVariables); + public static MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVars) { + return new MockHttpServletRequestBuilder(HttpMethod.PUT, urlTemplate, urlVars); } /** @@ -103,10 +104,10 @@ public abstract class MockMvcRequestBuilders { /** * Create a {@link MockHttpServletRequestBuilder} for a PATCH request. * @param urlTemplate a URL template; the resulting URL will be encoded - * @param urlVariables zero or more URL variables + * @param urlVars zero or more URL variables */ - public static MockHttpServletRequestBuilder patch(String urlTemplate, Object... urlVariables) { - return new MockHttpServletRequestBuilder(HttpMethod.PATCH, urlTemplate, urlVariables); + public static MockHttpServletRequestBuilder patch(String urlTemplate, Object... urlVars) { + return new MockHttpServletRequestBuilder(HttpMethod.PATCH, urlTemplate, urlVars); } /** @@ -121,10 +122,10 @@ public abstract class MockMvcRequestBuilders { /** * Create a {@link MockHttpServletRequestBuilder} for a DELETE request. * @param urlTemplate a URL template; the resulting URL will be encoded - * @param urlVariables zero or more URL variables + * @param urlVars zero or more URL variables */ - public static MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables) { - return new MockHttpServletRequestBuilder(HttpMethod.DELETE, urlTemplate, urlVariables); + public static MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVars) { + return new MockHttpServletRequestBuilder(HttpMethod.DELETE, urlTemplate, urlVars); } /** @@ -139,10 +140,10 @@ public abstract class MockMvcRequestBuilders { /** * Create a {@link MockHttpServletRequestBuilder} for an OPTIONS request. * @param urlTemplate a URL template; the resulting URL will be encoded - * @param urlVariables zero or more URL variables + * @param urlVars zero or more URL variables */ - public static MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVariables) { - return new MockHttpServletRequestBuilder(HttpMethod.OPTIONS, urlTemplate, urlVariables); + public static MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVars) { + return new MockHttpServletRequestBuilder(HttpMethod.OPTIONS, urlTemplate, urlVars); } /** @@ -157,11 +158,11 @@ public abstract class MockMvcRequestBuilders { /** * Create a {@link MockHttpServletRequestBuilder} for a HEAD request. * @param urlTemplate a URL template; the resulting URL will be encoded - * @param urlVariables zero or more URL variables + * @param urlVars zero or more URL variables * @since 4.1 */ - public static MockHttpServletRequestBuilder head(String urlTemplate, Object... urlVariables) { - return new MockHttpServletRequestBuilder(HttpMethod.HEAD, urlTemplate, urlVariables); + public static MockHttpServletRequestBuilder head(String urlTemplate, Object... urlVars) { + return new MockHttpServletRequestBuilder(HttpMethod.HEAD, urlTemplate, urlVars); } /** @@ -175,12 +176,12 @@ public abstract class MockMvcRequestBuilders { /** * Create a {@link MockHttpServletRequestBuilder} for a request with the given HTTP method. - * @param httpMethod the HTTP method + * @param method the HTTP method (GET, POST, etc) * @param urlTemplate a URL template; the resulting URL will be encoded - * @param urlVariables zero or more URL variables + * @param urlVars zero or more URL variables */ - public static MockHttpServletRequestBuilder request(HttpMethod httpMethod, String urlTemplate, Object... urlVariables) { - return new MockHttpServletRequestBuilder(httpMethod, urlTemplate, urlVariables); + public static MockHttpServletRequestBuilder request(HttpMethod method, String urlTemplate, Object... urlVars) { + return new MockHttpServletRequestBuilder(method, urlTemplate, urlVars); } /** @@ -194,12 +195,22 @@ public abstract class MockMvcRequestBuilders { } /** + * Alternative factory method that allows for custom HTTP verbs (e.g. WebDAV). + * @param httpMethod the HTTP method + * @param uri the URL + * @since 4.3 + */ + public static MockHttpServletRequestBuilder request(String httpMethod, URI uri) { + return new MockHttpServletRequestBuilder(httpMethod, uri); + } + + /** * Create a {@link MockMultipartHttpServletRequestBuilder} for a multipart request. * @param urlTemplate a URL template; the resulting URL will be encoded - * @param urlVariables zero or more URL variables + * @param urlVars zero or more URL variables */ - public static MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... urlVariables) { - return new MockMultipartHttpServletRequestBuilder(urlTemplate, urlVariables); + public static MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... urlVars) { + return new MockMultipartHttpServletRequestBuilder(urlTemplate, urlVars); } /** diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java index 982eacdf..211ca9a7 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HandlerResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,22 +24,33 @@ import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.util.ClassUtils; import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; +import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.MethodInvocationInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import static org.hamcrest.MatcherAssert.*; -import static org.springframework.test.util.AssertionErrors.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.springframework.test.util.AssertionErrors.assertEquals; +import static org.springframework.test.util.AssertionErrors.assertTrue; +import static org.springframework.test.util.AssertionErrors.fail; /** - * Factory for assertions on the selected handler. + * Factory for assertions on the selected handler or handler method. * <p>An instance of this class is typically accessed via * {@link MockMvcResultMatchers#handler}. * + * <p><strong>Note:</strong> Expectations that assert the controller method + * used to process the request work only for requests processed with + * {@link RequestMappingHandlerMapping} and {@link RequestMappingHandlerAdapter} + * which is used by default with the Spring MVC Java config and XML namespace. + * * @author Rossen Stoyanchev + * @author Sam Brannen * @since 3.2 */ public class HandlerResultMatchers { + /** * Protected constructor. * Use {@link MockMvcResultMatchers#handler()}. @@ -67,56 +78,92 @@ public class HandlerResultMatchers { } /** - * Assert the name of the controller method that processed the request with - * the given Hamcrest {@link Matcher}. - * <p>Use of this method implies annotated controllers are processed with - * {@link RequestMappingHandlerMapping} and {@link RequestMappingHandlerAdapter}. + * Assert the controller method used to process the request. + * <p>The expected method is specified through a "mock" controller method + * invocation similar to {@link MvcUriComponentsBuilder#fromMethodCall(Object)}. + * <p>For example, given this controller: + * <pre class="code"> + * @RestController + * public class SimpleController { + * + * @RequestMapping("/") + * public ResponseEntity<Void> handle() { + * return ResponseEntity.ok().build(); + * } + * } + * </pre> + * <p>A test that has statically imported {@link MvcUriComponentsBuilder#on} + * can be performed as follows: + * <pre class="code"> + * mockMvc.perform(get("/")) + * .andExpect(handler().methodCall(on(SimpleController.class).handle())); + * </pre> + * + * @param obj either the value returned from a "mock" controller invocation + * or the "mock" controller itself after an invocation + */ + public ResultMatcher methodCall(final Object obj) { + return new ResultMatcher() { + @Override + public void match(MvcResult result) throws Exception { + if (!MethodInvocationInfo.class.isInstance(obj)) { + fail(String.format("The supplied object [%s] is not an instance of %s. " + + "Ensure that you invoke the handler method via MvcUriComponentsBuilder.on().", + obj, MethodInvocationInfo.class.getName())); + } + MethodInvocationInfo invocationInfo = (MethodInvocationInfo) obj; + Method expected = invocationInfo.getControllerMethod(); + Method actual = getHandlerMethod(result).getMethod(); + assertEquals("Handler method", expected, actual); + } + }; + } + + /** + * Assert the name of the controller method used to process the request + * using the given Hamcrest {@link Matcher}. */ public ResultMatcher methodName(final Matcher<? super String> matcher) { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - Object handler = assertHandlerMethod(result); - assertThat("HandlerMethod", ((HandlerMethod) handler).getMethod().getName(), matcher); + HandlerMethod handlerMethod = getHandlerMethod(result); + assertThat("Handler method", handlerMethod.getMethod().getName(), matcher); } }; } /** - * Assert the name of the controller method that processed the request. - * <p>Use of this method implies annotated controllers are processed with - * {@link RequestMappingHandlerMapping} and {@link RequestMappingHandlerAdapter}. + * Assert the name of the controller method used to process the request. */ public ResultMatcher methodName(final String name) { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - Object handler = assertHandlerMethod(result); - assertEquals("HandlerMethod", name, ((HandlerMethod) handler).getMethod().getName()); + HandlerMethod handlerMethod = getHandlerMethod(result); + assertEquals("Handler method", name, handlerMethod.getMethod().getName()); } }; } /** - * Assert the controller method that processed the request. - * <p>Use of this method implies annotated controllers are processed with - * {@link RequestMappingHandlerMapping} and {@link RequestMappingHandlerAdapter}. + * Assert the controller method used to process the request. */ public ResultMatcher method(final Method method) { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - Object handler = assertHandlerMethod(result); - assertEquals("HandlerMethod", method, ((HandlerMethod) handler).getMethod()); + HandlerMethod handlerMethod = getHandlerMethod(result); + assertEquals("Handler method", method, handlerMethod.getMethod()); } }; } - private static Object assertHandlerMethod(MvcResult result) { + private static HandlerMethod getHandlerMethod(MvcResult result) { Object handler = result.getHandler(); assertTrue("No handler: ", handler != null); assertTrue("Not a HandlerMethod: " + handler, HandlerMethod.class.isInstance(handler)); - return handler; + return (HandlerMethod) handler; } } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java index c57387ee..aac0bab9 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/HeaderResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,22 +16,26 @@ package org.springframework.test.web.servlet.result; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; + import org.hamcrest.Matcher; +import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; -import static org.hamcrest.MatcherAssert.*; -import static org.springframework.test.util.AssertionErrors.*; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.springframework.test.util.AssertionErrors.assertEquals; +import static org.springframework.test.util.AssertionErrors.assertTrue; /** * Factory for response header assertions. - * <p>An instance of this class is usually accessed via + * <p>An instance of this class is available via * {@link MockMvcResultMatchers#header}. * * @author Rossen Stoyanchev @@ -41,16 +45,18 @@ import java.util.TimeZone; */ public class HeaderResultMatchers { + /** * Protected constructor. - * Use {@link MockMvcResultMatchers#header()}. + * See {@link MockMvcResultMatchers#header()}. */ protected HeaderResultMatchers() { } + /** - * Assert the primary value of the named response header with the given - * Hamcrest {@link Matcher}. + * Assert the primary value of the response header with the given Hamcrest + * String {@code Matcher}. */ public ResultMatcher string(final String name, final Matcher<? super String> matcher) { return new ResultMatcher() { @@ -62,7 +68,22 @@ public class HeaderResultMatchers { } /** - * Assert the primary value of the named response header as a {@link String}. + * Assert the values of the response header with the given Hamcrest + * Iterable {@link Matcher}. + * @since 4.3 + */ + public <T> ResultMatcher stringValues(final String name, final Matcher<Iterable<String>> matcher) { + return new ResultMatcher() { + @Override + public void match(MvcResult result) { + List<String> values = result.getResponse().getHeaders(name); + assertThat("Response header " + name, values, matcher); + } + }; + } + + /** + * Assert the primary value of the response header as a String value. */ public ResultMatcher string(final String name, final String value) { return new ResultMatcher() { @@ -74,6 +95,20 @@ public class HeaderResultMatchers { } /** + * Assert the values of the response header as String values. + * @since 4.3 + */ + public ResultMatcher stringValues(final String name, final String... values) { + return new ResultMatcher() { + @Override + public void match(MvcResult result) { + List<Object> actual = result.getResponse().getHeaderValues(name); + assertEquals("Response header " + name, Arrays.asList(values), actual); + } + }; + } + + /** * Assert that the named response header does not exist. * @since 4.0 */ @@ -81,23 +116,25 @@ public class HeaderResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) { - assertTrue("Response should not contain header " + name, !result.getResponse().containsHeader(name)); + assertTrue("Response should not contain header " + name, + !result.getResponse().containsHeader(name)); } }; } /** * Assert the primary value of the named response header as a {@code long}. - * <p>The {@link ResultMatcher} returned by this method throws an {@link AssertionError} - * if the response does not contain the specified header, or if the supplied - * {@code value} does not match the primary value. + * <p>The {@link ResultMatcher} returned by this method throws an + * {@link AssertionError} if the response does not contain the specified + * header, or if the supplied {@code value} does not match the primary value. */ public ResultMatcher longValue(final String name, final long value) { return new ResultMatcher() { @Override public void match(MvcResult result) { - assertTrue("Response does not contain header " + name, result.getResponse().containsHeader(name)); - assertEquals("Response header " + name, value, Long.parseLong(result.getResponse().getHeader(name))); + MockHttpServletResponse response = result.getResponse(); + assertTrue("Response does not contain header " + name, response.containsHeader(name)); + assertEquals("Response header " + name, value, Long.parseLong(response.getHeader(name))); } }; } @@ -105,10 +142,9 @@ public class HeaderResultMatchers { /** * Assert the primary value of the named response header as a date String, * using the preferred date format described in RFC 7231. - * <p>The {@link ResultMatcher} returned by this method throws an {@link AssertionError} - * if the response does not contain the specified header, or if the supplied - * {@code value} does not match the primary value. - * + * <p>The {@link ResultMatcher} returned by this method throws an + * {@link AssertionError} if the response does not contain the specified + * header, or if the supplied {@code value} does not match the primary value. * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.1">Section 7.1.1.1 of RFC 7231</a> * @since 4.2 */ @@ -118,8 +154,10 @@ public class HeaderResultMatchers { public void match(MvcResult result) { SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); format.setTimeZone(TimeZone.getTimeZone("GMT")); - assertTrue("Response does not contain header " + name, result.getResponse().containsHeader(name)); - assertEquals("Response header " + name, format.format(new Date(value)), result.getResponse().getHeader(name)); + String formatted = format.format(new Date(value)); + MockHttpServletResponse response = result.getResponse(); + assertTrue("Response does not contain header " + name, response.containsHeader(name)); + assertEquals("Response header " + name, formatted, response.getHeader(name)); } }; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java index 39377075..f572a372 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java @@ -16,12 +16,17 @@ package org.springframework.test.web.servlet.result; +import java.io.UnsupportedEncodingException; + import com.jayway.jsonpath.JsonPath; import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.core.StringStartsWith; import org.springframework.test.util.JsonPathExpectationsHelper; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.util.StringUtils; /** * Factory for assertions on the response content using @@ -33,12 +38,15 @@ import org.springframework.test.web.servlet.ResultMatcher; * @author Rossen Stoyanchev * @author Craig Andrews * @author Sam Brannen + * @author Brian Clozel * @since 3.2 */ public class JsonPathResultMatchers { private final JsonPathExpectationsHelper jsonPathHelper; + private String prefix; + /** * Protected constructor. @@ -52,6 +60,19 @@ public class JsonPathResultMatchers { this.jsonPathHelper = new JsonPathExpectationsHelper(expression, args); } + /** + * Configures the current {@code JsonPathResultMatchers} instance + * to verify that the JSON payload is prepended with the given prefix. + * <p>Use this method if the JSON payloads are prefixed to avoid + * Cross Site Script Inclusion (XSSI) attacks. + * @param prefix the string prefix prepended to the actual JSON payload + * @since 4.3 + */ + public JsonPathResultMatchers prefix(String prefix) { + this.prefix = prefix; + return this; + } + /** * Evaluate the JSON path expression against the response content and @@ -61,7 +82,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValue(content, matcher); } }; @@ -75,7 +96,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - jsonPathHelper.assertValue(result.getResponse().getContentAsString(), expectedValue); + jsonPathHelper.assertValue(getContent(result), expectedValue); } }; } @@ -91,7 +112,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.exists(content); } }; @@ -108,7 +129,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.doesNotExist(content); } }; @@ -128,7 +149,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsEmpty(content); } }; @@ -148,7 +169,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsNotEmpty(content); } }; @@ -163,7 +184,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsString(content); } }; @@ -178,7 +199,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsBoolean(content); } }; @@ -193,7 +214,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsNumber(content); } }; @@ -207,7 +228,7 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsArray(content); } }; @@ -222,10 +243,29 @@ public class JsonPathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); + String content = getContent(result); jsonPathHelper.assertValueIsMap(content); } }; } + private String getContent(MvcResult result) throws UnsupportedEncodingException { + String content = result.getResponse().getContentAsString(); + if (StringUtils.hasLength(this.prefix)) { + try { + String reason = String.format("Expected a JSON payload prefixed with \"%s\" but found: %s", + this.prefix, StringUtils.quote(content.substring(0, this.prefix.length()))); + MatcherAssert.assertThat(reason, content, StringStartsWith.startsWith(this.prefix)); + return content.substring(this.prefix.length()); + } + catch (StringIndexOutOfBoundsException oobe) { + throw new AssertionError( + "JSON prefix \"" + this.prefix + "\" not found, exception: " + oobe.getMessage()); + } + } + else { + return content; + } + } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java index 32ad49fd..a12baf94 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/MockMvcResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java index 5efa9100..f2eba236 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/StatusResultMatchers.java @@ -33,6 +33,7 @@ import static org.springframework.test.util.AssertionErrors.*; * @author Keesun Baik * @author Rossen Stoyanchev * @author Sebastien Deleuze + * @author Brian Clozel * @since 3.2 */ public class StatusResultMatchers { @@ -562,6 +563,14 @@ public class StatusResultMatchers { } /** + * Assert the response status code is {@code HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS} (451). + * @since 4.3 + */ + public ResultMatcher isUnavailableForLegalReasons() { + return matcher(HttpStatus.valueOf(451)); + } + + /** * Assert the response status code is {@code HttpStatus.INTERNAL_SERVER_ERROR} (500). */ public ResultMatcher isInternalServerError() { diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java index 21dd9ebb..50c4db84 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/setup/AbstractMockMvcBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,7 +55,7 @@ public abstract class AbstractMockMvcBuilder<B extends AbstractMockMvcBuilder<B> private final List<ResultHandler> globalResultHandlers = new ArrayList<ResultHandler>(); - private Boolean dispatchOptions = Boolean.FALSE; + private Boolean dispatchOptions = Boolean.TRUE; private final List<MockMvcConfigurer> configurers = new ArrayList<MockMvcConfigurer>(4); diff --git a/spring-test/src/main/resources/META-INF/spring.factories b/spring-test/src/main/resources/META-INF/spring.factories index 012b38df..30cd85a1 100644 --- a/spring-test/src/main/resources/META-INF/spring.factories +++ b/spring-test/src/main/resources/META-INF/spring.factories @@ -7,3 +7,8 @@ org.springframework.test.context.TestExecutionListener = \ org.springframework.test.context.support.DirtiesContextTestExecutionListener,\ org.springframework.test.context.transaction.TransactionalTestExecutionListener,\ org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener + +# Default ContextCustomizerFactory implementations for the Spring TestContext Framework +# +org.springframework.test.context.ContextCustomizerFactory = \ + org.springframework.test.context.web.socket.MockServerContainerContextCustomizerFactory diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockFilterChainTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockFilterChainTests.java index bbafd498..d008751f 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockFilterChainTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockFilterChainTests.java @@ -83,7 +83,7 @@ public class MockFilterChainTests { chain.doFilter(this.request, this.response); fail("Expected Exception"); } - catch(IllegalStateException ex) { + catch (IllegalStateException ex) { assertEquals("This FilterChain has already been called!", ex.getMessage()); } } @@ -98,7 +98,7 @@ public class MockFilterChainTests { chain.doFilter(this.request, this.response); fail("Expected Exception"); } - catch(IllegalStateException ex) { + catch (IllegalStateException ex) { assertEquals("This FilterChain has already been called!", ex.getMessage()); } } @@ -122,7 +122,7 @@ public class MockFilterChainTests { chain.doFilter(this.request, this.response); fail("Expected Exception"); } - catch(IllegalStateException ex) { + catch (IllegalStateException ex) { assertEquals("This FilterChain has already been called!", ex.getMessage()); } } diff --git a/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueUtilsTests.java b/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueUtilsTests.java index 4296fd07..da461bee 100644 --- a/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -91,7 +91,10 @@ public class ProfileValueUtilsTests { assertClassIsEnabled(EnabledAnnotatedMultiValue.class); assertClassIsEnabled(MetaEnabledClass.class); assertClassIsEnabled(MetaEnabledWithCustomProfileValueSourceClass.class); + assertClassIsEnabled(EnabledWithCustomProfileValueSourceOnTestInterface.class); + assertClassIsDisabled(DisabledAnnotatedSingleValue.class); + assertClassIsDisabled(DisabledAnnotatedSingleValueOnTestInterface.class); assertClassIsDisabled(DisabledAnnotatedMultiValue.class); assertClassIsDisabled(MetaDisabledClass.class); assertClassIsDisabled(MetaDisabledWithCustomProfileValueSourceClass.class); @@ -105,6 +108,7 @@ public class ProfileValueUtilsTests { assertMethodIsEnabled(ENABLED_ANNOTATED_METHOD, EnabledAnnotatedSingleValue.class); assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, EnabledAnnotatedSingleValue.class); + assertMethodIsEnabled(NON_ANNOTATED_METHOD, MetaEnabledAnnotatedSingleValue.class); assertMethodIsEnabled(ENABLED_ANNOTATED_METHOD, MetaEnabledAnnotatedSingleValue.class); assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, MetaEnabledAnnotatedSingleValue.class); @@ -117,6 +121,8 @@ public class ProfileValueUtilsTests { assertMethodIsDisabled(ENABLED_ANNOTATED_METHOD, DisabledAnnotatedSingleValue.class); assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, DisabledAnnotatedSingleValue.class); + assertMethodIsDisabled(NON_ANNOTATED_METHOD, DisabledAnnotatedSingleValueOnTestInterface.class); + assertMethodIsDisabled(NON_ANNOTATED_METHOD, MetaDisabledAnnotatedSingleValue.class); assertMethodIsDisabled(ENABLED_ANNOTATED_METHOD, MetaDisabledAnnotatedSingleValue.class); assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, MetaDisabledAnnotatedSingleValue.class); @@ -176,6 +182,17 @@ public class ProfileValueUtilsTests { } } + @IfProfileValue(name = NAME, value = VALUE + "X") + private interface IfProfileValueTestInterface { + } + + @SuppressWarnings("unused") + private static class DisabledAnnotatedSingleValueOnTestInterface implements IfProfileValueTestInterface { + + public void nonAnnotatedMethod() { + } + } + @SuppressWarnings("unused") @IfProfileValue(name = NAME, values = { "foo", VALUE, "bar" }) private static class EnabledAnnotatedMultiValue { @@ -302,4 +319,13 @@ public class ProfileValueUtilsTests { private static class MetaDisabledWithCustomProfileValueSourceClass { } + @ProfileValueSourceConfiguration(HardCodedProfileValueSource.class) + private interface CustomProfileValueSourceTestInterface { + } + + @IfProfileValue(name = NAME, value = "42") + private static class EnabledWithCustomProfileValueSourceOnTestInterface + implements CustomProfileValueSourceTestInterface { + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java index 8efb56d3..70d80ee3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/BootstrapUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,16 +24,20 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.test.context.support.DefaultTestContextBootstrapper; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.context.web.WebTestContextBootstrapper; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; -import static org.springframework.test.context.BootstrapUtils.*; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.springframework.test.context.BootstrapUtils.resolveTestContextBootstrapper; /** * Unit tests for {@link BootstrapUtils}. * * @author Sam Brannen + * @author Phillip Webb * @since 4.2 */ public class BootstrapUtilsTests { @@ -41,7 +45,7 @@ public class BootstrapUtilsTests { private final CacheAwareContextLoaderDelegate delegate = mock(CacheAwareContextLoaderDelegate.class); @Rule - public ExpectedException exception = ExpectedException.none(); + public final ExpectedException exception = ExpectedException.none(); @Test public void resolveTestContextBootstrapperForNonAnnotatedClass() { @@ -49,6 +53,11 @@ public class BootstrapUtilsTests { } @Test + public void resolveTestContextBootstrapperForWebAppConfigurationAnnotatedClass() { + assertBootstrapper(WebAppConfigurationAnnotatedClass.class, WebTestContextBootstrapper.class); + } + + @Test public void resolveTestContextBootstrapperWithEmptyBootstrapWithAnnotation() { BootstrapContext bootstrapContext = BootstrapTestUtils.buildBootstrapContext(EmptyBootstrapWithAnnotationClass.class, delegate); @@ -124,4 +133,7 @@ public class BootstrapUtilsTests { @BootWithFoo static class DoubleMetaAnnotatedBootstrapWithAnnotationClass {} + @WebAppConfiguration + static class WebAppConfigurationAnnotatedClass {} + } diff --git a/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java index bf8eba04..0409e11f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.test.context; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -28,6 +29,7 @@ import org.springframework.test.context.support.AnnotationConfigContextLoader; import org.springframework.test.context.support.GenericXmlContextLoader; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; /** * Unit tests for {@link MergedContextConfiguration}. @@ -37,6 +39,7 @@ import static org.junit.Assert.*; * {@link org.springframework.test.context.cache.ContextCache ContextCache}. * * @author Sam Brannen + * @author Phillip Webb * @since 3.1 */ public class MergedContextConfigurationTests { @@ -401,6 +404,35 @@ public class MergedContextConfigurationTests { } /** + * @since 4.3 + */ + @Test + public void equalsWithSameContextCustomizers() { + Set<ContextCustomizer> customizers = Collections.singleton(mock(ContextCustomizer.class)); + MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, + EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers, loader, null, null); + MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, + EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers, loader, null, null); + assertEquals(mergedConfig1, mergedConfig2); + } + + /** + * @since 4.3 + */ + @Test + public void equalsWithDifferentContextCustomizers() { + Set<ContextCustomizer> customizers1 = Collections.singleton(mock(ContextCustomizer.class)); + Set<ContextCustomizer> customizers2 = Collections.singleton(mock(ContextCustomizer.class)); + + MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, + EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers1, loader, null, null); + MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, + EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, null, null, customizers2, loader, null, null); + assertNotEquals(mergedConfig1, mergedConfig2); + assertNotEquals(mergedConfig2, mergedConfig1); + } + + /** * @since 3.2.2 */ @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java b/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java index 648655fb..4df53d82 100644 --- a/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java @@ -116,6 +116,12 @@ public class TestExecutionListenersTests { } @Test + public void customListenersDeclaredOnInterface() { + assertRegisteredListeners(ExplicitListenersOnTestInterfaceTestCase.class, + asList(FooTestExecutionListener.class, BarTestExecutionListener.class)); + } + + @Test public void nonInheritedListeners() { assertNumRegisteredListeners(NonInheritedListenersTestCase.class, 1); } @@ -229,6 +235,13 @@ public class TestExecutionListenersTests { static class NonInheritedListenersTestCase extends InheritedListenersTestCase { } + @TestExecutionListeners({ FooTestExecutionListener.class, BarTestExecutionListener.class }) + interface ExplicitListenersTestInterface { + } + + static class ExplicitListenersOnTestInterfaceTestCase implements ExplicitListenersTestInterface { + } + @TestExecutionListeners(listeners = FooTestExecutionListener.class, value = BarTestExecutionListener.class) static class DuplicateListenersConfigTestCase { } diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java index 2fcbdb46..1c603e33 100644 --- a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTestNGTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -171,7 +171,7 @@ public class ClassLevelDirtiesContextTestNGTests { @TestExecutionListeners(listeners = { DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class }, inheritListeners = false) @ContextConfiguration - public static abstract class BaseTestCase extends AbstractTestNGSpringContextTests { + static abstract class BaseTestCase extends AbstractTestNGSpringContextTests { @Configuration static class Config { @@ -189,75 +189,75 @@ public class ClassLevelDirtiesContextTestNGTests { } } - public static final class CleanTestCase extends BaseTestCase { + static final class CleanTestCase extends BaseTestCase { @org.testng.annotations.Test - public void verifyContextWasAutowired() { + void verifyContextWasAutowired() { assertApplicationContextWasAutowired(); } } @DirtiesContext - public static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase { + static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase { @org.testng.annotations.Test - public void verifyContextWasAutowired() { + void verifyContextWasAutowired() { assertApplicationContextWasAutowired(); } } - public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends + static class InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase { } @DirtiesContext(classMode = ClassMode.AFTER_CLASS) - public static class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase { + static class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase { @org.testng.annotations.Test - public void verifyContextWasAutowired() { + void verifyContextWasAutowired() { assertApplicationContextWasAutowired(); } } - public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends + static class InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase { } @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) - public static class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase { + static class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase { @org.testng.annotations.Test - public void verifyContextWasAutowired1() { + void verifyContextWasAutowired1() { assertApplicationContextWasAutowired(); } @org.testng.annotations.Test - public void verifyContextWasAutowired2() { + void verifyContextWasAutowired2() { assertApplicationContextWasAutowired(); } @org.testng.annotations.Test - public void verifyContextWasAutowired3() { + void verifyContextWasAutowired3() { assertApplicationContextWasAutowired(); } } - public static class InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends + static class InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase { } @DirtiesContext - public static class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase { + static class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase { @org.testng.annotations.Test @DirtiesContext - public void dirtyContext() { + void dirtyContext() { assertApplicationContextWasAutowired(); } } - public static class InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase extends + static class InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase extends ClassLevelDirtiesContextWithDirtyMethodsTestCase { } diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java index 896c4654..9da13e7b 100644 --- a/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/cache/ClassLevelDirtiesContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,10 +30,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.support.DirtiesContextTestExecutionListener; +import org.springframework.test.context.junit4.SpringRunner; import static org.junit.Assert.*; import static org.springframework.test.context.cache.ContextCacheTestUtils.*; @@ -41,9 +38,8 @@ import static org.springframework.test.context.junit4.JUnitTestingUtils.*; /** * JUnit 4 based integration test which verifies correct {@linkplain ContextCache - * application context caching} in conjunction with the - * {@link SpringJUnit4ClassRunner} and {@link DirtiesContext @DirtiesContext} - * at the class level. + * application context caching} in conjunction with the {@link SpringRunner} and + * {@link DirtiesContext @DirtiesContext} at the class level. * * @author Sam Brannen * @since 3.0 @@ -148,10 +144,9 @@ public class ClassLevelDirtiesContextTests { // ------------------------------------------------------------------- - @RunWith(SpringJUnit4ClassRunner.class) - @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class }) + @RunWith(SpringRunner.class) @ContextConfiguration - public static abstract class BaseTestCase { + static abstract class BaseTestCase { @Configuration static class Config { diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java index 5d518d3c..4624fcb7 100644 --- a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import static org.springframework.test.context.cache.ContextCacheTestUtils.*; * @author Sam Brannen * @author Michail Nikolaev * @since 3.1 + * @see LruContextCacheTests * @see SpringRunnerContextCacheTests */ public class ContextCacheTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java new file mode 100644 index 00000000..ac6cc723 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheUtilsTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2016 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.cache; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.core.SpringProperties; + +import static org.junit.Assert.*; +import static org.springframework.test.context.cache.ContextCacheUtils.*; +import static org.springframework.test.context.cache.ContextCache.*; + +/** + * Unit tests for {@link ContextCacheUtils}. + * + * @author Sam Brannen + * @since 4.3 + */ +public class ContextCacheUtilsTests { + + @Before + @After + public void clearProperties() { + System.clearProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME); + SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, null); + } + + @Test + public void retrieveMaxCacheSizeFromDefault() { + assertDefaultValue(); + } + + @Test + public void retrieveMaxCacheSizeFromBogusSystemProperty() { + System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "bogus"); + assertDefaultValue(); + } + + @Test + public void retrieveMaxCacheSizeFromBogusSpringProperty() { + SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "bogus"); + assertDefaultValue(); + } + + @Test + public void retrieveMaxCacheSizeFromDecimalSpringProperty() { + SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "3.14"); + assertDefaultValue(); + } + + @Test + public void retrieveMaxCacheSizeFromSystemProperty() { + System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "42"); + assertEquals(42, retrieveMaxCacheSize()); + } + + @Test + public void retrieveMaxCacheSizeFromSystemPropertyContainingWhitespace() { + System.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "42\t"); + assertEquals(42, retrieveMaxCacheSize()); + } + + @Test + public void retrieveMaxCacheSizeFromSpringProperty() { + SpringProperties.setProperty(MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME, "99"); + assertEquals(99, retrieveMaxCacheSize()); + } + + private static void assertDefaultValue() { + assertEquals(DEFAULT_MAX_CONTEXT_CACHE_SIZE, retrieveMaxCacheSize()); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java new file mode 100644 index 00000000..4ae97b61 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/cache/LruContextCacheTests.java @@ -0,0 +1,177 @@ +/* + * Copyright 2002-2016 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.cache; + +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.util.ReflectionTestUtils; + +import static java.util.Arrays.*; +import static java.util.stream.Collectors.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests for the LRU eviction policy in {@link DefaultContextCache}. + * + * @author Sam Brannen + * @since 4.3 + * @see ContextCacheTests + */ +public class LruContextCacheTests { + + private static final MergedContextConfiguration abcConfig = config(Abc.class); + private static final MergedContextConfiguration fooConfig = config(Foo.class); + private static final MergedContextConfiguration barConfig = config(Bar.class); + private static final MergedContextConfiguration bazConfig = config(Baz.class); + + + private final ConfigurableApplicationContext abcContext = mock(ConfigurableApplicationContext.class); + private final ConfigurableApplicationContext fooContext = mock(ConfigurableApplicationContext.class); + private final ConfigurableApplicationContext barContext = mock(ConfigurableApplicationContext.class); + private final ConfigurableApplicationContext bazContext = mock(ConfigurableApplicationContext.class); + + + @Test(expected = IllegalArgumentException.class) + public void maxCacheSizeNegativeOne() { + new DefaultContextCache(-1); + } + + @Test(expected = IllegalArgumentException.class) + public void maxCacheSizeZero() { + new DefaultContextCache(0); + } + + @Test + public void maxCacheSizeOne() { + DefaultContextCache cache = new DefaultContextCache(1); + assertEquals(0, cache.size()); + assertEquals(1, cache.getMaxSize()); + + cache.put(fooConfig, fooContext); + assertCacheContents(cache, "Foo"); + + cache.put(fooConfig, fooContext); + assertCacheContents(cache, "Foo"); + + cache.put(barConfig, barContext); + assertCacheContents(cache, "Bar"); + + cache.put(fooConfig, fooContext); + assertCacheContents(cache, "Foo"); + } + + @Test + public void maxCacheSizeThree() { + DefaultContextCache cache = new DefaultContextCache(3); + assertEquals(0, cache.size()); + assertEquals(3, cache.getMaxSize()); + + cache.put(fooConfig, fooContext); + assertCacheContents(cache, "Foo"); + + cache.put(fooConfig, fooContext); + assertCacheContents(cache, "Foo"); + + cache.put(barConfig, barContext); + assertCacheContents(cache, "Foo", "Bar"); + + cache.put(bazConfig, bazContext); + assertCacheContents(cache, "Foo", "Bar", "Baz"); + + cache.put(abcConfig, abcContext); + assertCacheContents(cache, "Bar", "Baz", "Abc"); + } + + @Test + public void ensureLruOrderingIsUpdated() { + DefaultContextCache cache = new DefaultContextCache(3); + + // Note: when a new entry is added it is considered the MRU entry and inserted at the tail. + cache.put(fooConfig, fooContext); + cache.put(barConfig, barContext); + cache.put(bazConfig, bazContext); + assertCacheContents(cache, "Foo", "Bar", "Baz"); + + // Note: the MRU entry is moved to the tail when accessed. + cache.get(fooConfig); + assertCacheContents(cache, "Bar", "Baz", "Foo"); + + cache.get(barConfig); + assertCacheContents(cache, "Baz", "Foo", "Bar"); + + cache.get(bazConfig); + assertCacheContents(cache, "Foo", "Bar", "Baz"); + + cache.get(barConfig); + assertCacheContents(cache, "Foo", "Baz", "Bar"); + } + + @Test + public void ensureEvictedContextsAreClosed() { + DefaultContextCache cache = new DefaultContextCache(2); + + cache.put(fooConfig, fooContext); + cache.put(barConfig, barContext); + assertCacheContents(cache, "Foo", "Bar"); + + cache.put(bazConfig, bazContext); + assertCacheContents(cache, "Bar", "Baz"); + verify(fooContext, times(1)).close(); + + cache.put(abcConfig, abcContext); + assertCacheContents(cache, "Baz", "Abc"); + verify(barContext, times(1)).close(); + + verify(abcContext, never()).close(); + verify(bazContext, never()).close(); + } + + + private static MergedContextConfiguration config(Class<?> clazz) { + return new MergedContextConfiguration(null, null, new Class<?>[] { clazz }, null, null); + } + + @SuppressWarnings("unchecked") + private static void assertCacheContents(DefaultContextCache cache, String... expectedNames) { + + Map<MergedContextConfiguration, ApplicationContext> contextMap = + (Map<MergedContextConfiguration, ApplicationContext>) ReflectionTestUtils.getField(cache, "contextMap"); + + // @formatter:off + List<String> actualNames = contextMap.keySet().stream() + .map(cfg -> cfg.getClasses()[0]) + .map(Class::getSimpleName) + .collect(toList()); + // @formatter:on + + assertEquals(asList(expectedNames), actualNames); + } + + + private static class Abc {} + private static class Foo {} + private static class Bar {} + private static class Baz {} + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java index 44962e76..bde994dc 100644 --- a/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/cache/SpringRunnerContextCacheTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ import static org.springframework.test.context.cache.ContextCacheTestUtils.*; * @author Juergen Hoeller * @since 2.5 * @see ContextCacheTests + * @see LruContextCacheTests */ @RunWith(SpringJUnit4ClassRunner.class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesInterfaceTests.java new file mode 100644 index 00000000..80d33c8d --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesInterfaceTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.tests.sample.beans.Employee; + +import static org.junit.Assert.*; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@RunWith(SpringRunner.class) +public class ActiveProfilesInterfaceTests implements ActiveProfilesTestInterface { + + @Autowired + Employee employee; + + + @Test + public void profileFromTestInterface() { + assertNotNull(employee); + assertEquals("dev", employee.getName()); + } + + + @Configuration + static class Config { + + @Bean + @Profile("dev") + Employee employee1() { + return new Employee("dev"); + } + + @Bean + @Profile("prod") + Employee employee2() { + return new Employee("prod"); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesTestInterface.java new file mode 100644 index 00000000..63ef2004 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ActiveProfilesTestInterface.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.springframework.test.context.ActiveProfiles; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@ActiveProfiles("dev") +interface ActiveProfilesTestInterface { +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithInterfaceTests.java new file mode 100644 index 00000000..47e52b9f --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithInterfaceTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.junit.Assert.*; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@RunWith(SpringRunner.class) +public class BootstrapWithInterfaceTests implements BootstrapWithTestInterface { + + @Autowired + String foo; + + + @Test + public void injectedBean() { + assertEquals("foo", foo); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithTestInterface.java new file mode 100644 index 00000000..993f3afa --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/BootstrapWithTestInterface.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import java.util.List; + +import org.springframework.test.context.BootstrapWith; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; +import org.springframework.test.context.configuration.interfaces.BootstrapWithTestInterface.CustomTestContextBootstrapper; +import org.springframework.test.context.support.DefaultTestContextBootstrapper; + +import static java.util.Collections.*; + +/** + * @author Sam Brannen + * @author Phillip Webb + * @since 4.3 + */ +@BootstrapWith(CustomTestContextBootstrapper.class) +interface BootstrapWithTestInterface { + + static class CustomTestContextBootstrapper extends DefaultTestContextBootstrapper { + + @Override + protected List<ContextCustomizerFactory> getContextCustomizerFactories() { + return singletonList( + (ContextCustomizerFactory) (testClass, configAttributes) -> (ContextCustomizer) (context, + mergedConfig) -> context.getBeanFactory().registerSingleton("foo", "foo")); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationInterfaceTests.java new file mode 100644 index 00000000..4a035e77 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationInterfaceTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.tests.sample.beans.Employee; + +import static org.junit.Assert.*; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@RunWith(SpringRunner.class) +public class ContextConfigurationInterfaceTests implements ContextConfigurationTestInterface { + + @Autowired + Employee employee; + + + @Test + public void profileFromTestInterface() { + assertNotNull(employee); + assertEquals("Dilbert", employee.getName()); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationTestInterface.java new file mode 100644 index 00000000..7eb452fc --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextConfigurationTestInterface.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.configuration.interfaces.ContextConfigurationTestInterface.Config; +import org.springframework.tests.sample.beans.Employee; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@ContextConfiguration(classes = Config.class) +interface ContextConfigurationTestInterface { + + static class Config { + + @Bean + Employee employee() { + return new Employee("Dilbert"); + } + } + +}
\ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyInterfaceTests.java new file mode 100644 index 00000000..832c244b --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyInterfaceTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.junit.Assert.*; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@RunWith(SpringRunner.class) +public class ContextHierarchyInterfaceTests implements ContextHierarchyTestInterface { + + @Autowired + String foo; + + @Autowired + String bar; + + @Autowired + String baz; + + @Autowired + ApplicationContext context; + + + @Test + public void loadContextHierarchy() { + assertNotNull("child ApplicationContext", context); + assertNotNull("parent ApplicationContext", context.getParent()); + assertNull("grandparent ApplicationContext", context.getParent().getParent()); + assertEquals("foo", foo); + assertEquals("bar", bar); + assertEquals("baz-child", baz); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyTestInterface.java new file mode 100644 index 00000000..538e2a93 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/ContextHierarchyTestInterface.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.ContextHierarchy; +import org.springframework.test.context.hierarchies.standard.SingleTestClassWithTwoLevelContextHierarchyTests; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@ContextHierarchy({ + @ContextConfiguration(classes = SingleTestClassWithTwoLevelContextHierarchyTests.ParentConfig.class), + @ContextConfiguration(classes = SingleTestClassWithTwoLevelContextHierarchyTests.ChildConfig.class) }) +interface ContextHierarchyTestInterface { +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextInterfaceTests.java new file mode 100644 index 00000000..6b0be0a1 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextInterfaceTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.junit.Assert.*; +import static org.springframework.test.context.cache.ContextCacheTestUtils.*; +import static org.springframework.test.context.junit4.JUnitTestingUtils.*; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@RunWith(JUnit4.class) +public class DirtiesContextInterfaceTests { + + private static final AtomicInteger cacheHits = new AtomicInteger(0); + private static final AtomicInteger cacheMisses = new AtomicInteger(0); + + + @BeforeClass + public static void verifyInitialCacheState() { + resetContextCache(); + // Reset static counters in case tests are run multiple times in a test suite -- + // for example, via JUnit's @Suite. + cacheHits.set(0); + cacheMisses.set(0); + assertContextCacheStatistics("BeforeClass", 0, cacheHits.get(), cacheMisses.get()); + } + + @AfterClass + public static void verifyFinalCacheState() { + assertContextCacheStatistics("AfterClass", 0, cacheHits.get(), cacheMisses.get()); + } + + @Test + public void verifyDirtiesContextBehavior() throws Exception { + runTestClassAndAssertStats(ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase.class, 1); + assertContextCacheStatistics("after class-level @DirtiesContext with clean test method and default class mode", + 0, cacheHits.get(), cacheMisses.incrementAndGet()); + } + + private void runTestClassAndAssertStats(Class<?> testClass, int expectedTestCount) throws Exception { + runTestsAndAssertCounters(testClass, expectedTestCount, 0, expectedTestCount, 0, 0); + } + + + @RunWith(SpringRunner.class) + public static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase + implements DirtiesContextTestInterface { + + @Autowired + ApplicationContext applicationContext; + + + @Test + public void verifyContextWasAutowired() { + assertNotNull("The application context should have been autowired.", this.applicationContext); + } + + + @Configuration + static class Config { + /* no beans */ + } + + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextTestInterface.java new file mode 100644 index 00000000..cd486e14 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/DirtiesContextTestInterface.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.springframework.test.annotation.DirtiesContext; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@DirtiesContext +interface DirtiesContextTestInterface { +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigInterfaceTests.java new file mode 100644 index 00000000..26f044f8 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigInterfaceTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.junit.Test; + +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; + +import static org.junit.Assert.*; + +/** + * @author Sam Brannen + * @since 4.3 + */ +public class SqlConfigInterfaceTests extends AbstractTransactionalJUnit4SpringContextTests + implements SqlConfigTestInterface { + + @Test + @Sql(scripts = "/org/springframework/test/context/jdbc/schema.sql", // + config = @SqlConfig(separator = ";")) + @Sql("/org/springframework/test/context/jdbc/data-add-users-with-custom-script-syntax.sql") + public void methodLevelScripts() { + assertNumUsers(3); + } + + protected void assertNumUsers(int expected) { + assertEquals("Number of rows in the 'user' table.", expected, countRowsInTable("user")); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigTestInterface.java new file mode 100644 index 00000000..a707a4c2 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/SqlConfigTestInterface.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.EmptyDatabaseConfig; +import org.springframework.test.context.jdbc.SqlConfig; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@ContextConfiguration(classes = EmptyDatabaseConfig.class) +@DirtiesContext +@SqlConfig(commentPrefix = "`", blockCommentStartDelimiter = "#$", blockCommentEndDelimiter = "$#", separator = "@@") +interface SqlConfigTestInterface { +}
\ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceInterfaceTests.java new file mode 100644 index 00000000..66dd6057 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceInterfaceTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@RunWith(SpringRunner.class) +public class TestPropertySourceInterfaceTests implements TestPropertySourceTestInterface { + + @Autowired + Environment env; + + + @Test + public void propertiesAreAvailableInEnvironment() { + assertThat(property("foo"), is("bar")); + assertThat(property("enigma"), is("42")); + } + + private String property(String key) { + return env.getProperty(key); + } + + + @Configuration + static class Config { + /* no user beans required for these tests */ + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceTestInterface.java new file mode 100644 index 00000000..af27989e --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/TestPropertySourceTestInterface.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.springframework.test.context.TestPropertySource; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@TestPropertySource(properties = { "foo = bar", "enigma: 42" }) +interface TestPropertySourceTestInterface { +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationInterfaceTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationInterfaceTests.java new file mode 100644 index 00000000..fdb6706b --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationInterfaceTests.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.context.WebApplicationContext; + +import static org.junit.Assert.*; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@RunWith(SpringRunner.class) +public class WebAppConfigurationInterfaceTests implements WebAppConfigurationTestInterface { + + @Autowired + WebApplicationContext wac; + + + @Test + public void wacLoaded() { + assertNotNull(wac); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationTestInterface.java b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationTestInterface.java new file mode 100644 index 00000000..dcfabd14 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/configuration/interfaces/WebAppConfigurationTestInterface.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2016 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.configuration.interfaces; + +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.configuration.interfaces.WebAppConfigurationTestInterface.Config; +import org.springframework.test.context.web.WebAppConfiguration; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@WebAppConfiguration +@ContextConfiguration(classes = Config.class) +interface WebAppConfigurationTestInterface { + + @Configuration + static class Config { + /* no user beans required for these tests */ + } + +}
\ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesOverridePropertiesFilesTestPropertySourceTests.java b/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesOverridePropertiesFilesTestPropertySourceTests.java new file mode 100644 index 00000000..c50f7ba6 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/env/InlinedPropertiesOverridePropertiesFilesTestPropertySourceTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2002-2016 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.env; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.core.env.Environment; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import static org.junit.Assert.*; + +/** + * Integration tests for {@link TestPropertySource @TestPropertySource} support with + * inlined properties that overrides properties files. + * + * @author Sam Brannen + * @since 4.3 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@TestPropertySource(locations = "explicit.properties", properties = "explicit = inlined") +public class InlinedPropertiesOverridePropertiesFilesTestPropertySourceTests { + + @Autowired + Environment env; + + @Value("${explicit}") + String explicit; + + + @Test + public void inlinedPropertyOverridesValueFromPropertiesFile() { + assertEquals("inlined", env.getProperty("explicit")); + assertEquals("inlined", this.explicit); + } + + + @Configuration + static class Config { + + @Bean + public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java index 2868b694..f8ba83fd 100644 --- a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -40,7 +40,7 @@ import static org.junit.Assert.*; public class SingleTestClassWithTwoLevelContextHierarchyTests { @Configuration - static class ParentConfig { + public static class ParentConfig { @Bean public String foo() { @@ -54,7 +54,7 @@ public class SingleTestClassWithTwoLevelContextHierarchyTests { } @Configuration - static class ChildConfig { + public static class ChildConfig { @Bean public String bar() { diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/ComposedAnnotationSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/ComposedAnnotationSqlScriptsTests.java new file mode 100644 index 00000000..0e709739 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/ComposedAnnotationSqlScriptsTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2016 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.jdbc; + +import java.lang.annotation.Retention; + +import org.junit.Test; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; + +import static java.lang.annotation.RetentionPolicy.*; +import static org.junit.Assert.*; +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.*; + +/** + * Integration tests that verify support for using {@link Sql @Sql} as a + * merged, composed annotation. + * + * @author Sam Brannen + * @since 4.3 + */ +@ContextConfiguration(classes = EmptyDatabaseConfig.class) +@DirtiesContext +public class ComposedAnnotationSqlScriptsTests extends AbstractTransactionalJUnit4SpringContextTests { + + @Test + @ComposedSql( + scripts = { "drop-schema.sql", "schema.sql" }, + statements = "INSERT INTO user VALUES('Dilbert')", + executionPhase = BEFORE_TEST_METHOD + ) + public void composedSqlAnnotation() { + assertEquals("Number of rows in the 'user' table.", 1, countRowsInTable("user")); + } + + + @Sql + @Retention(RUNTIME) + @interface ComposedSql { + + @AliasFor(annotation = Sql.class) + String[] value() default {}; + + @AliasFor(annotation = Sql.class) + String[] scripts() default {}; + + @AliasFor(annotation = Sql.class) + String[] statements() default {}; + + @AliasFor(annotation = Sql.class) + ExecutionPhase executionPhase(); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/MetaAnnotationSqlScriptsTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/MetaAnnotationSqlScriptsTests.java index 3417c17f..4fc2069e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/jdbc/MetaAnnotationSqlScriptsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/MetaAnnotationSqlScriptsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import static org.junit.Assert.*; /** * Integration tests that verify support for using {@link Sql @Sql} and - * {@link SqlGroup @SqlGroup} as a meta-annotations. + * {@link SqlGroup @SqlGroup} as meta-annotations. * * @author Sam Brannen * @since 4.1 diff --git a/spring-test/src/test/java/org/springframework/test/context/jdbc/PrimaryDataSourceTests.java b/spring-test/src/test/java/org/springframework/test/context/jdbc/PrimaryDataSourceTests.java new file mode 100644 index 00000000..f0716780 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/jdbc/PrimaryDataSourceTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2016 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.jdbc; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.test.transaction.TransactionTestUtils; + +import static org.junit.Assert.*; + +/** + * Integration tests that ensure that <em>primary</em> data sources are + * supported. + * + * @author Sam Brannen + * @since 4.3 + * @see org.springframework.test.context.transaction.PrimaryTransactionManagerTests + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@DirtiesContext +public class PrimaryDataSourceTests { + + @Configuration + static class Config { + + @Primary + @Bean + public DataSource primaryDataSource() { + // @formatter:off + return new EmbeddedDatabaseBuilder() + .generateUniqueName(true) + .addScript("classpath:/org/springframework/test/context/jdbc/schema.sql") + .build(); + // @formatter:on + } + + @Bean + public DataSource additionalDataSource() { + return new EmbeddedDatabaseBuilder().generateUniqueName(true).build(); + } + + } + + + private JdbcTemplate jdbcTemplate; + + + @Autowired + public void setDataSource(DataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Test + @Sql("data.sql") + public void dataSourceTest() { + TransactionTestUtils.assertInTransaction(false); + assertEquals("Number of rows in the 'user' table.", 1, + JdbcTestUtils.countRowsInTable(this.jdbcTemplate, "user")); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java index 493be806..5760755f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ import org.springframework.transaction.annotation.Transactional; * @see MethodLevelTransactionalSpringRunnerTests * @see Transactional */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration("transactionalTests-context.xml") public abstract class AbstractTransactionalSpringRunnerTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java index 23440f7d..4596e6cf 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.transaction.AfterTransaction; import org.springframework.test.context.transaction.BeforeTransaction; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import static org.junit.Assert.*; @@ -43,6 +44,7 @@ import static org.springframework.test.transaction.TransactionTestUtils.*; * @author Sam Brannen * @since 2.5 */ +@Transactional public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactionalSpringRunnerTests { protected static JdbcTemplate jdbcTemplate; @@ -79,7 +81,7 @@ public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactio } @BeforeTransaction - public void beforeTransaction() { + void beforeTransaction() { assertInTransaction(false); this.inTransaction = true; BeforeAndAfterTransactionAnnotationTests.numBeforeTransactionCalls++; @@ -88,7 +90,7 @@ public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactio } @AfterTransaction - public void afterTransaction() { + void afterTransaction() { assertInTransaction(false); this.inTransaction = false; BeforeAndAfterTransactionAnnotationTests.numAfterTransactionCalls++; @@ -115,7 +117,6 @@ public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactio } @Test - @Transactional public void transactionalMethod1() { assertInTransaction(true); assertEquals("Adding jane", 1, addPerson(jdbcTemplate, JANE)); @@ -124,7 +125,6 @@ public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactio } @Test - @Transactional public void transactionalMethod2() { assertInTransaction(true); assertEquals("Adding jane", 1, addPerson(jdbcTemplate, JANE)); @@ -134,6 +134,7 @@ public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactio } @Test + @Transactional(propagation = Propagation.NOT_SUPPORTED) public void nonTransactionalMethod() { assertInTransaction(false); assertEquals("Adding luke", 1, addPerson(jdbcTemplate, LUKE)); diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelDisabledSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelDisabledSpringRunnerTests.java index b55abdaa..2db442a9 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelDisabledSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelDisabledSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -30,7 +30,7 @@ import static org.junit.Assert.*; * @author Juergen Hoeller * @author Sam Brannen */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @TestExecutionListeners(ClassLevelDisabledSpringRunnerTests.CustomTestExecutionListener.class) @IfProfileValue(name = "ClassLevelDisabledSpringRunnerTests.profile_value.name", value = "enigmaX") public class ClassLevelDisabledSpringRunnerTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java index 8f29a2d0..5eb8c8ed 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ import static org.springframework.test.transaction.TransactionTestUtils.*; * {@link Transactional @Transactional}, {@link TestExecutionListeners * @TestExecutionListeners}, and {@link ContextConfiguration * @ContextConfiguration} annotations in conjunction with the - * {@link SpringJUnit4ClassRunner} and the following + * {@link SpringRunner} and the following * {@link TestExecutionListener TestExecutionListeners}: * * <ul> diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java new file mode 100644 index 00000000..2edfb0d5 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ContextCustomizerSpringRunnerTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2016 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; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.BootstrapWith; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; +import org.springframework.test.context.junit4.ContextCustomizerSpringRunnerTests.CustomTestContextBootstrapper; +import org.springframework.test.context.support.DefaultTestContextBootstrapper; + +import static java.util.Collections.*; +import static org.junit.Assert.*; + +/** + * JUnit 4 based integration test which verifies support of + * {@link ContextCustomizerFactory} and {@link ContextCustomizer}. + * + * @author Sam Brannen + * @author Phillip Webb + * @since 4.3 + */ +@RunWith(SpringRunner.class) +@BootstrapWith(CustomTestContextBootstrapper.class) +public class ContextCustomizerSpringRunnerTests { + + @Autowired String foo; + + + @Test + public void injectedBean() { + assertEquals("foo", foo); + } + + + static class CustomTestContextBootstrapper extends DefaultTestContextBootstrapper { + + @Override + protected List<ContextCustomizerFactory> getContextCustomizerFactories() { + return singletonList( + (ContextCustomizerFactory) (testClass, configAttributes) -> + (ContextCustomizer) (context, mergedConfig) -> context.getBeanFactory().registerSingleton("foo", "foo") + ); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java index aab80b71..be8a2ff9 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ import static org.junit.Assert.*; * @author Sam Brannen * @since 3.0 */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @BootstrapWith(CustomDefaultContextLoaderClassSpringRunnerTests.PropertiesBasedTestContextBootstrapper.class) @ContextConfiguration("PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties") public class CustomDefaultContextLoaderClassSpringRunnerTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java index 0c1f043a..1b027899 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseRollbackAnnotationTransactionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ import static org.springframework.test.transaction.TransactionTestUtils.*; * @see Transactional#transactionManager * @see DefaultRollbackFalseTransactionalTests */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false) @Transactional("txMgr") @Rollback(false) diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java index 984fc62f..49f70f6d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueRollbackAnnotationTransactionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,7 +45,7 @@ import static org.springframework.test.transaction.TransactionTestUtils.*; * @see Transactional#transactionManager * @see DefaultRollbackTrueTransactionalTests */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(classes = EmbeddedPersonDatabaseTestsConfig.class, inheritLocations = false) @Transactional("txMgr") @Rollback(true) diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRunnerTests.java index a445fcaf..f4e84996 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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,7 +34,7 @@ import static org.junit.Assert.*; * {@link IfProfileValue @IfProfileValue} and * {@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration} * (with the <em>implicit, default {@link ProfileValueSource}</em>) annotations in - * conjunction with the {@link SpringJUnit4ClassRunner}. + * conjunction with the {@link SpringRunner}. * <p> * Note that {@link TestExecutionListeners @TestExecutionListeners} is * explicitly configured with an empty list, thus disabling all default @@ -44,7 +44,7 @@ import static org.junit.Assert.*; * @since 2.5 * @see HardCodedProfileValueSourceSpringRunnerTests */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @TestExecutionListeners( {}) public class EnabledAndIgnoredSpringRunnerTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ExpectedExceptionSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ExpectedExceptionSpringRunnerTests.java index 5fc3947d..4cfb4699 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/ExpectedExceptionSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ExpectedExceptionSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import static org.springframework.test.context.junit4.JUnitTestingUtils.*; /** * Verifies proper handling of JUnit's {@link Test#expected() @Test(expected=...)} - * support in conjunction with the {@link SpringJUnit4ClassRunner}. + * support in conjunction with the {@link SpringRunner}. * * @author Sam Brannen * @since 3.0 @@ -39,7 +39,7 @@ public class ExpectedExceptionSpringRunnerTests { @Test public void expectedExceptions() throws Exception { - runTestsAndAssertCounters(SpringJUnit4ClassRunner.class, ExpectedExceptionSpringRunnerTestCase.class, 1, 0, 1, 0, 0); + runTestsAndAssertCounters(SpringRunner.class, ExpectedExceptionSpringRunnerTestCase.class, 1, 0, 1, 0, 0); } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java index daebe2bc..4918606f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsJUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,7 +79,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests { } protected Class<? extends Runner> getRunnerClass() { - return SpringJUnit4ClassRunner.class; + return SpringRunner.class; } @Test @@ -133,7 +133,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests { } } - @RunWith(SpringJUnit4ClassRunner.class) + @RunWith(SpringRunner.class) @TestExecutionListeners({}) public static abstract class BaseTestCase { @@ -168,7 +168,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests { } @Ignore("TestCase classes are run manually by the enclosing test class") - @RunWith(SpringJUnit4ClassRunner.class) + @RunWith(SpringRunner.class) @ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml") @Transactional public static class FailingBeforeTransactionTestCase { @@ -184,7 +184,7 @@ public class FailingBeforeAndAfterMethodsJUnitTests { } @Ignore("TestCase classes are run manually by the enclosing test class") - @RunWith(SpringJUnit4ClassRunner.class) + @RunWith(SpringRunner.class) @ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml") @Transactional public static class FailingAfterTransactionTestCase { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/HardCodedProfileValueSourceSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/HardCodedProfileValueSourceSpringRunnerTests.java index 9a26751f..b0e18843 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/HardCodedProfileValueSourceSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/HardCodedProfileValueSourceSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -28,7 +28,7 @@ import org.springframework.test.annotation.ProfileValueSourceConfiguration; * @IfProfileValue} and {@link ProfileValueSourceConfiguration * @ProfileValueSourceConfiguration} (with an * <em>explicit, custom defined {@link ProfileValueSource}</em>) annotations in - * conjunction with the {@link SpringJUnit4ClassRunner}. + * conjunction with the {@link SpringRunner}. * </p> * * @author Sam Brannen diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java index 42700595..f9b6b749 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ import static org.springframework.test.transaction.TransactionTestUtils.*; * {@link Transactional @Transactional}, {@link TestExecutionListeners * @TestExecutionListeners}, and {@link ContextConfiguration * @ContextConfiguration} annotations in conjunction with the - * {@link SpringJUnit4ClassRunner} and the following + * {@link SpringRunner} and the following * {@link TestExecutionListener TestExecutionListeners}: * * <ul> diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/OptionalContextConfigurationSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/OptionalContextConfigurationSpringRunnerTests.java new file mode 100644 index 00000000..5cb4ce83 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/OptionalContextConfigurationSpringRunnerTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2016 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; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.junit.Assert.assertEquals; + +/** + * JUnit 4 based integration test which verifies that {@link @ContextConfiguration} + * is optional. + * + * @author Phillip Webb + * @author Sam Brannen + * @since 4.3 + */ +@RunWith(SpringRunner.class) +public class OptionalContextConfigurationSpringRunnerTests { + + @Autowired + String foo; + + + @Test + public void contextConfigurationAnnotationIsOptional() { + assertEquals("foo", foo); + } + + + @Configuration + static class Config { + + @Bean + String foo() { + return "foo"; + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java index acbb6cc2..b52a9fa1 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -32,7 +32,7 @@ import static org.junit.Assert.*; /** * <p> * JUnit 4 based test class, which verifies the expected functionality of - * {@link SpringJUnit4ClassRunner} in conjunction with support for application contexts + * {@link SpringRunner} in conjunction with support for application contexts * loaded from Java {@link Properties} files. Specifically, the * {@link ContextConfiguration#loader() loader} attribute of {@code ContextConfiguration} * and the @@ -54,7 +54,7 @@ import static org.junit.Assert.*; * @see GenericPropertiesContextLoader * @see SpringJUnit4ClassRunnerAppCtxTests */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration(loader = GenericPropertiesContextLoader.class) public class PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java index 94a884bf..d2bf46bf 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ import static org.springframework.test.context.junit4.JUnitTestingUtils.*; /** * Verifies proper handling of the following in conjunction with the - * {@link SpringJUnit4ClassRunner}: + * {@link SpringRunner}: * <ul> * <li>Spring's {@link Repeat @Repeat}</li> * <li>Spring's {@link Timed @Timed}</li> @@ -82,7 +82,7 @@ public class RepeatedSpringRunnerTests { } protected Class<? extends Runner> getRunnerClass() { - return SpringJUnit4ClassRunner.class; + return SpringRunner.class; } @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit47ClassRunnerRuleTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit47ClassRunnerRuleTests.java index 6f0eddff..e59cdde9 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit47ClassRunnerRuleTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit47ClassRunnerRuleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -27,14 +27,14 @@ import static org.junit.Assert.*; /** * Verifies support for JUnit 4.7 {@link Rule Rules} in conjunction with the - * {@link SpringJUnit4ClassRunner}. The body of this test class is taken from - * the JUnit 4.7 release notes. + * {@link SpringRunner}. The body of this test class is taken from the + * JUnit 4.7 release notes. * * @author JUnit 4.7 Team * @author Sam Brannen * @since 3.0 */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @TestExecutionListeners( {}) public class SpringJUnit47ClassRunnerRuleTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java index 292aebfc..c60745d3 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ import static org.junit.Assert.*; /** * SpringJUnit4ClassRunnerAppCtxTests serves as a <em>proof of concept</em> * JUnit 4 based test class, which verifies the expected functionality of - * {@link SpringJUnit4ClassRunner} in conjunction with the following: + * {@link SpringRunner} in conjunction with the following: * * <ul> * <li>{@link ContextConfiguration @ContextConfiguration}</li> @@ -73,7 +73,7 @@ import static org.junit.Assert.*; * @see RelativePathSpringJUnit4ClassRunnerAppCtxTests * @see InheritedConfigSpringJUnit4ClassRunnerAppCtxTests */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration @TestExecutionListeners(DependencyInjectionTestExecutionListener.class) public class SpringJUnit4ClassRunnerAppCtxTests implements ApplicationContextAware, BeanNameAware, InitializingBean { 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 8f301117..020bda60 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,12 +45,12 @@ import org.springframework.test.context.junit4.profile.xml.DevProfileXmlConfigTe import org.springframework.test.context.transaction.programmatic.ProgrammaticTxMgmtTests; /** - * JUnit test suite for tests involving {@link SpringJUnit4ClassRunner} and the + * JUnit test suite for tests involving {@link SpringRunner} and the * <em>Spring TestContext Framework</em>; only intended to be run manually as a * convenience. * * <p>This test suite serves a dual purpose of verifying that tests run with - * {@link SpringJUnit4ClassRunner} can be used in conjunction with JUnit's + * {@link SpringRunner} can be used in conjunction with JUnit's * {@link Suite} runner. * * <p>Note that tests included in this suite will be executed at least twice if diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesSpringRunnerTests.java index 4dd270ca..f4cda4ea 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2016 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. @@ -22,7 +22,7 @@ import org.springframework.test.context.TestExecutionListeners; /** * <p> - * Simple unit test to verify that {@link SpringJUnit4ClassRunner} does not + * Simple unit test to verify that {@link SpringRunner} does not * hinder correct functionality of standard JUnit 4.4+ testing features. * </p> * <p> @@ -35,7 +35,7 @@ import org.springframework.test.context.TestExecutionListeners; * @since 2.5 * @see StandardJUnit4FeaturesTests */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @TestExecutionListeners({}) public class StandardJUnit4FeaturesSpringRunnerTests extends StandardJUnit4FeaturesTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java index d59bc140..cede912c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ import static org.springframework.test.context.junit4.JUnitTestingUtils.*; /** * Verifies proper handling of the following in conjunction with the - * {@link SpringJUnit4ClassRunner}: + * {@link SpringRunner}: * <ul> * <li>JUnit's {@link Test#timeout() @Test(timeout=...)}</li> * <li>Spring's {@link Timed @Timed}</li> @@ -49,7 +49,7 @@ public class TimedSpringRunnerTests { } protected Class<? extends Runner> getRunnerClass() { - return SpringJUnit4ClassRunner.class; + return SpringRunner.class; } @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java index 3fcdb040..cffe292f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -36,7 +36,7 @@ import static org.springframework.test.transaction.TransactionTestUtils.*; * @author Sam Brannen * @since 2.5 */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration("transactionalTests-context.xml") @Transactional public class TimedTransactionalSpringRunnerTests { diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/BarConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/BarConfig.java new file mode 100644 index 00000000..1ee6b6fd --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/BarConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2016 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.aci.annotation; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@Configuration +class BarConfig { + + @Bean + String bar() { + return "bar"; + } + +}
\ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/FooConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/FooConfig.java new file mode 100644 index 00000000..a080fd84 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/FooConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2016 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.aci.annotation; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Sam Brannen + * @since 4.3 + */ +@Configuration +class FooConfig { + + @Bean + String foo() { + return "foo"; + } + +}
\ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerConfiguredViaMetaAnnotationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerConfiguredViaMetaAnnotationTests.java new file mode 100644 index 00000000..971e3f4b --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerConfiguredViaMetaAnnotationTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2016 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.aci.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.annotation.AliasFor; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.junit4.aci.annotation.InitializerConfiguredViaMetaAnnotationTests.ComposedContextConfiguration; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import static org.junit.Assert.assertEquals; + +/** + * Integration test that demonstrates how to register one or more {@code @Configuration} + * classes via an {@link ApplicationContextInitializer} in a composed annotation so + * that certain {@code @Configuration} classes are always registered whenever the composed + * annotation is used, even if the composed annotation is used to declare additional + * {@code @Configuration} classes. + * + * <p>This class has been implemented in response to the following Stack Overflow question: + * <a href="http://stackoverflow.com/questions/35733344/can-contextconfiguration-in-a-custom-annotation-be-merged"> + * Can {@code @ContextConfiguration} in a custom annotation be merged?</a> + * + * @author Sam Brannen + * @since 4.3 + */ +@RunWith(SpringRunner.class) +@ComposedContextConfiguration(BarConfig.class) +public class InitializerConfiguredViaMetaAnnotationTests { + + @Autowired + String foo; + + @Autowired + String bar; + + @Autowired + List<String> strings; + + + @Test + public void beansFromInitializerAndComposedAnnotation() { + assertEquals(2, strings.size()); + assertEquals("foo", foo); + assertEquals("bar", bar); + } + + + static class FooConfigInitializer implements ApplicationContextInitializer<GenericApplicationContext> { + + @Override + public void initialize(GenericApplicationContext applicationContext) { + new AnnotatedBeanDefinitionReader(applicationContext).register(FooConfig.class); + } + } + + @ContextConfiguration(loader = AnnotationConfigContextLoader.class, initializers = FooConfigInitializer.class) + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @interface ComposedContextConfiguration { + + @AliasFor(annotation = ContextConfiguration.class, attribute = "classes") + Class<?>[] value() default {}; + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsContextInitializerTests.java b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsContextInitializerTests.java index 4b70a8cb..f92c7418 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsContextInitializerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsContextInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.test.context.support; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -35,63 +36,62 @@ import org.springframework.web.context.support.GenericWebApplicationContext; * @author Sam Brannen * @since 3.1 */ +@SuppressWarnings("unchecked") public class BootstrapTestUtilsContextInitializerTests extends AbstractContextConfigurationUtilsTests { @Test - public void buildMergedConfigWithLocalInitializer() { - Class<?> testClass = InitializersFoo.class; - Class<?>[] expectedClasses = new Class<?>[] { FooConfig.class }; - Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> expectedInitializerClasses// - = new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>(); - expectedInitializerClasses.add(FooInitializer.class); + public void buildMergedConfigWithSingleLocalInitializer() { + Class<?> testClass = SingleInitializer.class; + MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); + + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, + initializers(FooInitializer.class), DelegatingSmartContextLoader.class); + } + @Test + public void buildMergedConfigWithLocalInitializerAndConfigClass() { + Class<?> testClass = InitializersFoo.class; MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, - DelegatingSmartContextLoader.class); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, classes(FooConfig.class), + initializers(FooInitializer.class), DelegatingSmartContextLoader.class); } @Test public void buildMergedConfigWithLocalAndInheritedInitializer() { Class<?> testClass = InitializersBar.class; - Class<?>[] expectedClasses = new Class<?>[] { FooConfig.class, BarConfig.class }; - Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> expectedInitializerClasses// - = new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>(); - expectedInitializerClasses.add(FooInitializer.class); - expectedInitializerClasses.add(BarInitializer.class); - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, - DelegatingSmartContextLoader.class); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, classes(FooConfig.class, BarConfig.class), + initializers(FooInitializer.class, BarInitializer.class), DelegatingSmartContextLoader.class); } @Test public void buildMergedConfigWithOverriddenInitializers() { Class<?> testClass = OverriddenInitializersBar.class; - Class<?>[] expectedClasses = new Class<?>[] { FooConfig.class, BarConfig.class }; - Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> expectedInitializerClasses// - = new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>(); - expectedInitializerClasses.add(BarInitializer.class); - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, - DelegatingSmartContextLoader.class); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, classes(FooConfig.class, BarConfig.class), + initializers(BarInitializer.class), DelegatingSmartContextLoader.class); } @Test public void buildMergedConfigWithOverriddenInitializersAndClasses() { Class<?> testClass = OverriddenInitializersAndClassesBar.class; - Class<?>[] expectedClasses = new Class<?>[] { BarConfig.class }; - Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> expectedInitializerClasses// - = new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>(); - expectedInitializerClasses.add(BarInitializer.class); - MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses, - DelegatingSmartContextLoader.class); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, classes(BarConfig.class), + initializers(BarInitializer.class), DelegatingSmartContextLoader.class); + } + + private Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializers( + Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>... classes) { + + return new HashSet<>(Arrays.asList(classes)); + } + + private Class<?>[] classes(Class<?>... classes) { + return classes; } @@ -109,6 +109,10 @@ public class BootstrapTestUtilsContextInitializerTests extends AbstractContextCo } } + @ContextConfiguration(initializers = FooInitializer.class) + private static class SingleInitializer { + } + @ContextConfiguration(classes = FooConfig.class, initializers = FooInitializer.class) private static class InitializersFoo { } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java index cef782e2..b499c257 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,15 +21,20 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.test.context.BootstrapTestUtils; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.web.WebDelegatingSmartContextLoader; import org.springframework.test.context.web.WebMergedContextConfiguration; -import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; /** * Unit tests for {@link BootstrapTestUtils} involving {@link MergedContextConfiguration}. @@ -39,12 +44,28 @@ import static org.junit.Assert.*; */ public class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigurationUtilsTests { + @Rule + public final ExpectedException exception = ExpectedException.none(); + + @Test - public void buildMergedConfigWithoutAnnotation() { + public void buildImplicitMergedConfigWithoutAnnotation() { Class<?> testClass = Enigma.class; MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass); - assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, null); + assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, DelegatingSmartContextLoader.class); + } + + /** + * @since 4.3 + */ + @Test + public void buildMergedConfigWithContextConfigurationWithoutLocationsClassesOrInitializers() { + exception.expect(IllegalStateException.class); + exception.expectMessage(startsWith("DelegatingSmartContextLoader was unable to detect defaults, " + + "and no ApplicationContextInitializers or ContextCustomizers were declared for context configuration attributes")); + + buildMergedContextConfiguration(MissingContextAttributesTestCase.class); } @Test @@ -200,4 +221,8 @@ public class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigur public static class GermanShepherd extends WorkingDog { } + @ContextConfiguration + static class MissingContextAttributesTestCase { + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java index 3faab204..37025fdf 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextLoaderUtilsContextHierarchyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,9 +59,13 @@ public class ContextLoaderUtilsContextHierarchyTests extends AbstractContextConf resolveContextHierarchyAttributes(SingleTestClassWithContextConfigurationAndContextHierarchyOnSingleMetaAnnotation.class); } - @Test(expected = IllegalStateException.class) + @Test public void resolveContextHierarchyAttributesForSingleTestClassWithImplicitSingleLevelContextHierarchy() { - resolveContextHierarchyAttributes(BareAnnotations.class); + List<List<ContextConfigurationAttributes>> hierarchyAttributes = resolveContextHierarchyAttributes(BareAnnotations.class); + assertEquals(1, hierarchyAttributes.size()); + List<ContextConfigurationAttributes> configAttributesList = hierarchyAttributes.get(0); + assertEquals(1, configAttributesList.size()); + debugConfigAttributes(configAttributesList); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java index 0395ee36..8f416cfa 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,17 +56,6 @@ public class DelegatingSmartContextLoaderTests { // --- SmartContextLoader - processContextConfiguration() ------------------ @Test - public void processContextConfigurationWithoutLocationsAndConfigurationClassesForBogusTestClass() { - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage(startsWith("Neither")); - expectedException.expectMessage(containsString("was able to detect defaults")); - - ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes(getClass(), - EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class); - loader.processContextConfiguration(configAttributes); - } - - @Test public void processContextConfigurationWithDefaultXmlConfigGeneration() { ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes(XmlTestCase.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class); diff --git a/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java index 8f6c8d7e..b1d7ddfd 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/TestPropertySourceUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,13 +26,17 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.annotation.AnnotationConfigurationException; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.ResourceLoader; import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockPropertySource; import org.springframework.test.context.TestPropertySource; import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; import static org.springframework.test.context.support.TestPropertySourceUtils.*; /** @@ -44,20 +48,16 @@ import static org.springframework.test.context.support.TestPropertySourceUtils.* public class TestPropertySourceUtilsTests { private static final String[] EMPTY_STRING_ARRAY = new String[0]; - private static final String[] KEY_VALUE_PAIR = new String[] { "key = value" }; + + private static final String[] KEY_VALUE_PAIR = new String[] {"key = value"}; + + private static final String[] FOO_LOCATIONS = new String[] {"classpath:/foo.properties"}; + @Rule public ExpectedException expectedException = ExpectedException.none(); - private void assertMergedTestPropertySources(Class<?> testClass, String[] expectedLocations, - String[] expectedProperties) { - MergedTestPropertySources mergedPropertySources = buildMergedTestPropertySources(testClass); - assertNotNull(mergedPropertySources); - assertArrayEquals(expectedLocations, mergedPropertySources.getLocations()); - assertArrayEquals(expectedProperties, mergedPropertySources.getProperties()); - } - @Test public void emptyAnnotation() { expectedException.expect(IllegalStateException.class); @@ -76,8 +76,8 @@ public class TestPropertySourceUtilsTests { @Test public void value() { - assertMergedTestPropertySources(ValuePropertySources.class, new String[] { "classpath:/value.xml" }, - EMPTY_STRING_ARRAY); + assertMergedTestPropertySources(ValuePropertySources.class, asArray("classpath:/value.xml"), + EMPTY_STRING_ARRAY); } @Test @@ -88,44 +88,88 @@ public class TestPropertySourceUtilsTests { @Test public void locationsAndProperties() { - assertMergedTestPropertySources(LocationsAndPropertiesPropertySources.class, new String[] { - "classpath:/foo1.xml", "classpath:/foo2.xml" }, new String[] { "k1a=v1a", "k1b: v1b" }); + assertMergedTestPropertySources(LocationsAndPropertiesPropertySources.class, + asArray("classpath:/foo1.xml", "classpath:/foo2.xml"), asArray("k1a=v1a", "k1b: v1b")); } @Test public void inheritedLocationsAndProperties() { - assertMergedTestPropertySources(InheritedPropertySources.class, new String[] { "classpath:/foo1.xml", - "classpath:/foo2.xml" }, new String[] { "k1a=v1a", "k1b: v1b" }); + assertMergedTestPropertySources(InheritedPropertySources.class, + asArray("classpath:/foo1.xml", "classpath:/foo2.xml"), asArray("k1a=v1a", "k1b: v1b")); } @Test public void extendedLocationsAndProperties() { - assertMergedTestPropertySources(ExtendedPropertySources.class, new String[] { "classpath:/foo1.xml", - "classpath:/foo2.xml", "classpath:/bar1.xml", "classpath:/bar2.xml" }, new String[] { "k1a=v1a", - "k1b: v1b", "k2a v2a", "k2b: v2b" }); + assertMergedTestPropertySources(ExtendedPropertySources.class, + asArray("classpath:/foo1.xml", "classpath:/foo2.xml", "classpath:/bar1.xml", "classpath:/bar2.xml"), + asArray("k1a=v1a", "k1b: v1b", "k2a v2a", "k2b: v2b")); } @Test public void overriddenLocations() { assertMergedTestPropertySources(OverriddenLocationsPropertySources.class, - new String[] { "classpath:/baz.properties" }, new String[] { "k1a=v1a", "k1b: v1b", "key = value" }); + asArray("classpath:/baz.properties"), asArray("k1a=v1a", "k1b: v1b", "key = value")); } @Test public void overriddenProperties() { - assertMergedTestPropertySources(OverriddenPropertiesPropertySources.class, new String[] { - "classpath:/foo1.xml", "classpath:/foo2.xml", "classpath:/baz.properties" }, KEY_VALUE_PAIR); + assertMergedTestPropertySources(OverriddenPropertiesPropertySources.class, + asArray("classpath:/foo1.xml", "classpath:/foo2.xml", "classpath:/baz.properties"), KEY_VALUE_PAIR); } @Test public void overriddenLocationsAndProperties() { assertMergedTestPropertySources(OverriddenLocationsAndPropertiesPropertySources.class, - new String[] { "classpath:/baz.properties" }, KEY_VALUE_PAIR); + asArray("classpath:/baz.properties"), KEY_VALUE_PAIR); + } + + + @Test + public void addPropertiesFilesToEnvironmentWithNullContext() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("must not be null"); + addPropertiesFilesToEnvironment((ConfigurableApplicationContext) null, FOO_LOCATIONS); + } + + @Test + public void addPropertiesFilesToEnvironmentWithContextAndNullLocations() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("must not be null"); + addPropertiesFilesToEnvironment(mock(ConfigurableApplicationContext.class), (String[]) null); + } + + @Test + public void addPropertiesFilesToEnvironmentWithNullEnvironment() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("must not be null"); + addPropertiesFilesToEnvironment((ConfigurableEnvironment) null, mock(ResourceLoader.class), FOO_LOCATIONS); + } + + @Test + public void addPropertiesFilesToEnvironmentWithEnvironmentAndNullLocations() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("must not be null"); + addPropertiesFilesToEnvironment(new MockEnvironment(), mock(ResourceLoader.class), (String[]) null); + } + + @Test + public void addPropertiesFilesToEnvironmentWithSinglePropertyFromVirtualFile() { + ConfigurableEnvironment environment = new MockEnvironment(); + + MutablePropertySources propertySources = environment.getPropertySources(); + propertySources.remove(MockPropertySource.MOCK_PROPERTIES_PROPERTY_SOURCE_NAME); + assertEquals(0, propertySources.size()); + + String pair = "key = value"; + ByteArrayResource resource = new ByteArrayResource(pair.getBytes(), "from inlined property: " + pair); + ResourceLoader resourceLoader = mock(ResourceLoader.class); + when(resourceLoader.getResource(anyString())).thenReturn(resource); + + addPropertiesFilesToEnvironment(environment, resourceLoader, FOO_LOCATIONS); + assertEquals(1, propertySources.size()); + assertEquals("value", environment.getProperty("key")); } - /** - * @since 4.1.5 - */ @Test public void addInlinedPropertiesToEnvironmentWithNullContext() { expectedException.expect(IllegalArgumentException.class); @@ -133,19 +177,13 @@ public class TestPropertySourceUtilsTests { addInlinedPropertiesToEnvironment((ConfigurableApplicationContext) null, KEY_VALUE_PAIR); } - /** - * @since 4.1.5 - */ @Test public void addInlinedPropertiesToEnvironmentWithContextAndNullInlinedProperties() { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("inlined"); - addInlinedPropertiesToEnvironment(mock(ConfigurableApplicationContext.class), null); + addInlinedPropertiesToEnvironment(mock(ConfigurableApplicationContext.class), (String[]) null); } - /** - * @since 4.1.5 - */ @Test public void addInlinedPropertiesToEnvironmentWithNullEnvironment() { expectedException.expect(IllegalArgumentException.class); @@ -153,39 +191,27 @@ public class TestPropertySourceUtilsTests { addInlinedPropertiesToEnvironment((ConfigurableEnvironment) null, KEY_VALUE_PAIR); } - /** - * @since 4.1.5 - */ @Test public void addInlinedPropertiesToEnvironmentWithEnvironmentAndNullInlinedProperties() { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("inlined"); - addInlinedPropertiesToEnvironment(new MockEnvironment(), null); + addInlinedPropertiesToEnvironment(new MockEnvironment(), (String[]) null); } - /** - * @since 4.1.5 - */ @Test public void addInlinedPropertiesToEnvironmentWithMalformedUnicodeInValue() { expectedException.expect(IllegalStateException.class); expectedException.expectMessage("Failed to load test environment property"); - addInlinedPropertiesToEnvironment(new MockEnvironment(), new String[] { "key = \\uZZZZ" }); + addInlinedPropertiesToEnvironment(new MockEnvironment(), asArray("key = \\uZZZZ")); } - /** - * @since 4.1.5 - */ @Test public void addInlinedPropertiesToEnvironmentWithMultipleKeyValuePairsInSingleInlinedProperty() { expectedException.expect(IllegalStateException.class); expectedException.expectMessage("Failed to load exactly one test environment property"); - addInlinedPropertiesToEnvironment(new MockEnvironment(), new String[] { "a=b\nx=y" }); + addInlinedPropertiesToEnvironment(new MockEnvironment(), asArray("a=b\nx=y")); } - /** - * @since 4.1.5 - */ @Test @SuppressWarnings("rawtypes") public void addInlinedPropertiesToEnvironmentWithEmptyProperty() { @@ -193,7 +219,7 @@ public class TestPropertySourceUtilsTests { MutablePropertySources propertySources = environment.getPropertySources(); propertySources.remove(MockPropertySource.MOCK_PROPERTIES_PROPERTY_SOURCE_NAME); assertEquals(0, propertySources.size()); - addInlinedPropertiesToEnvironment(environment, new String[] { " " }); + addInlinedPropertiesToEnvironment(environment, asArray(" ")); assertEquals(1, propertySources.size()); assertEquals(0, ((Map) propertySources.iterator().next().getSource()).size()); } @@ -202,10 +228,25 @@ public class TestPropertySourceUtilsTests { public void convertInlinedPropertiesToMapWithNullInlinedProperties() { expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("inlined"); - convertInlinedPropertiesToMap(null); + convertInlinedPropertiesToMap((String[]) null); + } + + + private static void assertMergedTestPropertySources(Class<?> testClass, String[] expectedLocations, + String[] expectedProperties) { + + MergedTestPropertySources mergedPropertySources = buildMergedTestPropertySources(testClass); + assertNotNull(mergedPropertySources); + assertArrayEquals(expectedLocations, mergedPropertySources.getLocations()); + assertArrayEquals(expectedProperties, mergedPropertySources.getProperties()); + } + + + @SafeVarargs + private static <T> T[] asArray(T... arr) { + return arr; } - // ------------------------------------------------------------------- @TestPropertySource static class EmptyPropertySources { diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java index a7c9f974..6c515027 100644 --- a/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -54,8 +54,8 @@ import static org.testng.Assert.*; * @since 3.1 */ @ContextConfiguration -public class AnnotationConfigTransactionalTestNGSpringContextTests extends - AbstractTransactionalTestNGSpringContextTests { +public class AnnotationConfigTransactionalTestNGSpringContextTests + extends AbstractTransactionalTestNGSpringContextTests { private static final String JANE = "jane"; private static final String SUE = "sue"; @@ -94,7 +94,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends } @BeforeClass - public void beforeClass() { + void beforeClass() { numSetUpCalls = 0; numSetUpCallsInTransaction = 0; numTearDownCalls = 0; @@ -102,7 +102,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends } @AfterClass - public void afterClass() { + void afterClass() { assertEquals(numSetUpCalls, NUM_TESTS, "number of calls to setUp()."); assertEquals(numSetUpCallsInTransaction, NUM_TX_TESTS, "number of calls to setUp() within a transaction."); assertEquals(numTearDownCalls, NUM_TESTS, "number of calls to tearDown()."); @@ -111,7 +111,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends @Test @Transactional(propagation = Propagation.NOT_SUPPORTED) - public void autowiringFromConfigClass() { + void autowiringFromConfigClass() { assertNotNull(employee, "The employee should have been autowired."); assertEquals(employee.getName(), "John Smith"); @@ -120,13 +120,13 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends } @BeforeTransaction - public void beforeTransaction() { + void beforeTransaction() { assertNumRowsInPersonTable(1, "before a transactional test method"); assertAddPerson(YODA); } @BeforeMethod - public void setUp() throws Exception { + void setUp() throws Exception { numSetUpCalls++; if (inTransaction()) { numSetUpCallsInTransaction++; @@ -135,7 +135,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends } @Test - public void modifyTestDataWithinTransaction() { + void modifyTestDataWithinTransaction() { assertInTransaction(true); assertAddPerson(JANE); assertAddPerson(SUE); @@ -143,7 +143,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends } @AfterMethod - public void tearDown() throws Exception { + void tearDown() throws Exception { numTearDownCalls++; if (inTransaction()) { numTearDownCallsInTransaction++; @@ -152,7 +152,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends } @AfterTransaction - public void afterTransaction() { + void afterTransaction() { assertEquals(deletePerson(YODA), 1, "Deleting yoda"); assertNumRowsInPersonTable(1, "after a transactional test method"); } @@ -162,7 +162,7 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends static class ContextConfiguration { @Bean - public Employee employee() { + Employee employee() { Employee employee = new Employee(); employee.setName("John Smith"); employee.setAge(42); @@ -171,17 +171,17 @@ public class AnnotationConfigTransactionalTestNGSpringContextTests extends } @Bean - public Pet pet() { + Pet pet() { return new Pet("Fido"); } @Bean - public PlatformTransactionManager transactionManager() { + PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @Bean - public DataSource dataSource() { + DataSource dataSource() { return new EmbeddedDatabaseBuilder()// .addScript("classpath:/org/springframework/test/jdbc/schema.sql")// .addScript("classpath:/org/springframework/test/jdbc/data.sql")// diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests.java index 2acc370e..d249fc38 100644 --- a/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -117,7 +117,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans } @BeforeClass - public void beforeClass() { + void beforeClass() { numSetUpCalls = 0; numSetUpCallsInTransaction = 0; numTearDownCalls = 0; @@ -125,7 +125,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans } @AfterClass - public void afterClass() { + void afterClass() { assertEquals(numSetUpCalls, NUM_TESTS, "number of calls to setUp()."); assertEquals(numSetUpCallsInTransaction, NUM_TX_TESTS, "number of calls to setUp() within a transaction."); assertEquals(numTearDownCalls, NUM_TESTS, "number of calls to tearDown()."); @@ -134,7 +134,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans @Test @Transactional(propagation = Propagation.NOT_SUPPORTED) - public void verifyApplicationContextSet() { + void verifyApplicationContextSet() { assertInTransaction(false); assertNotNull(super.applicationContext, "The application context should have been set due to ApplicationContextAware semantics."); @@ -144,7 +144,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans @Test @Transactional(propagation = Propagation.NOT_SUPPORTED) - public void verifyBeanInitialized() { + void verifyBeanInitialized() { assertInTransaction(false); assertTrue(beanInitialized, "This test instance should have been initialized due to InitializingBean semantics."); @@ -152,7 +152,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans @Test @Transactional(propagation = Propagation.NOT_SUPPORTED) - public void verifyBeanNameSet() { + void verifyBeanNameSet() { assertInTransaction(false); assertEquals(beanName, getClass().getName(), "The bean name of this test instance should have been set due to BeanNameAware semantics."); @@ -160,7 +160,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans @Test @Transactional(propagation = Propagation.NOT_SUPPORTED) - public void verifyAnnotationAutowiredFields() { + void verifyAnnotationAutowiredFields() { assertInTransaction(false); assertNull(nonrequiredLong, "The nonrequiredLong field should NOT have been autowired."); assertNotNull(pet, "The pet field should have been autowired."); @@ -169,7 +169,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans @Test @Transactional(propagation = Propagation.NOT_SUPPORTED) - public void verifyAnnotationAutowiredMethods() { + void verifyAnnotationAutowiredMethods() { assertInTransaction(false); assertNotNull(employee, "The setEmployee() method should have been autowired."); assertEquals(employee.getName(), "John Smith", "employee's name."); @@ -177,26 +177,26 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans @Test @Transactional(propagation = Propagation.NOT_SUPPORTED) - public void verifyResourceAnnotationInjectedFields() { + void verifyResourceAnnotationInjectedFields() { assertInTransaction(false); assertEquals(foo, "Foo", "The foo field should have been injected via @Resource."); } @Test @Transactional(propagation = Propagation.NOT_SUPPORTED) - public void verifyResourceAnnotationInjectedMethods() { + void verifyResourceAnnotationInjectedMethods() { assertInTransaction(false); assertEquals(bar, "Bar", "The setBar() method should have been injected via @Resource."); } @BeforeTransaction - public void beforeTransaction() { + void beforeTransaction() { assertNumRowsInPersonTable(1, "before a transactional test method"); assertAddPerson(YODA); } @BeforeMethod - public void setUp() throws Exception { + void setUp() throws Exception { numSetUpCalls++; if (inTransaction()) { numSetUpCallsInTransaction++; @@ -205,7 +205,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans } @Test - public void modifyTestDataWithinTransaction() { + void modifyTestDataWithinTransaction() { assertInTransaction(true); assertAddPerson(JANE); assertAddPerson(SUE); @@ -213,7 +213,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans } @AfterMethod - public void tearDown() throws Exception { + void tearDown() throws Exception { numTearDownCalls++; if (inTransaction()) { numTearDownCallsInTransaction++; @@ -222,7 +222,7 @@ public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTrans } @AfterTransaction - public void afterTransaction() { + void afterTransaction() { assertEquals(deletePerson(YODA), 1, "Deleting yoda"); assertNumRowsInPersonTable(1, "after a transactional test method"); } diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/web/ServletTestExecutionListenerTestNGIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/web/ServletTestExecutionListenerTestNGIntegrationTests.java index 25f33425..6de10205 100644 --- a/spring-test/src/test/java/org/springframework/test/context/testng/web/ServletTestExecutionListenerTestNGIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/testng/web/ServletTestExecutionListenerTestNGIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ public class ServletTestExecutionListenerTestNGIntegrationTests extends Abstract * @see #ensureMocksAreReinjectedBetweenTests_2 */ @Test - public void ensureMocksAreReinjectedBetweenTests_1() { + void ensureMocksAreReinjectedBetweenTests_1() { assertInjectedServletRequestEqualsRequestInRequestContextHolder(); } @@ -67,7 +67,7 @@ public class ServletTestExecutionListenerTestNGIntegrationTests extends Abstract * @see #ensureMocksAreReinjectedBetweenTests_1 */ @Test - public void ensureMocksAreReinjectedBetweenTests_2() { + void ensureMocksAreReinjectedBetweenTests_2() { assertInjectedServletRequestEqualsRequestInRequestContextHolder(); } diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/web/TestNGSpringContextWebTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/web/TestNGSpringContextWebTests.java index 9092c3fe..c14c0f24 100644 --- a/spring-test/src/test/java/org/springframework/test/context/testng/web/TestNGSpringContextWebTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/testng/web/TestNGSpringContextWebTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,34 +52,34 @@ public class TestNGSpringContextWebTests extends AbstractTestNGSpringContextTest static class Config { @Bean - public String foo() { + String foo() { return "enigma"; } } - protected ServletContext servletContext; + ServletContext servletContext; @Autowired - protected WebApplicationContext wac; + WebApplicationContext wac; @Autowired - protected MockServletContext mockServletContext; + MockServletContext mockServletContext; @Autowired - protected MockHttpServletRequest request; + MockHttpServletRequest request; @Autowired - protected MockHttpServletResponse response; + MockHttpServletResponse response; @Autowired - protected MockHttpSession session; + MockHttpSession session; @Autowired - protected ServletWebRequest webRequest; + ServletWebRequest webRequest; @Autowired - protected String foo; + String foo; @Override @@ -88,7 +88,7 @@ public class TestNGSpringContextWebTests extends AbstractTestNGSpringContextTest } @Test - public void basicWacFeatures() throws Exception { + void basicWacFeatures() throws Exception { assertNotNull("ServletContext should be set in the WAC.", wac.getServletContext()); assertNotNull("ServletContext should have been set via ServletContextAware.", servletContext); @@ -112,7 +112,7 @@ public class TestNGSpringContextWebTests extends AbstractTestNGSpringContextTest } @Test - public void fooEnigmaAutowired() { + void fooEnigmaAutowired() { assertEquals("enigma", foo); } diff --git a/spring-test/src/test/java/org/springframework/test/context/transaction/PrimaryTransactionManagerTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/PrimaryTransactionManagerTests.java new file mode 100644 index 00000000..d9cd1526 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/PrimaryTransactionManagerTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2002-2016 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.transaction; + +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.test.transaction.TransactionTestUtils; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Transactional; + +import static org.junit.Assert.*; + +/** + * Integration tests that ensure that <em>primary</em> transaction managers + * are supported. + * + * @author Sam Brannen + * @since 4.3 + * @see org.springframework.test.context.jdbc.PrimaryDataSourceTests + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@DirtiesContext +public class PrimaryTransactionManagerTests { + + @Configuration + static class Config { + + @Primary + @Bean + public PlatformTransactionManager primaryTransactionManager() { + return new DataSourceTransactionManager(dataSource1()); + } + + @Bean + public PlatformTransactionManager additionalTransactionManager() { + return new DataSourceTransactionManager(dataSource2()); + } + + @Bean + public DataSource dataSource1() { + // @formatter:off + return new EmbeddedDatabaseBuilder() + .generateUniqueName(true) + .addScript("classpath:/org/springframework/test/context/jdbc/schema.sql") + .build(); + // @formatter:on + } + + @Bean + public DataSource dataSource2() { + return new EmbeddedDatabaseBuilder().generateUniqueName(true).build(); + } + + } + + + private JdbcTemplate jdbcTemplate; + + + @Autowired + public void setDataSource(DataSource dataSource1) { + this.jdbcTemplate = new JdbcTemplate(dataSource1); + } + + @BeforeTransaction + public void beforeTransaction() { + assertNumUsers(0); + } + + @Test + @Transactional + public void transactionalTest() { + TransactionTestUtils.assertInTransaction(true); + + ClassPathResource resource = new ClassPathResource("/org/springframework/test/context/jdbc/data.sql"); + new ResourceDatabasePopulator(resource).execute(jdbcTemplate.getDataSource()); + + assertNumUsers(1); + } + + @AfterTransaction + public void afterTransaction() { + assertNumUsers(0); + } + + private void assertNumUsers(int expected) { + assertEquals("Number of rows in the 'user' table.", expected, + JdbcTestUtils.countRowsInTable(this.jdbcTemplate, "user")); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java index 8d4e015f..82cb0081 100644 --- a/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.SimpleTransactionStatus; import static org.hamcrest.CoreMatchers.*; - import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; import static org.springframework.transaction.annotation.Propagation.*; @@ -55,6 +54,7 @@ public class TransactionalTestExecutionListenerTests { private final TransactionalTestExecutionListener listener = new TransactionalTestExecutionListener() { + @Override protected PlatformTransactionManager getTransactionManager(TestContext testContext, String qualifier) { return tm; } @@ -82,10 +82,10 @@ public class TransactionalTestExecutionListenerTests { given(testContext.getTestInstance()).willReturn(instance); given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("transactionalTest")); - assertFalse(instance.invoked); + assertFalse("callback should not have been invoked", instance.invoked()); TransactionContextHolder.removeCurrentTransactionContext(); listener.beforeTestMethod(testContext); - assertEquals(invokedInTx, instance.invoked); + assertEquals(invokedInTx, instance.invoked()); } private void assertBeforeTestMethodWithNonTransactionalTestMethod(Class<? extends Invocable> clazz) @@ -95,10 +95,10 @@ public class TransactionalTestExecutionListenerTests { given(testContext.getTestInstance()).willReturn(instance); given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("nonTransactionalTest")); - assertFalse(instance.invoked); + assertFalse("callback should not have been invoked", instance.invoked()); TransactionContextHolder.removeCurrentTransactionContext(); listener.beforeTestMethod(testContext); - assertFalse(instance.invoked); + assertFalse("callback should not have been invoked", instance.invoked()); } private void assertAfterTestMethod(Class<? extends Invocable> clazz) throws Exception { @@ -114,11 +114,12 @@ public class TransactionalTestExecutionListenerTests { given(tm.getTransaction(BDDMockito.any(TransactionDefinition.class))).willReturn(new SimpleTransactionStatus()); - assertFalse(instance.invoked); + assertFalse("callback should not have been invoked", instance.invoked()); TransactionContextHolder.removeCurrentTransactionContext(); listener.beforeTestMethod(testContext); + assertFalse("callback should not have been invoked", instance.invoked()); listener.afterTestMethod(testContext); - assertTrue(instance.invoked); + assertTrue("callback should have been invoked", instance.invoked()); } private void assertAfterTestMethodWithNonTransactionalTestMethod(Class<? extends Invocable> clazz) throws Exception { @@ -127,11 +128,11 @@ public class TransactionalTestExecutionListenerTests { given(testContext.getTestInstance()).willReturn(instance); given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("nonTransactionalTest")); - assertFalse(instance.invoked); + assertFalse("callback should not have been invoked", instance.invoked()); TransactionContextHolder.removeCurrentTransactionContext(); listener.beforeTestMethod(testContext); listener.afterTestMethod(testContext); - assertFalse(instance.invoked); + assertFalse("callback should not have been invoked", instance.invoked()); } private void assertTransactionConfigurationAttributes(Class<?> clazz, String transactionManagerName, @@ -174,7 +175,7 @@ public class TransactionalTestExecutionListenerTests { given(testContext.getTestInstance()).willReturn(instance); given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("transactionalTest")); - assertFalse(instance.invoked); + assertFalse("callback should not have been invoked", instance.invoked()); TransactionContextHolder.removeCurrentTransactionContext(); try { @@ -245,6 +246,16 @@ public class TransactionalTestExecutionListenerTests { } @Test + public void beforeTestMethodWithBeforeTransactionDeclaredAsInterfaceDefaultMethod() throws Exception { + assertBeforeTestMethod(BeforeTransactionDeclaredAsInterfaceDefaultMethodTestCase.class); + } + + @Test + public void afterTestMethodWithAfterTransactionDeclaredAsInterfaceDefaultMethod() throws Exception { + assertAfterTestMethod(AfterTransactionDeclaredAsInterfaceDefaultMethodTestCase.class); + } + + @Test public void retrieveConfigurationAttributesWithMissingTransactionConfiguration() throws Exception { assertTransactionConfigurationAttributes(MissingTransactionConfigurationTestCase.class, "", true); } @@ -344,6 +355,16 @@ public class TransactionalTestExecutionListenerTests { } @Test + public void isRollbackWithClassLevelRollbackWithExplicitValueOnTestInterface() throws Exception { + assertIsRollback(ClassLevelRollbackWithExplicitValueOnTestInterfaceTestCase.class, false); + } + + @Test + public void isRollbackWithClassLevelRollbackViaMetaAnnotationOnTestInterface() throws Exception { + assertIsRollback(ClassLevelRollbackViaMetaAnnotationOnTestInterfaceTestCase.class, false); + } + + @Test public void isRollbackWithRollbackAndTransactionConfigurationDeclaredAtClassLevel() throws Exception { Class<?> clazz = ClassLevelRollbackAndTransactionConfigurationTestCase.class; BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(clazz); @@ -388,17 +409,35 @@ public class TransactionalTestExecutionListenerTests { String transactionManager() default "metaTxMgr"; } - private static abstract class Invocable { + private interface Invocable { + + void invoked(boolean invoked); + + boolean invoked(); + } + + private static class AbstractInvocable implements Invocable { boolean invoked = false; + + + @Override + public void invoked(boolean invoked) { + this.invoked = invoked; + } + + @Override + public boolean invoked() { + return this.invoked; + } } @Transactional - static class TransactionalDeclaredOnClassLocallyTestCase extends Invocable { + static class TransactionalDeclaredOnClassLocallyTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { - invoked = true; + invoked(true); } public void transactionalTest() { @@ -406,11 +445,11 @@ public class TransactionalTestExecutionListenerTests { } } - static class TransactionalDeclaredOnMethodLocallyTestCase extends Invocable { + static class TransactionalDeclaredOnMethodLocallyTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { - invoked = true; + invoked(true); } @Transactional @@ -424,11 +463,11 @@ public class TransactionalTestExecutionListenerTests { } @MetaTransactional - static class TransactionalDeclaredOnClassViaMetaAnnotationTestCase extends Invocable { + static class TransactionalDeclaredOnClassViaMetaAnnotationTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { - invoked = true; + invoked(true); } public void transactionalTest() { @@ -436,11 +475,11 @@ public class TransactionalTestExecutionListenerTests { } } - static class TransactionalDeclaredOnMethodViaMetaAnnotationTestCase extends Invocable { + static class TransactionalDeclaredOnMethodViaMetaAnnotationTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { - invoked = true; + invoked(true); } @MetaTransactional @@ -454,11 +493,11 @@ public class TransactionalTestExecutionListenerTests { } @MetaTxWithOverride(propagation = NOT_SUPPORTED) - static class TransactionalDeclaredOnClassViaMetaAnnotationWithOverrideTestCase extends Invocable { + static class TransactionalDeclaredOnClassViaMetaAnnotationWithOverrideTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { - invoked = true; + invoked(true); } public void transactionalTest() { @@ -466,11 +505,11 @@ public class TransactionalTestExecutionListenerTests { } } - static class TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase extends Invocable { + static class TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { - invoked = true; + invoked(true); } @MetaTxWithOverride(propagation = NOT_SUPPORTED) @@ -483,11 +522,11 @@ public class TransactionalTestExecutionListenerTests { } } - static class BeforeTransactionDeclaredLocallyTestCase extends Invocable { + static class BeforeTransactionDeclaredLocallyTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { - invoked = true; + invoked(true); } @Transactional @@ -500,11 +539,11 @@ public class TransactionalTestExecutionListenerTests { } } - static class BeforeTransactionDeclaredViaMetaAnnotationTestCase extends Invocable { + static class BeforeTransactionDeclaredViaMetaAnnotationTestCase extends AbstractInvocable { @MetaBeforeTransaction public void beforeTransaction() { - invoked = true; + invoked(true); } @Transactional @@ -517,11 +556,11 @@ public class TransactionalTestExecutionListenerTests { } } - static class AfterTransactionDeclaredLocallyTestCase extends Invocable { + static class AfterTransactionDeclaredLocallyTestCase extends AbstractInvocable { @AfterTransaction public void afterTransaction() { - invoked = true; + invoked(true); } @Transactional @@ -534,12 +573,54 @@ public class TransactionalTestExecutionListenerTests { } } - static class AfterTransactionDeclaredViaMetaAnnotationTestCase extends Invocable { + static class AfterTransactionDeclaredViaMetaAnnotationTestCase extends AbstractInvocable { @MetaAfterTransaction public void afterTransaction() { - invoked = true; + invoked(true); + } + + @Transactional + public void transactionalTest() { + /* no-op */ + } + + public void nonTransactionalTest() { + /* no-op */ + } + } + + interface BeforeTransactionInterface extends Invocable { + + @BeforeTransaction + default void beforeTransaction() { + invoked(true); } + } + + interface AfterTransactionInterface extends Invocable { + + @AfterTransaction + default void afterTransaction() { + invoked(true); + } + } + + static class BeforeTransactionDeclaredAsInterfaceDefaultMethodTestCase extends AbstractInvocable + implements BeforeTransactionInterface { + + @Transactional + public void transactionalTest() { + /* no-op */ + } + + public void nonTransactionalTest() { + /* no-op */ + } + } + + static class AfterTransactionDeclaredAsInterfaceDefaultMethodTestCase extends AbstractInvocable + implements AfterTransactionInterface { @Transactional public void transactionalTest() { @@ -642,4 +723,25 @@ public class TransactionalTestExecutionListenerTests { } } + @Rollback(false) + interface RollbackFalseTestInterface { + } + + static class ClassLevelRollbackWithExplicitValueOnTestInterfaceTestCase implements RollbackFalseTestInterface { + + public void test() { + } + } + + @Commit + interface RollbackFalseViaMetaAnnotationTestInterface { + } + + static class ClassLevelRollbackViaMetaAnnotationOnTestInterfaceTestCase + implements RollbackFalseViaMetaAnnotationTestInterface { + + public void test() { + } + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerTests.java index 0353a870..55201245 100644 --- a/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -38,11 +38,12 @@ import static org.springframework.test.context.web.ServletTestExecutionListener. * Unit tests for {@link ServletTestExecutionListener}. * * @author Sam Brannen + * @author Phillip Webb * @since 3.2.6 */ public class ServletTestExecutionListenerTests { - private static final String SET_UP_OUTSIDE_OF_STEL = "SET_UP_OUTSIDE_OF_STEL"; + private static final String SET_UP_OUTSIDE_OF_STEL = "setUpOutsideOfStel"; private final WebApplicationContext wac = mock(WebApplicationContext.class); private final MockServletContext mockServletContext = new MockServletContext(); @@ -50,30 +51,6 @@ public class ServletTestExecutionListenerTests { private final ServletTestExecutionListener listener = new ServletTestExecutionListener(); - private void assertAttributesAvailable() { - assertNotNull("request attributes should be available", RequestContextHolder.getRequestAttributes()); - } - - private void assertAttributesNotAvailable() { - assertNull("request attributes should not be available", RequestContextHolder.getRequestAttributes()); - } - - private void assertAttributeExists() { - RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); - assertNotNull("request attributes should exist", requestAttributes); - Object setUpOutsideOfStel = requestAttributes.getAttribute(SET_UP_OUTSIDE_OF_STEL, - RequestAttributes.SCOPE_REQUEST); - assertNotNull(SET_UP_OUTSIDE_OF_STEL + " should exist as a request attribute", setUpOutsideOfStel); - } - - private void assertAttributeDoesNotExist() { - RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); - assertNotNull("request attributes should exist", requestAttributes); - Object setUpOutsideOfStel = requestAttributes.getAttribute(SET_UP_OUTSIDE_OF_STEL, - RequestAttributes.SCOPE_REQUEST); - assertNull(SET_UP_OUTSIDE_OF_STEL + " should NOT exist as a request attribute", setUpOutsideOfStel); - } - @Before public void setUp() { given(wac.getServletContext()).willReturn(mockServletContext); @@ -86,7 +63,7 @@ public class ServletTestExecutionListenerTests { request.setAttribute(SET_UP_OUTSIDE_OF_STEL, "true"); RequestContextHolder.setRequestAttributes(servletWebRequest); - assertAttributeExists(); + assertSetUpOutsideOfStelAttributeExists(); } @Test @@ -95,16 +72,16 @@ public class ServletTestExecutionListenerTests { given(testContext.getApplicationContext()).willReturn(mock(ApplicationContext.class)); listener.beforeTestClass(testContext); - assertAttributeExists(); + assertSetUpOutsideOfStelAttributeExists(); listener.prepareTestInstance(testContext); - assertAttributeExists(); + assertSetUpOutsideOfStelAttributeExists(); listener.beforeTestMethod(testContext); - assertAttributeExists(); + assertSetUpOutsideOfStelAttributeExists(); listener.afterTestMethod(testContext); - assertAttributeExists(); + assertSetUpOutsideOfStelAttributeExists(); } @Test @@ -112,22 +89,22 @@ public class ServletTestExecutionListenerTests { BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(LegacyWebTestCase.class); RequestContextHolder.resetRequestAttributes(); - assertAttributesNotAvailable(); + assertRequestAttributesDoNotExist(); listener.beforeTestClass(testContext); listener.prepareTestInstance(testContext); - assertAttributesNotAvailable(); + assertRequestAttributesDoNotExist(); verify(testContext, times(0)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE); given(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).willReturn(null); listener.beforeTestMethod(testContext); - assertAttributesNotAvailable(); + assertRequestAttributesDoNotExist(); verify(testContext, times(0)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE); listener.afterTestMethod(testContext); verify(testContext, times(1)).removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE); - assertAttributesNotAvailable(); + assertRequestAttributesDoNotExist(); } @Test @@ -135,21 +112,21 @@ public class ServletTestExecutionListenerTests { BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(LegacyWebTestCase.class); listener.beforeTestClass(testContext); - assertAttributeExists(); + assertSetUpOutsideOfStelAttributeExists(); listener.prepareTestInstance(testContext); - assertAttributeExists(); + assertSetUpOutsideOfStelAttributeExists(); verify(testContext, times(0)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE); given(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).willReturn(null); listener.beforeTestMethod(testContext); - assertAttributeExists(); + assertSetUpOutsideOfStelAttributeExists(); verify(testContext, times(0)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE); given(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).willReturn(null); listener.afterTestMethod(testContext); verify(testContext, times(1)).removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE); - assertAttributeExists(); + assertSetUpOutsideOfStelAttributeExists(); } @Test @@ -158,7 +135,7 @@ public class ServletTestExecutionListenerTests { RequestContextHolder.resetRequestAttributes(); listener.beforeTestClass(testContext); - assertAttributesNotAvailable(); + assertRequestAttributesDoNotExist(); assertWebAppConfigTestCase(); } @@ -168,28 +145,70 @@ public class ServletTestExecutionListenerTests { BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(AtWebAppConfigWebTestCase.class); listener.beforeTestClass(testContext); - assertAttributesAvailable(); + assertRequestAttributesExist(); assertWebAppConfigTestCase(); } + /** + * @since 4.3 + */ + @Test + public void activateListenerWithoutExistingRequestAttributes() throws Exception { + BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(NoAtWebAppConfigWebTestCase.class); + given(testContext.getAttribute(ServletTestExecutionListener.ACTIVATE_LISTENER)).willReturn(true); + + RequestContextHolder.resetRequestAttributes(); + listener.beforeTestClass(testContext); + assertRequestAttributesDoNotExist(); + + assertWebAppConfigTestCase(); + } + + + private RequestAttributes assertRequestAttributesExist() { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + assertNotNull("request attributes should exist", requestAttributes); + return requestAttributes; + } + + private void assertRequestAttributesDoNotExist() { + assertNull("request attributes should not exist", RequestContextHolder.getRequestAttributes()); + } + + private void assertSetUpOutsideOfStelAttributeExists() { + RequestAttributes requestAttributes = assertRequestAttributesExist(); + Object setUpOutsideOfStel = requestAttributes.getAttribute(SET_UP_OUTSIDE_OF_STEL, + RequestAttributes.SCOPE_REQUEST); + assertNotNull(SET_UP_OUTSIDE_OF_STEL + " should exist as a request attribute", setUpOutsideOfStel); + } + + private void assertSetUpOutsideOfStelAttributeDoesNotExist() { + RequestAttributes requestAttributes = assertRequestAttributesExist(); + Object setUpOutsideOfStel = requestAttributes.getAttribute(SET_UP_OUTSIDE_OF_STEL, + RequestAttributes.SCOPE_REQUEST); + assertNull(SET_UP_OUTSIDE_OF_STEL + " should NOT exist as a request attribute", setUpOutsideOfStel); + } + private void assertWebAppConfigTestCase() throws Exception { listener.prepareTestInstance(testContext); - assertAttributeDoesNotExist(); + assertRequestAttributesExist(); + assertSetUpOutsideOfStelAttributeDoesNotExist(); verify(testContext, times(1)).setAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE); verify(testContext, times(1)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE); given(testContext.getAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).willReturn(Boolean.TRUE); given(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).willReturn(Boolean.TRUE); listener.beforeTestMethod(testContext); - assertAttributeDoesNotExist(); + assertRequestAttributesExist(); + assertSetUpOutsideOfStelAttributeDoesNotExist(); verify(testContext, times(1)).setAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE); verify(testContext, times(1)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE); listener.afterTestMethod(testContext); verify(testContext).removeAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE); verify(testContext).removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE); - assertAttributesNotAvailable(); + assertRequestAttributesDoNotExist(); } @@ -200,4 +219,7 @@ public class ServletTestExecutionListenerTests { static class AtWebAppConfigWebTestCase { } + static class NoAtWebAppConfigWebTestCase { + } + } diff --git a/spring-test/src/test/java/org/springframework/test/context/web/WebAppConfigurationBootstrapWithTests.java b/spring-test/src/test/java/org/springframework/test/context/web/WebAppConfigurationBootstrapWithTests.java new file mode 100644 index 00000000..ad03bb81 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/web/WebAppConfigurationBootstrapWithTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2016 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.web; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.test.context.BootstrapWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfigurationBootstrapWithTests.CustomWebTestContextBootstrapper; +import org.springframework.web.context.WebApplicationContext; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * JUnit-based integration tests that verify support for loading a + * {@link WebApplicationContext} with a custom {@link WebTestContextBootstrapper}. + * + * @author Sam Brannen + * @author Phillip Webb + * @since 4.3 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@WebAppConfiguration +@BootstrapWith(CustomWebTestContextBootstrapper.class) +public class WebAppConfigurationBootstrapWithTests { + + @Autowired + WebApplicationContext wac; + + + @Test + public void webApplicationContextIsLoaded() { + // from: src/test/webapp/resources/Spring.js + Resource resource = wac.getResource("/resources/Spring.js"); + assertNotNull(resource); + assertTrue(resource.exists()); + } + + + @Configuration + static class Config { + } + + /** + * Custom {@link WebTestContextBootstrapper} that requires {@code @WebAppConfiguration} + * but hard codes the resource base path. + */ + static class CustomWebTestContextBootstrapper extends WebTestContextBootstrapper { + + @Override + protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) { + return new WebMergedContextConfiguration(mergedConfig, "src/test/webapp"); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/context/web/socket/WebSocketServletServerContainerFactoryBeanTests.java b/spring-test/src/test/java/org/springframework/test/context/web/socket/WebSocketServletServerContainerFactoryBeanTests.java new file mode 100644 index 00000000..f35b87f9 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/web/socket/WebSocketServletServerContainerFactoryBeanTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2016 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.web.socket; + +import javax.websocket.server.ServerContainer; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; + +import static org.junit.Assert.*; + +/** + * Integration tests that validate support for {@link ServletServerContainerFactoryBean} + * in conjunction with {@link WebAppConfiguration @WebAppConfiguration} and the + * Spring TestContext Framework. + * + * @author Sam Brannen + * @since 4.3.1 + */ +@RunWith(SpringRunner.class) +@WebAppConfiguration +public class WebSocketServletServerContainerFactoryBeanTests { + + @Autowired + ServerContainer serverContainer; + + + @Test + public void servletServerContainerFactoryBeanSupport() { + assertEquals(42, serverContainer.getDefaultMaxTextMessageBufferSize()); + } + + + @Configuration + @EnableWebSocket + static class WebSocketConfig { + + @Bean + ServletServerContainerFactoryBean createWebSocketContainer() { + ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); + container.setMaxTextMessageBufferSize(42); + return container; + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java index 03db1d60..3416f215 100644 --- a/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/util/MetaAnnotationUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,10 +25,13 @@ import java.lang.annotation.Target; import org.junit.Test; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; +import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor; import org.springframework.transaction.annotation.Transactional; import static org.junit.Assert.*; @@ -112,10 +115,27 @@ public class MetaAnnotationUtilsTests { @Test public void findAnnotationDescriptorWithInheritedAnnotationOnInterface() throws Exception { // Note: @Transactional is inherited - assertEquals(InheritedAnnotationInterface.class, - findAnnotationDescriptor(InheritedAnnotationInterface.class, Transactional.class).getRootDeclaringClass()); - assertNull(findAnnotationDescriptor(SubInheritedAnnotationInterface.class, Transactional.class)); - assertNull(findAnnotationDescriptor(SubSubInheritedAnnotationInterface.class, Transactional.class)); + Transactional rawAnnotation = InheritedAnnotationInterface.class.getAnnotation(Transactional.class); + + AnnotationDescriptor<Transactional> descriptor; + + descriptor = findAnnotationDescriptor(InheritedAnnotationInterface.class, Transactional.class); + assertNotNull(descriptor); + assertEquals(InheritedAnnotationInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); + + descriptor = findAnnotationDescriptor(SubInheritedAnnotationInterface.class, Transactional.class); + assertNotNull(descriptor); + assertEquals(SubInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); + + descriptor = findAnnotationDescriptor(SubSubInheritedAnnotationInterface.class, Transactional.class); + assertNotNull(descriptor); + assertEquals(SubSubInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); } @Test @@ -130,9 +150,21 @@ public class MetaAnnotationUtilsTests { @Test public void findAnnotationDescriptorForNonInheritedAnnotationOnInterface() throws Exception { // Note: @Order is not inherited. - assertEquals(NonInheritedAnnotationInterface.class, - findAnnotationDescriptor(NonInheritedAnnotationInterface.class, Order.class).getRootDeclaringClass()); - assertNull(findAnnotationDescriptor(SubNonInheritedAnnotationInterface.class, Order.class)); + Order rawAnnotation = NonInheritedAnnotationInterface.class.getAnnotation(Order.class); + + AnnotationDescriptor<Order> descriptor; + + descriptor = findAnnotationDescriptor(NonInheritedAnnotationInterface.class, Order.class); + assertNotNull(descriptor); + assertEquals(NonInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(NonInheritedAnnotationInterface.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); + + descriptor = findAnnotationDescriptor(SubNonInheritedAnnotationInterface.class, Order.class); + assertNotNull(descriptor); + assertEquals(SubNonInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(NonInheritedAnnotationInterface.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); } @Test @@ -158,7 +190,17 @@ public class MetaAnnotationUtilsTests { @Test public void findAnnotationDescriptorForClassWithMetaAnnotatedInterface() { - assertNull(findAnnotationDescriptor(ClassWithMetaAnnotatedInterface.class, Component.class)); + Component rawAnnotation = AnnotationUtils.findAnnotation(ClassWithMetaAnnotatedInterface.class, + Component.class); + + AnnotationDescriptor<Component> descriptor; + + descriptor = findAnnotationDescriptor(ClassWithMetaAnnotatedInterface.class, Component.class); + assertNotNull(descriptor); + assertEquals(ClassWithMetaAnnotatedInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(Meta1.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); + assertEquals(Meta1.class, descriptor.getComposedAnnotation().annotationType()); } @Test @@ -253,11 +295,27 @@ public class MetaAnnotationUtilsTests { @SuppressWarnings("unchecked") public void findAnnotationDescriptorForTypesWithInheritedAnnotationOnInterface() throws Exception { // Note: @Transactional is inherited - assertEquals( - InheritedAnnotationInterface.class, - findAnnotationDescriptorForTypes(InheritedAnnotationInterface.class, Transactional.class).getRootDeclaringClass()); - assertNull(findAnnotationDescriptorForTypes(SubInheritedAnnotationInterface.class, Transactional.class)); - assertNull(findAnnotationDescriptorForTypes(SubSubInheritedAnnotationInterface.class, Transactional.class)); + Transactional rawAnnotation = InheritedAnnotationInterface.class.getAnnotation(Transactional.class); + + UntypedAnnotationDescriptor descriptor; + + descriptor = findAnnotationDescriptorForTypes(InheritedAnnotationInterface.class, Transactional.class); + assertNotNull(descriptor); + assertEquals(InheritedAnnotationInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); + + descriptor = findAnnotationDescriptorForTypes(SubInheritedAnnotationInterface.class, Transactional.class); + assertNotNull(descriptor); + assertEquals(SubInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); + + descriptor = findAnnotationDescriptorForTypes(SubSubInheritedAnnotationInterface.class, Transactional.class); + assertNotNull(descriptor); + assertEquals(SubSubInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(InheritedAnnotationInterface.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); } @Test @@ -274,10 +332,21 @@ public class MetaAnnotationUtilsTests { @SuppressWarnings("unchecked") public void findAnnotationDescriptorForTypesForNonInheritedAnnotationOnInterface() throws Exception { // Note: @Order is not inherited. - assertEquals( - NonInheritedAnnotationInterface.class, - findAnnotationDescriptorForTypes(NonInheritedAnnotationInterface.class, Order.class).getRootDeclaringClass()); - assertNull(findAnnotationDescriptorForTypes(SubNonInheritedAnnotationInterface.class, Order.class)); + Order rawAnnotation = NonInheritedAnnotationInterface.class.getAnnotation(Order.class); + + UntypedAnnotationDescriptor descriptor; + + descriptor = findAnnotationDescriptorForTypes(NonInheritedAnnotationInterface.class, Order.class); + assertNotNull(descriptor); + assertEquals(NonInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(NonInheritedAnnotationInterface.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); + + descriptor = findAnnotationDescriptorForTypes(SubNonInheritedAnnotationInterface.class, Order.class); + assertNotNull(descriptor); + assertEquals(SubNonInheritedAnnotationInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(NonInheritedAnnotationInterface.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); } @Test @@ -345,8 +414,18 @@ public class MetaAnnotationUtilsTests { @Test @SuppressWarnings("unchecked") public void findAnnotationDescriptorForTypesForClassWithMetaAnnotatedInterface() { - assertNull(findAnnotationDescriptorForTypes(ClassWithMetaAnnotatedInterface.class, Service.class, - Component.class, Order.class, Transactional.class)); + Component rawAnnotation = AnnotationUtils.findAnnotation(ClassWithMetaAnnotatedInterface.class, + Component.class); + + UntypedAnnotationDescriptor descriptor; + + descriptor = findAnnotationDescriptorForTypes(ClassWithMetaAnnotatedInterface.class, Service.class, + Component.class, Order.class, Transactional.class); + assertNotNull(descriptor); + assertEquals(ClassWithMetaAnnotatedInterface.class, descriptor.getRootDeclaringClass()); + assertEquals(Meta1.class, descriptor.getDeclaringClass()); + assertEquals(rawAnnotation, descriptor.getAnnotation()); + assertEquals(Meta1.class, descriptor.getComposedAnnotation().annotationType()); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java index fa692ed9..3ea39f9b 100644 --- a/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,12 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.AopUtils; import org.springframework.test.util.subpackage.Component; import org.springframework.test.util.subpackage.LegacyEntity; import org.springframework.test.util.subpackage.Person; +import org.springframework.test.util.subpackage.PersonEntity; import org.springframework.test.util.subpackage.StaticFields; import static org.hamcrest.CoreMatchers.*; @@ -41,12 +44,14 @@ public class ReflectionTestUtilsTests { private static final Float PI = new Float((float) 22 / 7); - private final Person person = new Person(); + private final Person person = new PersonEntity(); private final Component component = new Component(); + private final LegacyEntity entity = new LegacyEntity(); + @Rule - public ExpectedException exception = ExpectedException.none(); + public final ExpectedException exception = ExpectedException.none(); @Before @@ -54,7 +59,6 @@ public class ReflectionTestUtilsTests { StaticFields.reset(); } - @Test public void setFieldWithNullTargetObject() throws Exception { exception.expect(IllegalArgumentException.class); @@ -106,6 +110,29 @@ public class ReflectionTestUtilsTests { @Test public void setFieldAndGetFieldForStandardUseCases() throws Exception { + assertSetFieldAndGetFieldBehavior(this.person); + } + + @Test + public void setFieldAndGetFieldViaJdkDynamicProxy() throws Exception { + ProxyFactory pf = new ProxyFactory(this.person); + pf.addInterface(Person.class); + Person proxy = (Person) pf.getProxy(); + assertTrue("Proxy is a JDK dynamic proxy", AopUtils.isJdkDynamicProxy(proxy)); + assertSetFieldAndGetFieldBehaviorForProxy(proxy, this.person); + } + + @Test + public void setFieldAndGetFieldViaCglibProxy() throws Exception { + ProxyFactory pf = new ProxyFactory(this.person); + pf.setProxyTargetClass(true); + Person proxy = (Person) pf.getProxy(); + assertTrue("Proxy is a CGLIB proxy", AopUtils.isCglibProxy(proxy)); + assertSetFieldAndGetFieldBehaviorForProxy(proxy, this.person); + } + + private static void assertSetFieldAndGetFieldBehavior(Person person) { + // Set reflectively setField(person, "id", new Long(99), long.class); setField(person, "name", "Tom"); setField(person, "age", new Integer(42)); @@ -113,19 +140,33 @@ public class ReflectionTestUtilsTests { setField(person, "likesPets", Boolean.TRUE); setField(person, "favoriteNumber", PI, Number.class); + // Get reflectively + assertEquals(new Long(99), getField(person, "id")); + assertEquals("Tom", getField(person, "name")); + assertEquals(new Integer(42), getField(person, "age")); + assertEquals("blue", getField(person, "eyeColor")); + assertEquals(Boolean.TRUE, getField(person, "likesPets")); + assertEquals(PI, getField(person, "favoriteNumber")); + + // Get directly assertEquals("ID (private field in a superclass)", 99, person.getId()); assertEquals("name (protected field)", "Tom", person.getName()); assertEquals("age (private field)", 42, person.getAge()); assertEquals("eye color (package private field)", "blue", person.getEyeColor()); assertEquals("'likes pets' flag (package private boolean field)", true, person.likesPets()); assertEquals("'favorite number' (package field)", PI, person.getFavoriteNumber()); + } - assertEquals(new Long(99), getField(person, "id")); - assertEquals("Tom", getField(person, "name")); - assertEquals(new Integer(42), getField(person, "age")); - assertEquals("blue", getField(person, "eyeColor")); - assertEquals(Boolean.TRUE, getField(person, "likesPets")); - assertEquals(PI, getField(person, "favoriteNumber")); + private static void assertSetFieldAndGetFieldBehaviorForProxy(Person proxy, Person target) { + assertSetFieldAndGetFieldBehavior(proxy); + + // Get directly from Target + assertEquals("ID (private field in a superclass)", 99, target.getId()); + assertEquals("name (protected field)", "Tom", target.getName()); + assertEquals("age (private field)", 42, target.getAge()); + assertEquals("eye color (package private field)", "blue", target.getEyeColor()); + assertEquals("'likes pets' flag (package private boolean field)", true, target.likesPets()); + assertEquals("'favorite number' (package field)", PI, target.getFavoriteNumber()); } @Test @@ -163,17 +204,6 @@ public class ReflectionTestUtilsTests { setField(person, "likesPets", null, boolean.class); } - /** - * Verifies behavior requested in <a href="https://jira.spring.io/browse/SPR-9571">SPR-9571</a>. - */ - @Test - public void setFieldOnLegacyEntityWithSideEffectsInToString() { - String testCollaborator = "test collaborator"; - LegacyEntity entity = new LegacyEntity(); - setField(entity, "collaborator", testCollaborator, Object.class); - assertTrue(entity.toString().contains(testCollaborator)); - } - @Test public void setStaticFieldViaClass() throws Exception { setField(StaticFields.class, "publicField", "xxx"); @@ -359,4 +389,37 @@ public class ReflectionTestUtilsTests { invokeMethod(component, "configure", new Integer(42), "enigma", "baz", "quux"); } + @Test // SPR-14363 + public void getFieldOnLegacyEntityWithSideEffectsInToString() { + Object collaborator = getField(entity, "collaborator"); + assertNotNull(collaborator); + } + + @Test // SPR-9571 and SPR-14363 + public void setFieldOnLegacyEntityWithSideEffectsInToString() { + String testCollaborator = "test collaborator"; + setField(entity, "collaborator", testCollaborator, Object.class); + assertTrue(entity.toString().contains(testCollaborator)); + } + + @Test // SPR-14363 + public void invokeMethodOnLegacyEntityWithSideEffectsInToString() { + invokeMethod(entity, "configure", new Integer(42), "enigma"); + assertEquals("number should have been configured", new Integer(42), entity.getNumber()); + assertEquals("text should have been configured", "enigma", entity.getText()); + } + + @Test // SPR-14363 + public void invokeGetterMethodOnLegacyEntityWithSideEffectsInToString() { + Object collaborator = invokeGetterMethod(entity, "collaborator"); + assertNotNull(collaborator); + } + + @Test // SPR-14363 + public void invokeSetterMethodOnLegacyEntityWithSideEffectsInToString() { + String testCollaborator = "test collaborator"; + invokeSetterMethod(entity, "collaborator", testCollaborator); + assertTrue(entity.toString().contains(testCollaborator)); + } + } diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntity.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntity.java index 8c8ddfd4..72d4633f 100644 --- a/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntity.java +++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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,17 +31,41 @@ public class LegacyEntity { @Override public String toString() { - throw new RuntimeException( + throw new LegacyEntityException( "Invoking toString() on the default collaborator causes an undesirable side effect"); - }; + } }; + private Integer number; + private String text; + + + public void configure(Integer number, String text) { + this.number = number; + this.text = text; + } + + public Integer getNumber() { + return this.number; + } + + public String getText() { + return this.text; + } + + public Object getCollaborator() { + return this.collaborator; + } + + public void setCollaborator(Object collaborator) { + this.collaborator = collaborator; + } @Override public String toString() { return new ToStringCreator(this)// - .append("collaborator", this.collaborator)// - .toString(); + .append("collaborator", this.collaborator)// + .toString(); } } diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntityException.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntityException.java new file mode 100644 index 00000000..2318e90a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntityException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2016 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.util.subpackage; + +/** + * Exception thrown by a {@link LegacyEntity}. + * + * @author Sam Brannen + * @since 4.3.1 + */ +@SuppressWarnings("serial") +public class LegacyEntityException extends RuntimeException { + + public LegacyEntityException(String message) { + super(message); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/PersistentEntity.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/PersistentEntity.java index 3ff3d91d..4f34e2e8 100644 --- a/spring-test/src/test/java/org/springframework/test/util/subpackage/PersistentEntity.java +++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/PersistentEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2007-2011 the original author or authors. + * Copyright 2007-2016 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. @@ -28,11 +28,12 @@ public abstract class PersistentEntity { private long id; - public final long getId() { + public long getId() { return this.id; } - protected final void setId(long id) { + protected void setId(long id) { this.id = id; } + } diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/Person.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/Person.java index b89f5616..20177ed0 100644 --- a/spring-test/src/test/java/org/springframework/test/util/subpackage/Person.java +++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/Person.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -16,85 +16,27 @@ package org.springframework.test.util.subpackage; -import org.springframework.core.style.ToStringCreator; - /** - * Concrete subclass of {@link PersistentEntity} representing a <em>person</em> - * entity; intended for use in unit tests. + * Interface representing a <em>person</em> entity; intended for use in unit tests. + * + * <p>The introduction of an interface is necessary in order to test support for + * JDK dynamic proxies. * * @author Sam Brannen - * @since 2.5 + * @since 4.3 */ -public class Person extends PersistentEntity { - - protected String name; - - private int age; - - String eyeColor; - - boolean likesPets = false; - - private Number favoriteNumber; - - - public final String getName() { - return this.name; - } - - @SuppressWarnings("unused") - private final void setName(final String name) { - this.name = name; - } - - public final int getAge() { - return this.age; - } - - protected final void setAge(final int age) { - this.age = age; - } - - public final String getEyeColor() { - return this.eyeColor; - } - - final void setEyeColor(final String eyeColor) { - this.eyeColor = eyeColor; - } - - public final boolean likesPets() { - return this.likesPets; - } - - protected final void setLikesPets(final boolean likesPets) { - this.likesPets = likesPets; - } - - public final Number getFavoriteNumber() { - return this.favoriteNumber; - } - - protected final void setFavoriteNumber(Number favoriteNumber) { - this.favoriteNumber = favoriteNumber; - } - - @Override - public String toString() { - return new ToStringCreator(this) +public interface Person { - .append("id", this.getId()) + long getId(); - .append("name", this.name) + String getName(); - .append("age", this.age) + int getAge(); - .append("eyeColor", this.eyeColor) + String getEyeColor(); - .append("likesPets", this.likesPets) + boolean likesPets(); - .append("favoriteNumber", this.favoriteNumber) + Number getFavoriteNumber(); - .toString(); - } } diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/PersonEntity.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/PersonEntity.java new file mode 100644 index 00000000..1cdff47b --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/PersonEntity.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2016 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.util.subpackage; + +import org.springframework.core.style.ToStringCreator; + +/** + * Concrete subclass of {@link PersistentEntity} representing a <em>person</em> + * entity; intended for use in unit tests. + * + * @author Sam Brannen + * @since 2.5 + */ +public class PersonEntity extends PersistentEntity implements Person { + + protected String name; + + private int age; + + String eyeColor; + + boolean likesPets = false; + + private Number favoriteNumber; + + + public String getName() { + return this.name; + } + + @SuppressWarnings("unused") + private void setName(final String name) { + this.name = name; + } + + public int getAge() { + return this.age; + } + + protected void setAge(final int age) { + this.age = age; + } + + public String getEyeColor() { + return this.eyeColor; + } + + void setEyeColor(final String eyeColor) { + this.eyeColor = eyeColor; + } + + public boolean likesPets() { + return this.likesPets; + } + + protected void setLikesPets(final boolean likesPets) { + this.likesPets = likesPets; + } + + public Number getFavoriteNumber() { + return this.favoriteNumber; + } + + protected void setFavoriteNumber(Number favoriteNumber) { + this.favoriteNumber = favoriteNumber; + } + + @Override + public String toString() { + // @formatter:off + return new ToStringCreator(this) + .append("id", this.getId()) + .append("name", this.name) + .append("age", this.age) + .append("eyeColor", this.eyeColor) + .append("likesPets", this.likesPets) + .append("favoriteNumber", this.favoriteNumber) + .toString(); + // @formatter:on + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/client/DefaultRequestExpectationTests.java b/spring-test/src/test/java/org/springframework/test/web/client/DefaultRequestExpectationTests.java new file mode 100644 index 00000000..aea4f2d8 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/client/DefaultRequestExpectationTests.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2016 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.web.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.mock.http.client.MockAsyncClientHttpRequest; + +import static junit.framework.TestCase.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.test.web.client.ExpectedCount.once; +import static org.springframework.test.web.client.ExpectedCount.times; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Unit tests for {@link DefaultRequestExpectation}. + * @author Rossen Stoyanchev + */ +public class DefaultRequestExpectationTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + + @Test + public void match() throws Exception { + RequestExpectation expectation = new DefaultRequestExpectation(once(), requestTo("/foo")); + expectation.match(createRequest(GET, "/foo")); + } + + @Test + public void matchWithFailedExpection() throws Exception { + RequestExpectation expectation = new DefaultRequestExpectation(once(), requestTo("/foo")); + expectation.andExpect(method(POST)); + + this.thrown.expectMessage("Unexpected HttpMethod expected:<POST> but was:<GET>"); + expectation.match(createRequest(GET, "/foo")); + } + + @Test + public void hasRemainingCount() throws Exception { + RequestExpectation expectation = new DefaultRequestExpectation(times(2), requestTo("/foo")); + expectation.andRespond(withSuccess()); + + expectation.createResponse(createRequest(GET, "/foo")); + assertTrue(expectation.hasRemainingCount()); + + expectation.createResponse(createRequest(GET, "/foo")); + assertFalse(expectation.hasRemainingCount()); + } + + @Test + public void isSatisfied() throws Exception { + RequestExpectation expectation = new DefaultRequestExpectation(times(2), requestTo("/foo")); + expectation.andRespond(withSuccess()); + + expectation.createResponse(createRequest(GET, "/foo")); + assertFalse(expectation.isSatisfied()); + + expectation.createResponse(createRequest(GET, "/foo")); + assertTrue(expectation.isSatisfied()); + } + + + + private ClientHttpRequest createRequest(HttpMethod method, String url) { + try { + return new MockAsyncClientHttpRequest(method, new URI(url)); + } + catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/client/MockClientHttpRequestFactoryTests.java b/spring-test/src/test/java/org/springframework/test/web/client/MockClientHttpRequestFactoryTests.java deleted file mode 100644 index dfd3a714..00000000 --- a/spring-test/src/test/java/org/springframework/test/web/client/MockClientHttpRequestFactoryTests.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2002-2014 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.web.client; - -import java.net.URI; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.web.client.RestTemplate; - -import static org.junit.Assert.*; -import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; - -/** - * Tests for - * {@link org.springframework.test.web.client.MockMvcClientHttpRequestFactory}. - * - * @author Rossen Stoyanchev - */ -public class MockClientHttpRequestFactoryTests { - - private MockRestServiceServer server; - - private ClientHttpRequestFactory factory; - - - @Before - public void setup() { - RestTemplate restTemplate = new RestTemplate(); - this.server = MockRestServiceServer.createServer(restTemplate); - this.factory = restTemplate.getRequestFactory(); - } - - @Test - public void createRequest() throws Exception { - URI uri = new URI("/foo"); - ClientHttpRequest expected = (ClientHttpRequest) this.server.expect(anything()); - ClientHttpRequest actual = this.factory.createRequest(uri, HttpMethod.GET); - - assertSame(expected, actual); - assertEquals(uri, actual.getURI()); - assertEquals(HttpMethod.GET, actual.getMethod()); - } - - @Test - public void noFurtherRequestsExpected() throws Exception { - try { - this.factory.createRequest(new URI("/foo"), HttpMethod.GET); - } - catch (AssertionError error) { - assertEquals("No further requests expected: HTTP GET /foo", error.getMessage()); - } - } - - @Test - public void verifyZeroExpected() throws Exception { - this.server.verify(); - } - - @Test - public void verifyExpectedEqualExecuted() throws Exception { - this.server.expect(anything()); - this.server.expect(anything()); - - this.factory.createRequest(new URI("/foo"), HttpMethod.GET); - this.factory.createRequest(new URI("/bar"), HttpMethod.POST); - } - - @Test - public void verifyMoreExpected() throws Exception { - this.server.expect(anything()); - this.server.expect(anything()); - - this.factory.createRequest(new URI("/foo"), HttpMethod.GET); - - try { - this.server.verify(); - } - catch (AssertionError error) { - assertTrue(error.getMessage(), error.getMessage().contains("1 out of 2 were executed")); - } - } - -} diff --git a/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java b/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java new file mode 100644 index 00000000..ddf8b26f --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/client/MockRestServiceServerTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2016 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.web.client; + +import org.junit.Test; + +import org.springframework.test.web.client.MockRestServiceServer.MockRestServiceServerBuilder; +import org.springframework.web.client.RestTemplate; + +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Unit tests for {@link MockRestServiceServer}. + * @author Rossen Stoyanchev + */ +public class MockRestServiceServerTests { + + private RestTemplate restTemplate = new RestTemplate(); + + + @Test + public void buildMultipleTimes() throws Exception { + MockRestServiceServerBuilder builder = MockRestServiceServer.bindTo(this.restTemplate); + + MockRestServiceServer server = builder.build(); + server.expect(requestTo("/foo")).andRespond(withSuccess()); + this.restTemplate.getForObject("/foo", Void.class); + server.verify(); + + server = builder.ignoreExpectOrder(true).build(); + server.expect(requestTo("/foo")).andRespond(withSuccess()); + server.expect(requestTo("/bar")).andRespond(withSuccess()); + this.restTemplate.getForObject("/bar", Void.class); + this.restTemplate.getForObject("/foo", Void.class); + server.verify(); + + server = builder.build(); + server.expect(requestTo("/bar")).andRespond(withSuccess()); + this.restTemplate.getForObject("/bar", Void.class); + server.verify(); + } + + @Test(expected = AssertionError.class) + public void exactExpectOrder() throws Exception { + MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate) + .ignoreExpectOrder(false).build(); + + server.expect(requestTo("/foo")).andRespond(withSuccess()); + server.expect(requestTo("/bar")).andRespond(withSuccess()); + this.restTemplate.getForObject("/bar", Void.class); + } + + @Test + public void ignoreExpectOrder() throws Exception { + MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate) + .ignoreExpectOrder(true).build(); + + server.expect(requestTo("/foo")).andRespond(withSuccess()); + server.expect(requestTo("/bar")).andRespond(withSuccess()); + this.restTemplate.getForObject("/bar", Void.class); + this.restTemplate.getForObject("/foo", Void.class); + server.verify(); + } + + @Test + public void resetAndReuseServer() throws Exception { + MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate).build(); + + server.expect(requestTo("/foo")).andRespond(withSuccess()); + this.restTemplate.getForObject("/foo", Void.class); + server.verify(); + server.reset(); + + server.expect(requestTo("/bar")).andRespond(withSuccess()); + this.restTemplate.getForObject("/bar", Void.class); + server.verify(); + } + + @Test + public void resetAndReuseServerWithUnorderedExpectationManager() throws Exception { + MockRestServiceServer server = MockRestServiceServer.bindTo(this.restTemplate) + .ignoreExpectOrder(true).build(); + + server.expect(requestTo("/foo")).andRespond(withSuccess()); + this.restTemplate.getForObject("/foo", Void.class); + server.verify(); + server.reset(); + + server.expect(requestTo("/foo")).andRespond(withSuccess()); + server.expect(requestTo("/bar")).andRespond(withSuccess()); + this.restTemplate.getForObject("/bar", Void.class); + this.restTemplate.getForObject("/foo", Void.class); + server.verify(); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/client/SimpleRequestExpectationManagerTests.java b/spring-test/src/test/java/org/springframework/test/web/client/SimpleRequestExpectationManagerTests.java new file mode 100644 index 00000000..d6f2b9cb --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/client/SimpleRequestExpectationManagerTests.java @@ -0,0 +1,173 @@ +/* + * Copyright 2002-2016 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.web.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.mock.http.client.MockAsyncClientHttpRequest; + +import static org.junit.Assert.assertEquals; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.test.web.client.ExpectedCount.max; +import static org.springframework.test.web.client.ExpectedCount.min; +import static org.springframework.test.web.client.ExpectedCount.once; +import static org.springframework.test.web.client.ExpectedCount.times; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Unit tests for {@link SimpleRequestExpectationManager}. + * @author Rossen Stoyanchev + */ +public class SimpleRequestExpectationManagerTests { + + private SimpleRequestExpectationManager manager = new SimpleRequestExpectationManager(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + + @Test + public void unexpectedRequest() throws Exception { + try { + this.manager.validateRequest(createRequest(GET, "/foo")); + } + catch (AssertionError error) { + assertEquals("No further requests expected: HTTP GET /foo\n" + + "0 request(s) executed.\n", error.getMessage()); + } + } + + @Test + public void zeroExpectedRequests() throws Exception { + this.manager.verify(); + } + + @Test + public void sequentialRequests() throws Exception { + this.manager.expectRequest(once(), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(once(), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); + + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.verify(); + } + + @Test + public void sequentialRequestsTooMany() throws Exception { + this.manager.expectRequest(max(1), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(max(1), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); + + this.thrown.expectMessage("No further requests expected: HTTP GET /baz\n" + + "2 request(s) executed:\n" + + "GET /foo\n" + + "GET /bar\n"); + + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.validateRequest(createRequest(GET, "/baz")); + } + + @Test + public void sequentialRequestsTooFew() throws Exception { + this.manager.expectRequest(min(1), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(min(1), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); + + this.thrown.expectMessage("Further request(s) expected leaving 1 unsatisfied expectation(s).\n" + + "1 request(s) executed:\nGET /foo\n"); + + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.verify(); + } + + @Test + public void repeatedRequests() throws Exception { + this.manager.expectRequest(times(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(times(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); + + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.verify(); + } + + @Test + public void repeatedRequestsTooMany() throws Exception { + this.manager.expectRequest(max(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(max(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); + + this.thrown.expectMessage("No further requests expected: HTTP GET /foo\n" + + "4 request(s) executed:\n" + + "GET /foo\n" + + "GET /bar\n" + + "GET /foo\n" + + "GET /bar\n"); + + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.validateRequest(createRequest(GET, "/foo")); + } + + @Test + public void repeatedRequestsTooFew() throws Exception { + this.manager.expectRequest(min(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(min(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); + + this.thrown.expectMessage("3 request(s) executed:\n" + + "GET /foo\n" + + "GET /bar\n" + + "GET /foo\n"); + + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.verify(); + } + + @Test + public void repeatedRequestsNotInOrder() throws Exception { + this.manager.expectRequest(times(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(times(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(times(2), requestTo("/baz")).andExpect(method(GET)).andRespond(withSuccess()); + + this.thrown.expectMessage("Unexpected HttpMethod expected:<GET> but was:<POST>"); + this.manager.validateRequest(createRequest(POST, "/foo")); + } + + + private ClientHttpRequest createRequest(HttpMethod method, String url) { + try { + return new MockAsyncClientHttpRequest(method, new URI(url)); + } + catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/client/UnorderedRequestExpectationManagerTests.java b/spring-test/src/test/java/org/springframework/test/web/client/UnorderedRequestExpectationManagerTests.java new file mode 100644 index 00000000..163f3865 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/client/UnorderedRequestExpectationManagerTests.java @@ -0,0 +1,133 @@ +/* + * Copyright 2002-2016 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.web.client; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.mock.http.client.MockAsyncClientHttpRequest; + +import static org.junit.Assert.assertEquals; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.test.web.client.ExpectedCount.max; +import static org.springframework.test.web.client.ExpectedCount.min; +import static org.springframework.test.web.client.ExpectedCount.once; +import static org.springframework.test.web.client.ExpectedCount.times; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +/** + * Unit tests for {@link UnorderedRequestExpectationManager}. + * @author Rossen Stoyanchev + */ +public class UnorderedRequestExpectationManagerTests { + + private UnorderedRequestExpectationManager manager = new UnorderedRequestExpectationManager(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + + @Test + public void unexpectedRequest() throws Exception { + try { + this.manager.validateRequest(createRequest(GET, "/foo")); + } + catch (AssertionError error) { + assertEquals("No further requests expected: HTTP GET /foo\n" + + "0 request(s) executed.\n", error.getMessage()); + } + } + + @Test + public void zeroExpectedRequests() throws Exception { + this.manager.verify(); + } + + @Test + public void multipleRequests() throws Exception { + this.manager.expectRequest(once(), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(once(), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); + + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.verify(); + } + + @Test + public void repeatedRequests() throws Exception { + this.manager.expectRequest(times(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(times(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); + + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.verify(); + } + + @Test + public void repeatedRequestsTooMany() throws Exception { + this.manager.expectRequest(max(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(max(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); + + this.thrown.expectMessage("No further requests expected: HTTP GET /foo\n" + + "4 request(s) executed:\n" + + "GET /bar\n" + + "GET /foo\n" + + "GET /bar\n" + + "GET /foo\n"); + + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/foo")); + } + + @Test + public void repeatedRequestsTooFew() throws Exception { + this.manager.expectRequest(min(2), requestTo("/foo")).andExpect(method(GET)).andRespond(withSuccess()); + this.manager.expectRequest(min(2), requestTo("/bar")).andExpect(method(GET)).andRespond(withSuccess()); + + this.thrown.expectMessage("3 request(s) executed:\n" + + "GET /bar\n" + + "GET /foo\n" + + "GET /foo\n"); + + this.manager.validateRequest(createRequest(GET, "/bar")); + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.validateRequest(createRequest(GET, "/foo")); + this.manager.verify(); + } + + + private ClientHttpRequest createRequest(HttpMethod method, String url) { + try { + return new MockAsyncClientHttpRequest(method, new URI(url)); + } + catch (URISyntaxException ex) { + throw new IllegalStateException(ex); + } + } +} diff --git a/spring-test/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java index eada001d..de034c24 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -15,11 +15,15 @@ */ package org.springframework.test.web.client.match; +import java.nio.charset.Charset; + import org.junit.Before; import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import static org.hamcrest.Matchers.*; @@ -32,11 +36,13 @@ public class ContentRequestMatchersTests { private MockClientHttpRequest request; + @Before public void setUp() { this.request = new MockClientHttpRequest(); } + @Test public void testContentType() throws Exception { this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON); @@ -45,14 +51,14 @@ public class ContentRequestMatchersTests { MockRestRequestMatchers.content().contentType(MediaType.APPLICATION_JSON).match(this.request); } - @Test(expected=AssertionError.class) + @Test(expected = AssertionError.class) public void testContentTypeNoMatch1() throws Exception { this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON); MockRestRequestMatchers.content().contentType("application/xml").match(this.request); } - @Test(expected=AssertionError.class) + @Test(expected = AssertionError.class) public void testContentTypeNoMatch2() throws Exception { this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON); @@ -66,7 +72,7 @@ public class ContentRequestMatchersTests { MockRestRequestMatchers.content().string("test").match(this.request); } - @Test(expected=AssertionError.class) + @Test(expected = AssertionError.class) public void testStringNoMatch() throws Exception { this.request.getBody().write("test".getBytes()); @@ -81,7 +87,7 @@ public class ContentRequestMatchersTests { MockRestRequestMatchers.content().bytes(content).match(this.request); } - @Test(expected=AssertionError.class) + @Test(expected = AssertionError.class) public void testBytesNoMatch() throws Exception { this.request.getBody().write("test".getBytes()); @@ -89,6 +95,22 @@ public class ContentRequestMatchersTests { } @Test + public void testFormData() throws Exception { + String contentType = "application/x-www-form-urlencoded;charset=UTF-8"; + String body = "name+1=value+1&name+2=value+A&name+2=value+B&name+3"; + + this.request.getHeaders().setContentType(MediaType.parseMediaType(contentType)); + this.request.getBody().write(body.getBytes(Charset.forName("UTF-8"))); + + MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); + map.add("name 1", "value 1"); + map.add("name 2", "value A"); + map.add("name 2", "value B"); + map.add("name 3", null); + MockRestRequestMatchers.content().formData(map).match(this.request); + } + + @Test public void testXml() throws Exception { String content = "<foo><bar>baz</bar><bar>bazz</bar></foo>"; this.request.getBody().write(content.getBytes()); @@ -96,7 +118,7 @@ public class ContentRequestMatchersTests { MockRestRequestMatchers.content().xml(content).match(this.request); } - @Test(expected=AssertionError.class) + @Test(expected = AssertionError.class) public void testXmlNoMatch() throws Exception { this.request.getBody().write("<foo>11</foo>".getBytes()); @@ -111,7 +133,7 @@ public class ContentRequestMatchersTests { MockRestRequestMatchers.content().node(hasXPath("/foo/bar")).match(this.request); } - @Test(expected=AssertionError.class) + @Test(expected = AssertionError.class) public void testNodeMatcherNoMatch() throws Exception { String content = "<foo><bar>baz</bar></foo>"; this.request.getBody().write(content.getBytes()); diff --git a/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java index 787020d6..58cad79c 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/match/MockRestRequestMatchersTests.java @@ -91,7 +91,6 @@ public class MockRestRequestMatchersTests { MockRestRequestMatchers.header("foo", "bad").match(this.request); } - @SuppressWarnings("unchecked") @Test public void headerContains() throws Exception { this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); @@ -99,13 +98,11 @@ public class MockRestRequestMatchersTests { MockRestRequestMatchers.header("foo", containsString("ba")).match(this.request); } - @SuppressWarnings("unchecked") @Test(expected = AssertionError.class) public void headerContainsWithMissingHeader() throws Exception { MockRestRequestMatchers.header("foo", containsString("baz")).match(this.request); } - @SuppressWarnings("unchecked") @Test(expected = AssertionError.class) public void headerContainsWithMissingValue() throws Exception { this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); diff --git a/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleAsyncTests.java b/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleAsyncTests.java index 73b17cb5..091b64d5 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleAsyncTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleAsyncTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.test.web.client.samples; -import org.junit.Before; import org.junit.Test; import org.springframework.core.io.ClassPathResource; @@ -29,6 +29,7 @@ import org.springframework.util.concurrent.ListenableFuture; import org.springframework.web.client.AsyncRestTemplate; import static org.junit.Assert.*; +import static org.springframework.test.web.client.ExpectedCount.manyTimes; import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; import static org.springframework.test.web.client.response.MockRestResponseCreators.*; @@ -39,20 +40,14 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat * code. * * @author Rossen Stoyanchev + * @since 4.1 */ public class SampleAsyncTests { - private MockRestServiceServer mockServer; - - private AsyncRestTemplate restTemplate; + private final AsyncRestTemplate restTemplate = new AsyncRestTemplate(); + private final MockRestServiceServer mockServer = MockRestServiceServer.createServer(this.restTemplate); - @Before - public void setup() { - this.restTemplate = new AsyncRestTemplate(); - this.mockServer = MockRestServiceServer.createServer(this.restTemplate); - - } @Test public void performGet() throws Exception { @@ -63,7 +58,8 @@ public class SampleAsyncTests { .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); @SuppressWarnings("unused") - ListenableFuture<ResponseEntity<Person>> ludwig = restTemplate.getForEntity("/composers/{id}", Person.class, 42); + ListenableFuture<ResponseEntity<Person>> ludwig = + this.restTemplate.getForEntity("/composers/{id}", Person.class, 42); // We are only validating the request. The response is mocked out. // person.getName().equals("Ludwig van Beethoven") @@ -73,19 +69,26 @@ public class SampleAsyncTests { } @Test - public void performGetAsync() throws Exception { + public void performGetManyTimes() throws Exception { String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}"; - this.mockServer.expect(requestTo("/composers/42")).andExpect(method(HttpMethod.GET)) + this.mockServer.expect(manyTimes(), requestTo("/composers/42")).andExpect(method(HttpMethod.GET)) .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); @SuppressWarnings("unused") - ListenableFuture<ResponseEntity<Person>> ludwig = restTemplate.getForEntity("/composers/{id}", Person.class, 42); + ListenableFuture<ResponseEntity<Person>> ludwig = + this.restTemplate.getForEntity("/composers/{id}", Person.class, 42); + // We are only validating the request. The response is mocked out. // person.getName().equals("Ludwig van Beethoven") // person.getDouble().equals(1.6035) + this.restTemplate.getForEntity("/composers/{id}", Person.class, 42); + this.restTemplate.getForEntity("/composers/{id}", Person.class, 42); + this.restTemplate.getForEntity("/composers/{id}", Person.class, 42); + this.restTemplate.getForEntity("/composers/{id}", Person.class, 42); + this.mockServer.verify(); } @@ -98,7 +101,8 @@ public class SampleAsyncTests { .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); @SuppressWarnings("unused") - ListenableFuture<ResponseEntity<Person>> ludwig = restTemplate.getForEntity("/composers/{id}", Person.class, 42); + ListenableFuture<ResponseEntity<Person>> ludwig = + this.restTemplate.getForEntity("/composers/{id}", Person.class, 42); // hotel.getId() == 42 // hotel.getName().equals("Holiday Inn") @@ -132,7 +136,8 @@ public class SampleAsyncTests { this.mockServer.verify(); } catch (AssertionError error) { - assertTrue(error.getMessage(), error.getMessage().contains("2 out of 4 were executed")); + assertTrue(error.getMessage(), error.getMessage().contains("2 unsatisfied expectation(s)")); } } + } diff --git a/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleTests.java b/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleTests.java index d3fc4823..6d5c7452 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/samples/SampleTests.java @@ -27,6 +27,7 @@ import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestTemplate; import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.client.ExpectedCount.manyTimes; import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; @@ -48,7 +49,7 @@ public class SampleTests { @Before public void setup() { this.restTemplate = new RestTemplate(); - this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + this.mockServer = MockRestServiceServer.bindTo(this.restTemplate).ignoreExpectOrder(true).build(); } @Test @@ -60,7 +61,7 @@ public class SampleTests { .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); @SuppressWarnings("unused") - Person ludwig = restTemplate.getForObject("/composers/{id}", Person.class, 42); + Person ludwig = this.restTemplate.getForObject("/composers/{id}", Person.class, 42); // We are only validating the request. The response is mocked out. // hotel.getId() == 42 @@ -70,6 +71,28 @@ public class SampleTests { } @Test + public void performGetManyTimes() throws Exception { + + String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}"; + + this.mockServer.expect(manyTimes(), requestTo("/composers/42")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); + + @SuppressWarnings("unused") + Person ludwig = this.restTemplate.getForObject("/composers/{id}", Person.class, 42); + + // We are only validating the request. The response is mocked out. + // hotel.getId() == 42 + // hotel.getName().equals("Holiday Inn") + + this.restTemplate.getForObject("/composers/{id}", Person.class, 42); + this.restTemplate.getForObject("/composers/{id}", Person.class, 42); + this.restTemplate.getForObject("/composers/{id}", Person.class, 42); + + this.mockServer.verify(); + } + + @Test public void performGetWithResponseBodyFromFile() throws Exception { Resource responseBody = new ClassPathResource("ludwig.json", this.getClass()); @@ -78,7 +101,7 @@ public class SampleTests { .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); @SuppressWarnings("unused") - Person ludwig = restTemplate.getForObject("/composers/{id}", Person.class, 42); + Person ludwig = this.restTemplate.getForObject("/composers/{id}", Person.class, 42); // hotel.getId() == 42 // hotel.getName().equals("Holiday Inn") @@ -102,17 +125,18 @@ public class SampleTests { .andRespond(withSuccess("8", MediaType.TEXT_PLAIN)); @SuppressWarnings("unused") - String result = this.restTemplate.getForObject("/number", String.class); - // result == "1" + String result1 = this.restTemplate.getForObject("/number", String.class); + // result1 == "1" - result = this.restTemplate.getForObject("/number", String.class); + @SuppressWarnings("unused") + String result2 = this.restTemplate.getForObject("/number", String.class); // result == "2" try { this.mockServer.verify(); } catch (AssertionError error) { - assertTrue(error.getMessage(), error.getMessage().contains("2 out of 4 were executed")); + assertTrue(error.getMessage(), error.getMessage().contains("2 unsatisfied expectation(s)")); } } } diff --git a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java index 7ffb5eb9..1b56ffa2 100644 --- a/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatchersIntegrationTests.java @@ -71,7 +71,6 @@ public class HeaderRequestMatchersIntegrationTests { this.mockServer.verify(); } - @SuppressWarnings("unchecked") @Test public void testStringContains() throws Exception { diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java index beab7668..bf347eed 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java @@ -129,8 +129,7 @@ public class DelegatingWebConnectionTests { WebClient webClient = new WebClient(); MockMvc mockMvc = MockMvcBuilders.standaloneSetup(TestController.class).build(); - MockMvcWebConnection mockConnection = new MockMvcWebConnection(mockMvc); - mockConnection.setWebClient(webClient); + MockMvcWebConnection mockConnection = new MockMvcWebConnection(mockMvc, webClient); WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*"); WebConnection httpConnection = new HttpWebConnection(webClient); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java index ddf68306..4c7f9cb5 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2015 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. @@ -16,7 +16,6 @@ package org.springframework.test.web.servlet.htmlunit; -import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; @@ -28,11 +27,9 @@ import javax.servlet.ServletContext; import javax.servlet.http.Cookie; import javax.servlet.http.HttpSession; -import com.gargoylesoftware.htmlunit.HttpMethod; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.WebRequest; -import com.gargoylesoftware.htmlunit.util.NameValuePair; +import org.apache.commons.io.IOUtils; import org.apache.http.auth.UsernamePasswordCredentials; + import org.junit.Before; import org.junit.Test; @@ -42,12 +39,16 @@ import org.springframework.mock.web.MockServletContext; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.util.FileCopyUtils; -import static java.util.Arrays.*; +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.util.NameValuePair; + +import static java.util.Arrays.asList; import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.junit.Assert.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; /** * Unit tests for {@link HtmlUnitRequestBuilder}. @@ -76,6 +77,7 @@ public class HtmlUnitRequestBuilderTests { requestBuilder = new HtmlUnitRequestBuilder(sessions, webClient, webRequest); } + // --- constructor @Test(expected = IllegalArgumentException.class) public void constructorNullSessions() { @@ -92,6 +94,8 @@ public class HtmlUnitRequestBuilderTests { new HtmlUnitRequestBuilder(sessions, webClient, null); } + // --- buildRequest + @Test @SuppressWarnings("deprecation") public void buildRequestBasicAuth() { @@ -241,8 +245,7 @@ public class HtmlUnitRequestBuilderTests { MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext); - assertThat(FileCopyUtils.copyToString(new InputStreamReader(actualRequest.getInputStream(), "ISO-8859-1")), - equalTo(content)); + assertThat(IOUtils.toString(actualRequest.getInputStream()), equalTo(content)); } @Test @@ -408,7 +411,8 @@ public class HtmlUnitRequestBuilderTests { assertThat(actualRequest.getParameter("name"), equalTo("value")); } - @Test // SPR-14177 + // SPR-14177 + @Test public void buildRequestParameterMapDecodesParameterName() throws Exception { webRequest.setUrl(new URL("http://example.com/example/?row%5B0%5D=value")); @@ -561,7 +565,7 @@ public class HtmlUnitRequestBuilderTests { MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext); - assertThat(FileCopyUtils.copyToString(actualRequest.getReader()), equalTo(expectedBody)); + assertThat(IOUtils.toString(actualRequest.getReader()), equalTo(expectedBody)); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java index c89f4f40..303f5af9 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,11 @@ import java.io.IOException; import java.net.URL; import javax.servlet.http.HttpServletRequest; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebConnection; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,20 +41,18 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import com.gargoylesoftware.htmlunit.WebConnection; -import com.gargoylesoftware.htmlunit.WebRequest; -import com.gargoylesoftware.htmlunit.WebResponse; - import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; /** * Integration tests for {@link MockMvcWebConnectionBuilderSupport}. * * @author Rob Winch + * @author Rossen Stoyanchev * @since 4.2 */ @RunWith(SpringJUnit4ClassRunner.class) @@ -58,93 +61,85 @@ import static org.mockito.Mockito.mock; @SuppressWarnings("rawtypes") public class MockMvcConnectionBuilderSupportTests { - private final WebConnection delegateConnection = mock(WebConnection.class); + private final WebClient client = mock(WebClient.class); + + private MockMvcWebConnectionBuilderSupport builder; @Autowired private WebApplicationContext wac; - private MockMvc mockMvc; - - private WebConnection connection; @Before public void setup() { - mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); - - connection = new MockMvcWebConnectionBuilderSupport(mockMvc){} - .createConnection(delegateConnection); + when(this.client.getWebConnection()).thenReturn(mock(WebConnection.class)); + this.builder = new MockMvcWebConnectionBuilderSupport(this.wac) {}; } + @Test(expected = IllegalArgumentException.class) public void constructorMockMvcNull() { - new MockMvcWebConnectionBuilderSupport((MockMvc)null){}; + new MockMvcWebConnectionBuilderSupport((MockMvc) null){}; } @Test(expected = IllegalArgumentException.class) public void constructorContextNull() { - new MockMvcWebConnectionBuilderSupport((WebApplicationContext)null){}; + new MockMvcWebConnectionBuilderSupport((WebApplicationContext) null){}; } @Test public void context() throws Exception { - connection = new MockMvcWebConnectionBuilderSupport(wac) {} - .createConnection(delegateConnection); + WebConnection conn = this.builder.createConnection(this.client); - assertMvcProcessed("http://localhost/"); - assertDelegateProcessed("http://example.com/"); + assertMockMvcUsed(conn, "http://localhost/"); + assertMockMvcNotUsed(conn, "http://example.com/"); } @Test public void mockMvc() throws Exception { - assertMvcProcessed("http://localhost/"); - assertDelegateProcessed("http://example.com/"); + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); + WebConnection conn = new MockMvcWebConnectionBuilderSupport(mockMvc) {}.createConnection(this.client); + + assertMockMvcUsed(conn, "http://localhost/"); + assertMockMvcNotUsed(conn, "http://example.com/"); } @Test public void mockMvcExampleDotCom() throws Exception { - connection = new MockMvcWebConnectionBuilderSupport(wac) {} - .useMockMvcForHosts("example.com") - .createConnection(delegateConnection); + WebConnection conn = this.builder.useMockMvcForHosts("example.com").createConnection(this.client); - assertMvcProcessed("http://localhost/"); - assertMvcProcessed("http://example.com/"); - assertDelegateProcessed("http://other.com/"); + assertMockMvcUsed(conn, "http://localhost/"); + assertMockMvcUsed(conn, "http://example.com/"); + assertMockMvcNotUsed(conn, "http://other.com/"); } @Test public void mockMvcAlwaysUseMockMvc() throws Exception { - connection = new MockMvcWebConnectionBuilderSupport(wac) {} - .alwaysUseMockMvc() - .createConnection(delegateConnection); - - assertMvcProcessed("http://other.com/"); + WebConnection conn = this.builder.alwaysUseMockMvc().createConnection(this.client); + assertMockMvcUsed(conn, "http://other.com/"); } @Test public void defaultContextPathEmpty() throws Exception { - connection = new MockMvcWebConnectionBuilderSupport(wac) {} - .createConnection(delegateConnection); - - assertThat(getWebResponse("http://localhost/abc").getContentAsString(), equalTo("")); + WebConnection conn = this.builder.createConnection(this.client); + assertThat(getResponse(conn, "http://localhost/abc").getContentAsString(), equalTo("")); } @Test public void defaultContextPathCustom() throws Exception { - connection = new MockMvcWebConnectionBuilderSupport(wac) {} - .contextPath("/abc").createConnection(delegateConnection); - - assertThat(getWebResponse("http://localhost/abc/def").getContentAsString(), equalTo("/abc")); + WebConnection conn = this.builder.contextPath("/abc").createConnection(this.client); + assertThat(getResponse(conn, "http://localhost/abc/def").getContentAsString(), equalTo("/abc")); } - private void assertMvcProcessed(String url) throws Exception { - assertThat(getWebResponse(url), notNullValue()); + + private void assertMockMvcUsed(WebConnection connection, String url) throws Exception { + assertThat(getResponse(connection, url), notNullValue()); } - private void assertDelegateProcessed(String url) throws Exception { - assertThat(getWebResponse(url), nullValue()); + private void assertMockMvcNotUsed(WebConnection connection, String url) throws Exception { + assertThat(getResponse(connection, url), nullValue()); } - private WebResponse getWebResponse(String url) throws IOException { + private WebResponse getResponse(WebConnection connection, String url) throws IOException { return connection.getResponse(new WebRequest(new URL(url))); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java index a7473c18..79a6af18 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java @@ -19,10 +19,13 @@ package org.springframework.test.web.servlet.htmlunit; import java.io.IOException; import java.net.URL; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import com.gargoylesoftware.htmlunit.HttpMethod; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.WebRequest; import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.util.Cookie; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,13 +33,17 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.tests.Assume; import org.springframework.tests.TestGroup; +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; @@ -49,9 +56,10 @@ import static org.junit.Assert.*; * * @author Rob Winch * @author Sam Brannen + * @author Rossen Stoyanchev * @since 4.2 */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration @WebAppConfiguration public class MockMvcWebClientBuilderTests { @@ -61,8 +69,6 @@ public class MockMvcWebClientBuilderTests { private MockMvc mockMvc; - private WebClient webClient; - @Before public void setup() { @@ -82,31 +88,65 @@ public class MockMvcWebClientBuilderTests { @Test public void mockMvcSetupWithDefaultWebClientDelegate() throws Exception { - this.webClient = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).build(); + WebClient client = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).build(); - assertMvcProcessed("http://localhost/test"); - Assume.group(TestGroup.PERFORMANCE, () -> assertDelegateProcessed("http://example.com/")); + assertMockMvcUsed(client, "http://localhost/test"); + Assume.group(TestGroup.PERFORMANCE, () -> assertMockMvcNotUsed(client, "http://example.com/")); } @Test public void mockMvcSetupWithCustomWebClientDelegate() throws Exception { WebClient otherClient = new WebClient(); - this.webClient = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).withDelegate(otherClient).build(); + WebClient client = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).withDelegate(otherClient).build(); + + assertMockMvcUsed(client, "http://localhost/test"); + Assume.group(TestGroup.PERFORMANCE, () -> assertMockMvcNotUsed(client, "http://example.com/")); + } + + @Test // SPR-14066 + public void cookieManagerShared() throws Exception { + this.mockMvc = MockMvcBuilders.standaloneSetup(new CookieController()).build(); + WebClient client = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).build(); + + assertThat(getResponse(client, "http://localhost/").getContentAsString(), equalTo("NA")); + client.getCookieManager().addCookie(new Cookie("localhost", "cookie", "cookieManagerShared")); + assertThat(getResponse(client, "http://localhost/").getContentAsString(), equalTo("cookieManagerShared")); + } + + @Test // SPR-14265 + public void cookiesAreManaged() throws Exception { + this.mockMvc = MockMvcBuilders.standaloneSetup(new CookieController()).build(); + WebClient client = MockMvcWebClientBuilder.mockMvcSetup(this.mockMvc).build(); + + assertThat(getResponse(client, "http://localhost/").getContentAsString(), equalTo("NA")); + assertThat(postResponse(client, "http://localhost/?cookie=foo").getContentAsString(), equalTo("Set")); + assertThat(getResponse(client, "http://localhost/").getContentAsString(), equalTo("foo")); + assertThat(deleteResponse(client, "http://localhost/").getContentAsString(), equalTo("Delete")); + assertThat(getResponse(client, "http://localhost/").getContentAsString(), equalTo("NA")); + } + + private void assertMockMvcUsed(WebClient client, String url) throws Exception { + assertThat(getResponse(client, url).getContentAsString(), equalTo("mvc")); + } - assertMvcProcessed("http://localhost/test"); - Assume.group(TestGroup.PERFORMANCE, () -> assertDelegateProcessed("http://example.com/")); + private void assertMockMvcNotUsed(WebClient client, String url) throws Exception { + assertThat(getResponse(client, url).getContentAsString(), not(equalTo("mvc"))); } - private void assertMvcProcessed(String url) throws Exception { - assertThat(getWebResponse(url).getContentAsString(), equalTo("mvc")); + private WebResponse getResponse(WebClient client, String url) throws IOException { + return createResponse(client, new WebRequest(new URL(url))); } - private void assertDelegateProcessed(String url) throws Exception { - assertThat(getWebResponse(url).getContentAsString(), not(equalTo("mvc"))); + private WebResponse postResponse(WebClient client, String url) throws IOException { + return createResponse(client, new WebRequest(new URL(url), HttpMethod.POST)); } - private WebResponse getWebResponse(String url) throws IOException { - return this.webClient.getWebConnection().getResponse(new WebRequest(new URL(url))); + private WebResponse deleteResponse(WebClient client, String url) throws IOException { + return createResponse(client, new WebRequest(new URL(url), HttpMethod.DELETE)); + } + + private WebResponse createResponse(WebClient client, WebRequest request) throws IOException { + return client.getWebConnection().getResponse(request); } @@ -124,4 +164,29 @@ public class MockMvcWebClientBuilderTests { } } + @RestController + static class CookieController { + + static final String COOKIE_NAME = "cookie"; + + @RequestMapping(path = "/", produces = "text/plain") + String cookie(@CookieValue(name = COOKIE_NAME, defaultValue = "NA") String cookie) { + return cookie; + } + + @PostMapping(path = "/", produces = "text/plain") + String setCookie(@RequestParam String cookie, HttpServletResponse response) { + response.addCookie(new javax.servlet.http.Cookie(COOKIE_NAME, cookie)); + return "Set"; + } + + @DeleteMapping(path = "/", produces = "text/plain") + String deleteCookie(HttpServletResponse response) { + javax.servlet.http.Cookie cookie = new javax.servlet.http.Cookie(COOKIE_NAME, ""); + cookie.setMaxAge(0); + response.addCookie(cookie); + return "Delete"; + } + } + } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java index b74a22f2..1b3ba8ce 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java @@ -35,6 +35,7 @@ import static org.junit.Assert.*; * @author Rob Winch * @since 4.2 */ +@SuppressWarnings("deprecation") public class MockMvcWebConnectionTests { private final WebClient webClient = new WebClient(); @@ -49,7 +50,7 @@ public class MockMvcWebConnectionTests { @Test public void contextPathNull() throws IOException { - this.webClient.setWebConnection(new MockMvcWebConnection(this.mockMvc, null)); + this.webClient.setWebConnection(new MockMvcWebConnection(this.mockMvc, (String) null)); Page page = this.webClient.getPage("http://localhost/context/a"); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java index f367153c..29c83115 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java @@ -18,6 +18,7 @@ package org.springframework.test.web.servlet.htmlunit; import java.net.URL; import java.util.List; +import javax.servlet.http.Cookie; import com.gargoylesoftware.htmlunit.WebRequest; import com.gargoylesoftware.htmlunit.WebResponse; @@ -95,16 +96,41 @@ public class MockWebResponseBuilderTests { public void buildResponseHeaders() throws Exception { this.response.addHeader("Content-Type", "text/html"); this.response.addHeader("X-Test", "value"); + Cookie cookie = new Cookie("cookieA", "valueA"); + cookie.setDomain("domain"); + cookie.setPath("/path"); + cookie.setMaxAge(1800); + cookie.setSecure(true); + cookie.setHttpOnly(true); + this.response.addCookie(cookie); WebResponse webResponse = this.responseBuilder.build(); List<NameValuePair> responseHeaders = webResponse.getResponseHeaders(); - assertThat(responseHeaders.size(), equalTo(2)); + assertThat(responseHeaders.size(), equalTo(3)); NameValuePair header = responseHeaders.get(0); assertThat(header.getName(), equalTo("Content-Type")); assertThat(header.getValue(), equalTo("text/html")); header = responseHeaders.get(1); assertThat(header.getName(), equalTo("X-Test")); assertThat(header.getValue(), equalTo("value")); + header = responseHeaders.get(2); + assertThat(header.getName(), equalTo("Set-Cookie")); + assertThat(header.getValue(), startsWith("cookieA=valueA;domain=domain;path=/path;expires=")); + assertThat(header.getValue(), endsWith(";secure;httpOnly")); + } + + // SPR-14169 + @Test + public void buildResponseHeadersNullDomainDefaulted() throws Exception { + Cookie cookie = new Cookie("cookieA", "valueA"); + this.response.addCookie(cookie); + WebResponse webResponse = this.responseBuilder.build(); + + List<NameValuePair> responseHeaders = webResponse.getResponseHeaders(); + assertThat(responseHeaders.size(), equalTo(1)); + NameValuePair header = responseHeaders.get(0); + assertThat(header.getName(), equalTo("Set-Cookie")); + assertThat(header.getValue(), equalTo("cookieA=valueA")); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java index 85e41b02..f60e1a76 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java @@ -19,6 +19,7 @@ package org.springframework.test.web.servlet.htmlunit.webdriver; import java.io.IOException; import javax.servlet.http.HttpServletRequest; +import com.gargoylesoftware.htmlunit.util.Cookie; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,12 +28,13 @@ import org.openqa.selenium.htmlunit.HtmlUnitDriver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.tests.Assume; import org.springframework.tests.TestGroup; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; @@ -48,7 +50,7 @@ import static org.junit.Assert.*; * @author Sam Brannen * @since 4.2 */ -@RunWith(SpringJUnit4ClassRunner.class) +@RunWith(SpringRunner.class) @ContextConfiguration @WebAppConfiguration public class MockMvcHtmlUnitDriverBuilderTests { @@ -84,16 +86,16 @@ public class MockMvcHtmlUnitDriverBuilderTests { WebConnectionHtmlUnitDriver otherDriver = new WebConnectionHtmlUnitDriver(); this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).withDelegate(otherDriver).build(); - assertMvcProcessed("http://localhost/test"); - Assume.group(TestGroup.PERFORMANCE, () -> assertDelegateProcessed("http://example.com/")); + assertMockMvcUsed("http://localhost/test"); + Assume.group(TestGroup.PERFORMANCE, () -> assertMockMvcNotUsed("http://example.com/")); } @Test public void mockMvcSetupWithDefaultDriverDelegate() throws Exception { this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc).build(); - assertMvcProcessed("http://localhost/test"); - Assume.group(TestGroup.PERFORMANCE, () -> assertDelegateProcessed("http://example.com/")); + assertMockMvcUsed("http://localhost/test"); + Assume.group(TestGroup.PERFORMANCE, () -> assertMockMvcNotUsed("http://example.com/")); } @Test @@ -108,11 +110,25 @@ public class MockMvcHtmlUnitDriverBuilderTests { assertFalse(this.driver.isJavascriptEnabled()); } - private void assertMvcProcessed(String url) throws Exception { + @Test // SPR-14066 + public void cookieManagerShared() throws Exception { + WebConnectionHtmlUnitDriver otherDriver = new WebConnectionHtmlUnitDriver(); + this.mockMvc = MockMvcBuilders.standaloneSetup(new CookieController()).build(); + this.driver = MockMvcHtmlUnitDriverBuilder.mockMvcSetup(this.mockMvc) + .withDelegate(otherDriver).build(); + + assertThat(get("http://localhost/"), equalTo("")); + Cookie cookie = new Cookie("localhost", "cookie", "cookieManagerShared"); + otherDriver.getWebClient().getCookieManager().addCookie(cookie); + assertThat(get("http://localhost/"), equalTo("cookieManagerShared")); + } + + + private void assertMockMvcUsed(String url) throws Exception { assertThat(get(url), containsString(EXPECTED_BODY)); } - private void assertDelegateProcessed(String url) throws Exception { + private void assertMockMvcNotUsed(String url) throws Exception { assertThat(get(url), not(containsString(EXPECTED_BODY))); } @@ -136,4 +152,13 @@ public class MockMvcHtmlUnitDriverBuilderTests { } } -}
\ No newline at end of file + @RestController + static class CookieController { + + @RequestMapping(path = "/", produces = "text/plain") + String cookie(@CookieValue("cookie") String cookie) { + return cookie; + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java index 90ab35ba..53c76989 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/request/MockHttpServletRequestBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.test.web.servlet.request; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.Charset; import java.security.Principal; import java.util.Arrays; import java.util.Collections; @@ -25,7 +26,6 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; - import javax.servlet.ServletContext; import javax.servlet.http.Cookie; @@ -43,8 +43,12 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.servlet.FlashMap; import org.springframework.web.servlet.support.SessionFlashMapManager; +import org.springframework.web.util.UriComponentsBuilder; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; /** * Unit tests for building a {@link MockHttpServletRequest} with @@ -270,6 +274,20 @@ public class MockHttpServletRequestBuilderTests { } @Test + public void requestParameterFromRequestBodyFormData() throws Exception { + String contentType = "application/x-www-form-urlencoded;charset=UTF-8"; + String body = "name+1=value+1&name+2=value+A&name+2=value+B&name+3"; + + MockHttpServletRequest request = new MockHttpServletRequestBuilder(HttpMethod.POST, "/foo") + .contentType(contentType).content(body.getBytes(Charset.forName("UTF-8"))) + .buildRequest(this.servletContext); + + assertArrayEquals(new String[] {"value 1"}, request.getParameterMap().get("name 1")); + assertArrayEquals(new String[] {"value A", "value B"}, request.getParameterMap().get("name 2")); + assertArrayEquals(new String[] {null}, request.getParameterMap().get("name 3")); + } + + @Test public void acceptHeader() { this.builder.accept(MediaType.TEXT_HTML, MediaType.APPLICATION_XML); @@ -429,7 +447,7 @@ public class MockHttpServletRequestBuilderTests { @Test public void sessionAttributes() { - Map<String, Object> map = new HashMap<String, Object>(); + Map<String, Object> map = new HashMap<>(); map.put("foo", "bar"); this.builder.sessionAttrs(map); @@ -472,6 +490,7 @@ public class MockHttpServletRequestBuilderTests { } // SPR-12945 + @Test public void mergeInvokesDefaultRequestPostProcessorFirst() { final String ATTR = "ATTR"; @@ -492,6 +511,20 @@ public class MockHttpServletRequestBuilderTests { assertEquals(EXEPCTED, request.getAttribute(ATTR)); } + // SPR-13719 + + @Test + public void arbitraryMethod() { + String httpMethod = "REPort"; + URI url = UriComponentsBuilder.fromPath("/foo/{bar}").buildAndExpand(42).toUri(); + this.builder = new MockHttpServletRequestBuilder(httpMethod, url); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals(httpMethod, request.getMethod()); + assertEquals("/foo/42", request.getPathInfo()); + } + private final class User implements Principal { diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java index bf0b5885..b587a34e 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.springframework.test.web.servlet.StubMvcResult; * @author Rossen Stoyanchev * @author Craig Andrews * @author Sam Brannen + * @author Brian Clozel */ public class JsonPathResultMatchersTests { @@ -41,7 +42,7 @@ public class JsonPathResultMatchersTests { "'emptyString': '', " + // "'emptyArray': [], " + // "'emptyMap': {} " + // - "}"; + "}"; private static final StubMvcResult stubMvcResult; @@ -57,7 +58,6 @@ public class JsonPathResultMatchersTests { } } - @Test public void value() throws Exception { new JsonPathResultMatchers("$.str").value("foo").match(stubMvcResult); @@ -233,4 +233,42 @@ public class JsonPathResultMatchersTests { new JsonPathResultMatchers("$.arr").isString().match(stubMvcResult); } + @Test(expected = AssertionError.class) + public void valueWithJsonPrefixNotConfigured() throws Exception { + String jsonPrefix = "prefix"; + StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix); + new JsonPathResultMatchers("$.str").value("foo").match(result); + } + + @Test(expected = AssertionError.class) + public void valueWithJsonWrongPrefix() throws Exception { + String jsonPrefix = "prefix"; + StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix); + new JsonPathResultMatchers("$.str").prefix("wrong").value("foo").match(result); + } + + @Test + public void valueWithJsonPrefix() throws Exception { + String jsonPrefix = "prefix"; + StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix); + new JsonPathResultMatchers("$.str").prefix(jsonPrefix).value("foo").match(result); + } + + @Test(expected = AssertionError.class) + public void prefixWithPayloadNotLongEnough() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + response.addHeader("Content-Type", "application/json"); + response.getWriter().print(new String("test".getBytes("ISO-8859-1"))); + StubMvcResult result = new StubMvcResult(null, null, null, null, null, null, response); + + new JsonPathResultMatchers("$.str").prefix("prefix").value("foo").match(result); + } + + private StubMvcResult createPrefixedStubMvcResult(String jsonPrefix) throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + response.addHeader("Content-Type", "application/json"); + response.getWriter().print(jsonPrefix + new String(RESPONSE_CONTENT.getBytes("ISO-8859-1"))); + return new StubMvcResult(null, null, null, null, null, null, response); + } + } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java index 7b1c04d4..ead30b9e 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/StatusResultMatchersTests.java @@ -59,7 +59,7 @@ public class StatusResultMatchersTests { List<AssertionError> failures = new ArrayList<AssertionError>(); - for(HttpStatus status : HttpStatus.values()) { + for (HttpStatus status : HttpStatus.values()) { MockHttpServletResponse response = new MockHttpServletResponse(); response.setStatus(status.value()); MvcResult mvcResult = new StubMvcResult(request, null, null, null, null, null, response); @@ -91,9 +91,7 @@ public class StatusResultMatchersTests { @Test public void statusRanges() throws Exception { - - for(HttpStatus status : HttpStatus.values()) { - + for (HttpStatus status : HttpStatus.values()) { MockHttpServletResponse response = new MockHttpServletResponse(); response.setStatus(status.value()); MvcResult mvcResult = new StubMvcResult(request, null, null, null, null, null, response); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java index 31943512..0613048d 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/AsyncControllerJavaConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.concurrent.Callable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; + import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; @@ -36,7 +37,7 @@ import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.async.CallableProcessingInterceptor; @@ -55,6 +56,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * Tests with Java configuration. * * @author Rossen Stoyanchev + * @author Sam Brannen */ @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @@ -123,7 +125,7 @@ public class AsyncControllerJavaConfigTests { @RestController static class AsyncController { - @RequestMapping(path = "/callable") + @GetMapping("/callable") public Callable<Map<String, String>> getCallable() { return () -> Collections.singletonMap("key", "value"); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java index 5e115f66..ee365936 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/PersonController.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -16,25 +16,24 @@ package org.springframework.test.web.servlet.samples.context; -import org.springframework.stereotype.Controller; import org.springframework.test.web.Person; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; -@Controller +@RestController +@RequestMapping("/person") public class PersonController { private final PersonDao personDao; - public PersonController(PersonDao personDao) { + PersonController(PersonDao personDao) { this.personDao = personDao; } - @RequestMapping(value="/person/{id}", method=RequestMethod.GET) - @ResponseBody + @GetMapping("/{id}") public Person getPerson(@PathVariable long id) { return this.personDao.getPerson(id); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java index 48038763..c922297f 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.test.web.servlet.samples.standalone; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; @@ -26,6 +27,7 @@ import org.junit.Test; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.test.web.Person; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -36,6 +38,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import static org.junit.Assert.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; @@ -67,11 +70,39 @@ public class AsyncTests { this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}")); } @Test + public void streaming() throws Exception { + this.mockMvc.perform(get("/1").param("streaming", "true")) + .andExpect(request().asyncStarted()) + .andDo(r -> r.getAsyncResult()) // fetch async result similar to "asyncDispatch" builder + .andExpect(status().isOk()) + .andExpect(content().string("name=Joe")); + } + + @Test + public void streamingSlow() throws Exception { + this.mockMvc.perform(get("/1").param("streamingSlow", "true")) + .andExpect(request().asyncStarted()) + .andDo(r -> r.getAsyncResult()) + .andExpect(status().isOk()) + .andExpect(content().string("name=Joe&someBoolean=true")); + } + + @Test + public void streamingJson() throws Exception { + this.mockMvc.perform(get("/1").param("streamingJson", "true")) + .andExpect(request().asyncStarted()) + .andDo(r -> r.getAsyncResult()) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) + .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.5}")); + } + + @Test public void deferredResult() throws Exception { MvcResult mvcResult = this.mockMvc.perform(get("/1").param("deferredResult", "true")) .andExpect(request().asyncStarted()) @@ -81,7 +112,7 @@ public class AsyncTests { this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}")); } @@ -94,7 +125,7 @@ public class AsyncTests { this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}")); } @@ -122,7 +153,7 @@ public class AsyncTests { this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}")); } @@ -137,7 +168,7 @@ public class AsyncTests { this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}")); } @@ -161,7 +192,7 @@ public class AsyncTests { this.mockMvc.perform(asyncDispatch(mvcResult)) .andDo(print(writer)) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)) .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}")); assertTrue(writer.toString().contains("Async started = false")); @@ -184,6 +215,31 @@ public class AsyncTests { return () -> new Person("Joe"); } + @RequestMapping(params = "streaming") + public StreamingResponseBody getStreaming() { + return os -> os.write("name=Joe".getBytes()); + } + + @RequestMapping(params = "streamingSlow") + public StreamingResponseBody getStreamingSlow() { + return os -> { + os.write("name=Joe".getBytes()); + try { + Thread.sleep(200); + os.write("&someBoolean=true".getBytes()); + } + catch (InterruptedException e) { + /* no-op */ + } + }; + } + + @RequestMapping(params = "streamingJson") + public ResponseEntity<StreamingResponseBody> getStreamingJson() { + return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON_UTF8) + .body(os -> os.write("{\"name\":\"Joe\",\"someDouble\":0.5}".getBytes(StandardCharsets.UTF_8))); + } + @RequestMapping(params = "deferredResult") public DeferredResult<Person> getDeferredResult() { DeferredResult<Person> deferredResult = new DeferredResult<Person>(); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ViewResolutionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ViewResolutionTests.java index 0ac2b07c..b286f626 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ViewResolutionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ViewResolutionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,10 +53,7 @@ public class ViewResolutionTests { @Test public void testJspOnly() throws Exception { - - InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); - viewResolver.setPrefix("/WEB-INF/"); - viewResolver.setSuffix(".jsp"); + InternalResourceViewResolver viewResolver = new InternalResourceViewResolver("/WEB-INF/", ".jsp"); standaloneSetup(new PersonController()).setViewResolvers(viewResolver).build() .perform(get("/person/Corea")) @@ -68,7 +65,6 @@ public class ViewResolutionTests { @Test public void testJsonOnly() throws Exception { - standaloneSetup(new PersonController()).setSingleView(new MappingJackson2JsonView()).build() .perform(get("/person/Corea")) .andExpect(status().isOk()) @@ -78,7 +74,6 @@ public class ViewResolutionTests { @Test public void testXmlOnly() throws Exception { - Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setClassesToBeBound(Person.class); @@ -91,7 +86,6 @@ public class ViewResolutionTests { @Test public void testContentNegotiation() throws Exception { - Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setClassesToBeBound(Person.class); @@ -131,7 +125,6 @@ public class ViewResolutionTests { @Test public void defaultViewResolver() throws Exception { - standaloneSetup(new PersonController()).build() .perform(get("/person/Corea")) .andExpect(model().attribute("person", hasProperty("name", equalTo("Corea")))) diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java index 0c470ea5..d0d60138 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java @@ -16,8 +16,6 @@ package org.springframework.test.web.servlet.samples.standalone.resultmatchers; -import java.nio.charset.Charset; - import org.junit.Before; import org.junit.Test; @@ -44,8 +42,6 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; */ public class ContentAssertionTests { - public static final MediaType TEXT_PLAIN_UTF8 = new MediaType("text", "plain", Charset.forName("UTF-8")); - private MockMvc mockMvc; @Before @@ -56,8 +52,10 @@ public class ContentAssertionTests { @Test public void testContentType() throws Exception { this.mockMvc.perform(get("/handle").accept(MediaType.TEXT_PLAIN)) - .andExpect(content().contentType(MediaType.TEXT_PLAIN)) - .andExpect(content().contentType("text/plain")); + .andExpect(content().contentType(MediaType.valueOf("text/plain;charset=ISO-8859-1"))) + .andExpect(content().contentType("text/plain;charset=ISO-8859-1")) + .andExpect(content().contentTypeCompatibleWith("text/plain")) + .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_PLAIN)); this.mockMvc.perform(get("/handleUtf8")) .andExpect(content().contentType(MediaType.valueOf("text/plain;charset=UTF-8"))) diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java index 9d3b883d..2c671bbf 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -18,66 +18,84 @@ package org.springframework.test.web.servlet.samples.standalone.resultmatchers; import java.lang.reflect.Method; -import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; -import org.springframework.stereotype.Controller; +import org.springframework.http.ResponseEntity; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.bind.annotation.RequestMapping; - -import static org.hamcrest.Matchers.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.handler; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; +import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.on; /** - * Examples of expectations on the handler or handler method that executed the request. - * - * <p>Note that in most cases "handler" is synonymous with "controller". - * For example an {@code @Controller} is a kind of handler. + * Examples of expectations on the controller type and controller method. * * @author Rossen Stoyanchev + * @author Sam Brannen */ public class HandlerAssertionTests { - private MockMvc mockMvc; + private final MockMvc mockMvc = standaloneSetup(new SimpleController()).alwaysExpect(status().isOk()).build(); + + @Rule + public final ExpectedException exception = ExpectedException.none(); - @Before - public void setup() { - this.mockMvc = standaloneSetup(new SimpleController()).alwaysExpect(status().isOk()).build(); - } @Test - public void testHandlerType() throws Exception { + public void handlerType() throws Exception { this.mockMvc.perform(get("/")).andExpect(handler().handlerType(SimpleController.class)); } @Test - public void testHandlerMethodNameEqualTo() throws Exception { - this.mockMvc.perform(get("/")).andExpect(handler().methodName("handle")); + public void methodCallOnNonMock() throws Exception { + exception.expect(AssertionError.class); + exception.expectMessage("The supplied object [bogus] is not an instance of"); + exception.expectMessage(MvcUriComponentsBuilder.MethodInvocationInfo.class.getName()); + exception.expectMessage("Ensure that you invoke the handler method via MvcUriComponentsBuilder.on()"); - // Hamcrest matcher.. - this.mockMvc.perform(get("/")).andExpect(handler().methodName(equalTo("handle"))); + this.mockMvc.perform(get("/")).andExpect(handler().methodCall("bogus")); } @Test - public void testHandlerMethodNameMatcher() throws Exception { + public void methodCall() throws Exception { + this.mockMvc.perform(get("/")).andExpect(handler().methodCall(on(SimpleController.class).handle())); + } + + @Test + public void methodName() throws Exception { + this.mockMvc.perform(get("/")).andExpect(handler().methodName("handle")); + } + + @Test + public void methodNameMatchers() throws Exception { + this.mockMvc.perform(get("/")).andExpect(handler().methodName(equalTo("handle"))); this.mockMvc.perform(get("/")).andExpect(handler().methodName(is(not("save")))); } @Test - public void testHandlerMethod() throws Exception { + public void method() throws Exception { Method method = SimpleController.class.getMethod("handle"); this.mockMvc.perform(get("/")).andExpect(handler().method(method)); } - @Controller - private static class SimpleController { + @RestController + static class SimpleController { @RequestMapping("/") - public String handle() { - return "view"; + public ResponseEntity<Void> handle() { + return ResponseEntity.ok().build(); } } + } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java index e81824c8..38fbba01 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,11 @@ package org.springframework.test.web.servlet.samples.standalone.resultmatchers; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + import org.junit.Before; import org.junit.Test; @@ -28,16 +33,22 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.request.WebRequest; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.springframework.http.HttpHeaders.IF_MODIFIED_SINCE; +import static org.springframework.http.HttpHeaders.LAST_MODIFIED; +import static org.springframework.http.HttpHeaders.VARY; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; /** * Examples of expectations on response header values. @@ -48,86 +59,95 @@ import java.util.TimeZone; */ public class HeaderAssertionTests { - private static final String EXPECTED_ASSERTION_ERROR_MSG = "Should have thrown an AssertionError"; - - private static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + private static final String ERROR_MESSAGE = "Should have thrown an AssertionError"; - private static final String LAST_MODIFIED = "Last-Modified"; - - private final long currentTime = System.currentTimeMillis(); private String now; - private String oneMinuteAgo; + private String minuteAgo; - private String oneSecondLater; + private String secondLater; private MockMvc mockMvc; - private PersonController personController; + private final long currentTime = System.currentTimeMillis(); @Before public void setup() { SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); - this.now = dateFormat.format(new Date(currentTime)); - this.oneMinuteAgo = dateFormat.format(new Date(currentTime - (1000 * 60))); - this.oneSecondLater = dateFormat.format(new Date(currentTime + 1000)); - this.personController = new PersonController(); - this.personController.setStubTimestamp(currentTime); - this.mockMvc = standaloneSetup(this.personController).build(); + this.now = dateFormat.format(new Date(this.currentTime)); + this.minuteAgo = dateFormat.format(new Date(this.currentTime - (1000 * 60))); + this.secondLater = dateFormat.format(new Date(this.currentTime + 1000)); + + PersonController controller = new PersonController(); + controller.setStubTimestamp(this.currentTime); + this.mockMvc = standaloneSetup(controller).build(); } + @Test public void stringWithCorrectResponseHeaderValue() throws Exception { - this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))// - .andExpect(header().string(LAST_MODIFIED, now)); + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, minuteAgo)) + .andExpect(header().string(LAST_MODIFIED, now)); } @Test public void stringWithMatcherAndCorrectResponseHeaderValue() throws Exception { - this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))// - .andExpect(header().string(LAST_MODIFIED, equalTo(now))); + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, minuteAgo)) + .andExpect(header().string(LAST_MODIFIED, equalTo(now))); + } + + @Test + public void multiStringHeaderValue() throws Exception { + this.mockMvc.perform(get("/persons/1")).andExpect(header().stringValues(VARY, "foo", "bar")); + } + + @SuppressWarnings("unchecked") + @Test + public void multiStringHeaderValueWithMatchers() throws Exception { + this.mockMvc.perform(get("/persons/1")) + .andExpect(header().stringValues(VARY, hasItems(containsString("foo"), startsWith("bar")))); } @Test public void dateValueWithCorrectResponseHeaderValue() throws Exception { - this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))// - .andExpect(header().dateValue(LAST_MODIFIED, currentTime)); + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, minuteAgo)) + .andExpect(header().dateValue(LAST_MODIFIED, this.currentTime)); } @Test public void longValueWithCorrectResponseHeaderValue() throws Exception { - this.mockMvc.perform(get("/persons/1"))// - .andExpect(header().longValue("X-Rate-Limiting", 42)); + this.mockMvc.perform(get("/persons/1")) + .andExpect(header().longValue("X-Rate-Limiting", 42)); } @Test public void stringWithMissingResponseHeader() throws Exception { - this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))// - .andExpect(status().isNotModified())// - .andExpect(header().string("X-Custom-Header", (String) null)); + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now)) + .andExpect(status().isNotModified()) + .andExpect(header().stringValues("X-Custom-Header")); } @Test public void stringWithMatcherAndMissingResponseHeader() throws Exception { - this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))// - .andExpect(status().isNotModified())// - .andExpect(header().string("X-Custom-Header", nullValue())); + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now)) + .andExpect(status().isNotModified()) + .andExpect(header().string("X-Custom-Header", nullValue())); } @Test public void longValueWithMissingResponseHeader() throws Exception { try { - this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now))// - .andExpect(status().isNotModified())// - .andExpect(header().longValue("X-Custom-Header", 99L)); + this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, now)) + .andExpect(status().isNotModified()) + .andExpect(header().longValue("X-Custom-Header", 99L)); - fail(EXPECTED_ASSERTION_ERROR_MSG); + fail(ERROR_MESSAGE); } catch (AssertionError e) { - if (EXPECTED_ASSERTION_ERROR_MSG.equals(e.getMessage())) { + if (ERROR_MESSAGE.equals(e.getMessage())) { throw e; } assertEquals("Response does not contain header " + "X-Custom-Header", e.getMessage()); @@ -138,32 +158,30 @@ public class HeaderAssertionTests { @Test public void doesNotExist() throws Exception { - this.mockMvc.perform(get("/persons/1")) - .andExpect(header().doesNotExist("X-Custom-Header")); + this.mockMvc.perform(get("/persons/1")).andExpect(header().doesNotExist("X-Custom-Header")); } // SPR-10771 @Test(expected = AssertionError.class) public void doesNotExistFail() throws Exception { - this.mockMvc.perform(get("/persons/1")) - .andExpect(header().doesNotExist(LAST_MODIFIED)); + this.mockMvc.perform(get("/persons/1")).andExpect(header().doesNotExist(LAST_MODIFIED)); } @Test public void stringWithIncorrectResponseHeaderValue() throws Exception { - assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, oneSecondLater), oneSecondLater); + assertIncorrectResponseHeader(header().string(LAST_MODIFIED, secondLater), secondLater); } @Test public void stringWithMatcherAndIncorrectResponseHeaderValue() throws Exception { - assertIncorrectResponseHeaderValue(header().string(LAST_MODIFIED, equalTo(oneSecondLater)), oneSecondLater); + assertIncorrectResponseHeader(header().string(LAST_MODIFIED, equalTo(secondLater)), secondLater); } @Test public void dateValueWithIncorrectResponseHeaderValue() throws Exception { - long unexpected = currentTime + 1000; - assertIncorrectResponseHeaderValue(header().dateValue(LAST_MODIFIED, unexpected), oneSecondLater); + long unexpected = this.currentTime + 1000; + assertIncorrectResponseHeader(header().dateValue(LAST_MODIFIED, unexpected), secondLater); } @Test(expected = AssertionError.class) @@ -171,21 +189,20 @@ public class HeaderAssertionTests { this.mockMvc.perform(get("/persons/1")).andExpect(header().longValue("X-Rate-Limiting", 1)); } - private void assertIncorrectResponseHeaderValue(ResultMatcher resultMatcher, String unexpected) throws Exception { + private void assertIncorrectResponseHeader(ResultMatcher matcher, String unexpected) throws Exception { try { - this.mockMvc.perform(get("/persons/1").header(IF_MODIFIED_SINCE, oneMinuteAgo))// - .andExpect(resultMatcher); + this.mockMvc.perform(get("/persons/1") + .header(IF_MODIFIED_SINCE, minuteAgo)) + .andExpect(matcher); - fail(EXPECTED_ASSERTION_ERROR_MSG); + fail(ERROR_MESSAGE); } catch (AssertionError e) { - if (EXPECTED_ASSERTION_ERROR_MSG.equals(e.getMessage())) { + if (ERROR_MESSAGE.equals(e.getMessage())) { throw e; } - // [SPR-10659] Ensure that the header name is included in the message - // - // We don't use assertEquals() since we cannot control the formatting - // produced by JUnit or Hamcrest. + // SPR-10659: ensure header name is in the message + // Unfortunately, we can't control formatting from JUnit or Hamcrest. assertMessageContains(e, "Response header " + LAST_MODIFIED); assertMessageContains(e, unexpected); assertMessageContains(e, now); @@ -198,8 +215,6 @@ public class HeaderAssertionTests { } - // ------------------------------------------------------------------------- - @Controller private static class PersonController { @@ -216,6 +231,7 @@ public class HeaderAssertionTests { .ok() .lastModified(calculateLastModified(id)) .header("X-Rate-Limiting", "42") + .header("Vary", "foo", "bar") .body(new Person("Jason")); } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java index 4ae88e7a..c1e25791 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java @@ -158,7 +158,7 @@ public class XpathAssertionTests { standaloneSetup(new BlogFeedController()).build() .perform(get("/blog.atom").accept(MediaType.APPLICATION_ATOM_XML)) .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_ATOM_XML)) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_ATOM_XML)) .andExpect(xpath("//feed/title").string("Test Feed")) .andExpect(xpath("//feed/icon").string("http://www.example.com/favicon.ico")); } diff --git a/spring-test/src/test/resources/log4j.properties b/spring-test/src/test/resources/log4j.properties index 8b88c2e0..258e7b84 100644 --- a/spring-test/src/test/resources/log4j.properties +++ b/spring-test/src/test/resources/log4j.properties @@ -25,6 +25,9 @@ log4j.logger.org.springframework.test.context.web=WARN #log4j.logger.org.springframework.test.context.support.AbstractGenericContextLoader=INFO #log4j.logger.org.springframework.test.context.support.AnnotationConfigContextLoader=INFO +# The following must be kept at DEBUG in order to test SPR-14363. +log4j.logger.org.springframework.test.util=DEBUG + log4j.logger.org.springframework.test.web.servlet.result=DEBUG #log4j.logger.org.springframework.test=TRACE |