summaryrefslogtreecommitdiff
path: root/spring-test/src
diff options
context:
space:
mode:
authorEmmanuel Bourg <ebourg@apache.org>2014-12-03 14:31:16 +0100
committerEmmanuel Bourg <ebourg@apache.org>2014-12-03 14:31:16 +0100
commitc56370beb0a2bfa263e125fce107dceccee89fd3 (patch)
tree7ee611ceb0acbbdf7f83abcd72adb854b7d77225 /spring-test/src
parentaa5221b73661fa728dc4e62e1230e9104528c4eb (diff)
Imported Upstream version 3.2.12
Diffstat (limited to 'spring-test/src')
-rw-r--r--spring-test/src/main/java/org/springframework/mock/env/MockEnvironment.java61
-rw-r--r--spring-test/src/main/java/org/springframework/mock/env/MockPropertySource.java104
-rw-r--r--spring-test/src/main/java/org/springframework/mock/env/package-info.java10
-rw-r--r--spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java56
-rw-r--r--spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java86
-rw-r--r--spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java122
-rw-r--r--spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpResponse.java70
-rw-r--r--spring-test/src/main/java/org/springframework/mock/http/client/package-info.java23
-rw-r--r--spring-test/src/main/java/org/springframework/mock/http/package-info.java23
-rw-r--r--spring-test/src/main/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java78
-rw-r--r--spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContext.java345
-rw-r--r--spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java230
-rw-r--r--spring-test/src/main/java/org/springframework/mock/jndi/package-info.java13
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/DelegatingServletInputStream.java66
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/DelegatingServletOutputStream.java71
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/HeaderValueHolder.java96
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockBodyContent.java197
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockExpressionEvaluator.java96
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockFilterChain.java183
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockFilterConfig.java104
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java917
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java603
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java263
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockJspWriter.java191
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockMultipartFile.java131
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java131
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java353
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockRequestDispatcher.java93
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockServletConfig.java103
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java499
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/PassThroughFilterChain.java85
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/package-info.java15
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockActionRequest.java86
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockActionResponse.java122
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockBaseURL.java152
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockCacheControl.java70
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockClientDataRequest.java128
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockEvent.java88
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockEventRequest.java88
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockEventResponse.java34
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockMimeResponse.java240
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java95
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortalContext.java101
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletConfig.java183
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletContext.java278
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletPreferences.java114
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletRequest.java531
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletRequestDispatcher.java84
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletResponse.java195
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletSession.java232
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletURL.java115
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockRenderRequest.java93
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockRenderResponse.java88
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceRequest.java128
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceResponse.java40
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceURL.java69
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/MockStateAwareResponse.java154
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java145
-rw-r--r--spring-test/src/main/java/org/springframework/mock/web/portlet/package-info.java14
-rw-r--r--spring-test/src/main/java/org/springframework/test/AbstractDependencyInjectionSpringContextTests.java294
-rw-r--r--spring-test/src/main/java/org/springframework/test/AbstractSingleSpringContextTests.java362
-rw-r--r--spring-test/src/main/java/org/springframework/test/AbstractSpringContextTests.java171
-rw-r--r--spring-test/src/main/java/org/springframework/test/AbstractTransactionalDataSourceSpringContextTests.java199
-rw-r--r--spring-test/src/main/java/org/springframework/test/AbstractTransactionalSpringContextTests.java359
-rw-r--r--spring-test/src/main/java/org/springframework/test/AssertThrows.java255
-rw-r--r--spring-test/src/main/java/org/springframework/test/ConditionalTestCase.java99
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/AbstractAnnotationAwareTransactionalTests.java313
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java142
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/ExpectedException.java43
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/IfProfileValue.java105
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/NotTransactional.java43
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSource.java52
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java56
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java209
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/Repeat.java43
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/Rollback.java45
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/SystemProfileValueSource.java58
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/Timed.java57
-rw-r--r--spring-test/src/main/java/org/springframework/test/annotation/package-info.java6
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java109
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java121
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ContextCache.java256
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java312
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java420
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java157
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ContextLoader.java95
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java707
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java376
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java126
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/TestContext.java220
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/TestContextManager.java463
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java120
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java100
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit38/AbstractJUnit38SpringContextTests.java399
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit38/AbstractTransactionalJUnit38SpringContextTests.java158
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit38/package-info.java7
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java102
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java175
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java474
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java7
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestClassCallbacks.java92
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestMethodCallbacks.java105
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestClassCallbacks.java64
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestMethodCallbacks.java77
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/statements/SpringFailOnTimeout.java76
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/statements/SpringRepeat.java76
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/junit4/statements/package-info.java7
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/package-info.java16
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java286
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java286
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java266
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/AbstractTestExecutionListener.java73
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java192
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java112
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java47
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/DependencyInjectionTestExecutionListener.java115
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java146
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/GenericPropertiesContextLoader.java52
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/GenericXmlContextLoader.java50
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/support/package-info.java6
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/testng/AbstractTestNGSpringContextTests.java210
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java166
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/testng/package-info.java7
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java45
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java45
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfiguration.java70
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfigurationAttributes.java77
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java596
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/package-info.java7
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java267
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/AnnotationConfigWebContextLoader.java172
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/GenericXmlWebContextLoader.java48
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java194
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java66
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/WebDelegatingSmartContextLoader.java48
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java206
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/web/package-info.java6
-rw-r--r--spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java419
-rw-r--r--spring-test/src/main/java/org/springframework/test/jdbc/SimpleJdbcTestUtils.java187
-rw-r--r--spring-test/src/main/java/org/springframework/test/jdbc/package-info.java6
-rw-r--r--spring-test/src/main/java/org/springframework/test/jpa/AbstractAspectjJpaTests.java61
-rw-r--r--spring-test/src/main/java/org/springframework/test/jpa/AbstractJpaTests.java386
-rw-r--r--spring-test/src/main/java/org/springframework/test/jpa/OrmXmlOverridingShadowingClassLoader.java55
-rw-r--r--spring-test/src/main/java/org/springframework/test/jpa/package-info.java9
-rw-r--r--spring-test/src/main/java/org/springframework/test/package-info.java9
-rw-r--r--spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java93
-rw-r--r--spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java315
-rw-r--r--spring-test/src/main/java/org/springframework/test/util/package-info.java5
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/AbstractModelAndViewTests.java169
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/ModelAndViewAssert.java230
-rw-r--r--spring-test/src/main/java/org/springframework/test/web/package-info.java6
-rw-r--r--spring-test/src/main/java/overview.html7
-rw-r--r--spring-test/src/test/java/org/springframework/mock/web/MockFilterChainTests.java161
-rw-r--r--spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java206
-rw-r--r--spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java224
-rw-r--r--spring-test/src/test/java/org/springframework/mock/web/MockHttpSessionTests.java47
-rw-r--r--spring-test/src/test/java/org/springframework/mock/web/MockPageContextTests.java56
-rw-r--r--spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java132
-rw-r--r--spring-test/src/test/java/org/springframework/test/AbstractSpr3350SingleSpringContextTests.java79
-rw-r--r--spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests-context.properties2
-rw-r--r--spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests.java60
-rw-r--r--spring-test/src/test/java/org/springframework/test/Spr3264DependencyInjectionSpringContextTests.java67
-rw-r--r--spring-test/src/test/java/org/springframework/test/Spr3264SingleSpringContextTests.java55
-rw-r--r--spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests-context.xml9
-rw-r--r--spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests.java45
-rw-r--r--spring-test/src/test/java/org/springframework/test/annotation/ProfileValueAnnotationAwareTransactionalTests-context.xml12
-rw-r--r--spring-test/src/test/java/org/springframework/test/annotation/ProfileValueAnnotationAwareTransactionalTests.java147
-rw-r--r--spring-test/src/test/java/org/springframework/test/annotation/ProfileValueUtilsTests.java214
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java272
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java329
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/ContextHierarchyDirtiesContextTests.java212
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests$BareAnnotations-context.xml7
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java911
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java450
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java160
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java193
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java153
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests-context.properties5
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests.java60
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesTests-context.properties5
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesTests.java61
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests-context.xml44
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests.java83
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelOneTests.java96
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelTwoTests.java62
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithOverriddenConfigLevelTwoTests.java73
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/DirtiesContextWithContextHierarchyTests.java149
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithSingleLevelContextHierarchyTests.java63
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests.java79
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java93
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSubclassTests.java72
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSuperclassTests.java71
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithSingleLevelContextHierarchyTests.java72
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSubclassTests.java78
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSuperclassTests.java79
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests.java62
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyTests.java78
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/web/ControllerIntegrationTests.java102
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java88
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/web/EarTests.java65
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java76
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit38/ConcreteTransactionalJUnit38SpringContextTests-context.xml33
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit38/ConcreteTransactionalJUnit38SpringContextTests.java231
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit38/FailingBeforeAndAfterMethodsTests-context.xml12
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit38/FailingBeforeAndAfterMethodsTests.java156
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit38/ProfileValueJUnit38SpringContextTests.java212
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit38/RepeatedJUnit38SpringContextTests.java95
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/AbsolutePathSpringJUnit4ClassRunnerAppCtxTests.java37
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java74
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests-context.xml8
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java140
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelDisabledSpringRunnerTests.java70
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests-context.xml8
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java117
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.java52
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests-context.xml29
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests.java232
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java71
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseTransactionalSpringRunnerTests-context.xml15
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseTransactionalSpringRunnerTests.java91
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueTransactionalSpringRunnerTests-context.xml8
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueTransactionalSpringRunnerTests.java88
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRunnerTests.java100
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ExpectedExceptionSpringRunnerTests.java90
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTests-context.xml12
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTests.java202
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/HardCodedProfileValueSourceSpringRunnerTests.java56
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/InheritedConfigSpringJUnit4ClassRunnerAppCtxTests.java35
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests-context.xml8
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java118
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context1.xml11
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context2.xml9
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context3.xml13
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.java50
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ParameterizedDependencyInjectionTests-context.xml21
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/ParameterizedDependencyInjectionTests.java114
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties5
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java78
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/RelativePathSpringJUnit4ClassRunnerAppCtxTests.java37
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java190
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseTransactionalSpringRunnerTests-context.xml15
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseTransactionalSpringRunnerTests.java88
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueTransactionalSpringRunnerTests-context.xml8
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueTransactionalSpringRunnerTests.java87
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit47ClassRunnerRuleTests.java53
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml27
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java241
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerTests.java48
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java110
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesSpringRunnerTests.java44
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesTests.java108
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java102
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java72
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/TrackingRunListener.java68
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/AciTestSuite.java51
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/DevProfileInitializer.java32
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/FooBarAliasInitializer.java32
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/DevProfileConfig.java35
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/GlobalConfig.java38
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerWithoutConfigFilesOrClassesTest.java62
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/MergedInitializersAnnotationConfigTests.java44
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/MultipleInitializersAnnotationConfigTests.java54
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/OrderedInitializersAnnotationConfigTests.java148
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/OverriddenInitializersAnnotationConfigTests.java44
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/SingleInitializerAnnotationConfigTests.java60
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml19
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests.java53
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.java39
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigTestSuite.java48
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingDefaultConfigClassesInheritedTests.java62
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingExplicitConfigClassesInheritedTests.java45
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesBaseTests.java70
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesInheritedTests.java62
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.java62
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.java45
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesBaseTests.java69
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesInheritedTests.java61
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesBaseTests.java52
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesInheritedTests.java52
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesBaseTests.java53
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesInheritedTests.java54
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/annotation/PojoAndStringConfig.java67
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests-context.xml39
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java119
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/db-schema.sql16
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/db-test-data.sql3
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/DriversLicense.hbm.xml14
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/DriversLicense.java60
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/Person.hbm.xml16
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/Person.java76
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/PersonRepository.java33
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java53
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/PersonService.java33
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/impl/StandardPersonService.java55
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileAnnotationConfigTests.java58
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileConfig.java35
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileAnnotationConfigTests.java39
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileConfig.java41
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java37
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java57
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java37
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java39
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/import.xml13
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests-context.xml17
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests.java57
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileXmlConfigTests.java39
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java37
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests-context.xml11
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests.java44
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingExplicitLocationsInheritedTests.java44
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsBaseTests-context.xml11
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsBaseTests.java52
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsInheritedTests-context.xml9
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsInheritedTests.java49
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/ExplicitLocationsBaseTests.java52
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/ExplicitLocationsInheritedTests.java49
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/Spr3896SuiteTests.java49
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/Jsr250LifecycleTests.java118
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/LifecycleBean.java44
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests-context.xml13
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests.java56
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/Spr8849Tests.java46
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass1-context.xml7
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass1.java49
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass2-context.xml7
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass2.java49
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/datasource-config.xml12
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/spr8849-schema.sql3
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AbstractTransactionalAnnotatedConfigClassTests.java136
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java100
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AtBeanLiteModeScopeTests.java104
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/LifecycleBean.java51
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassWithAtConfigurationTests.java85
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.java137
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/schema.sql6
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9604/LookUpTxMgrViaTransactionManagementConfigurerTests.java102
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpNonexistentTxMgrTests.java59
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndDefaultNameTests.java84
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndNameTests.java86
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndQualifierAtClassLevelTests.java84
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndQualifierAtMethodLevelTests.java84
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeTests.java77
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799AnnotationConfigTests.java54
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests.java42
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/junit4/transactionalTests-context.xml19
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/AnnotatedFooConfigInnerClassTestCase.java40
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderTests.java81
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/ContextConfigurationInnerClassTestCase.java34
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests-context.xml7
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests.java58
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java190
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/FinalConfigInnerClassTestCase.java35
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderResourceLocationsTests$1ClasspathExistentDefaultLocationsTestCase-context.xml7
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderResourceLocationsTests.java140
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/MultipleStaticConfigurationClassesTestCase.java38
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/NonStaticConfigInnerClassesTestCase.java40
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/PlainVanillaFooConfigInnerClassTestCase.java32
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/support/PrivateConfigInnerClassTestCase.java35
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java194
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests-context.xml25
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests.java233
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/DirtiesContextTransactionalTestNGSpringContextTests-context.xml12
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/DirtiesContextTransactionalTestNGSpringContextTests.java86
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests-context.xml12
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests.java257
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/TimedTransactionalTestNGSpringContextTests-context.xml12
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/TimedTransactionalTestNGSpringContextTests.java47
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/data.sql1
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/schema.sql4
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/web/ServletTestExecutionListenerTestNGIntegrationTests.java78
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/testng/web/TestNGSpringContextWebTests.java119
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/web/AbstractBasicWacTests.java98
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/web/BasicAnnotationConfigWacTests.java48
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/web/BasicXmlWacTests.java36
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/web/JUnit4SpringContextWebTests.java118
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/web/RequestAndSessionScopedBeansWacTests.java80
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerJUnitIntegrationTests.java78
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerTests.java202
-rw-r--r--spring-test/src/test/java/org/springframework/test/context/web/WebContextLoaderTestSuite.java41
-rw-r--r--spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java142
-rw-r--r--spring-test/src/test/java/org/springframework/test/transaction/TransactionTestUtils.java82
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java280
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/subpackage/Component.java85
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntity.java45
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/subpackage/PersistentEntity.java38
-rw-r--r--spring-test/src/test/java/org/springframework/test/util/subpackage/Person.java99
-rw-r--r--spring-test/src/test/java/org/springframework/web/multipart/MockMultipartHttpServletRequestTests.java97
-rw-r--r--spring-test/src/test/resources/log4j.xml63
-rw-r--r--spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests-ChildConfig.xml9
-rw-r--r--spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests-context.xml9
-rw-r--r--spring-test/src/test/resources/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests-context.xml7
-rw-r--r--spring-test/src/test/resources/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests-context.xml10
-rw-r--r--spring-test/src/test/resources/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$ImproperDuplicateDefaultXmlAndConfigClassTestCase-context.xml8
-rw-r--r--spring-test/src/test/resources/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$XmlTestCase-context.xml8
-rw-r--r--spring-test/src/test/resources/org/springframework/test/context/web/BasicXmlWacTests-context.xml7
-rw-r--r--spring-test/src/test/resources/org/springframework/test/context/web/RequestAndSessionScopedBeansWacTests-context.xml11
-rw-r--r--spring-test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments.sql16
397 files changed, 41303 insertions, 0 deletions
diff --git a/spring-test/src/main/java/org/springframework/mock/env/MockEnvironment.java b/spring-test/src/main/java/org/springframework/mock/env/MockEnvironment.java
new file mode 100644
index 00000000..8042e6d1
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/env/MockEnvironment.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2012 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.mock.env;
+
+import org.springframework.core.env.AbstractEnvironment;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+/**
+ * Simple {@link ConfigurableEnvironment} implementation exposing
+ * {@link #setProperty(String, String)} and {@link #withProperty(String, String)}
+ * methods for testing purposes.
+ *
+ * @author Chris Beams
+ * @author Sam Brannen
+ * @since 3.2
+ * @see org.springframework.mock.env.MockPropertySource
+ */
+public class MockEnvironment extends AbstractEnvironment {
+
+ private MockPropertySource propertySource = new MockPropertySource();
+
+ /**
+ * Create a new {@code MockEnvironment} with a single {@link MockPropertySource}.
+ */
+ public MockEnvironment() {
+ getPropertySources().addLast(propertySource);
+ }
+
+ /**
+ * Set a property on the underlying {@link MockPropertySource} for this environment.
+ */
+ public void setProperty(String key, String value) {
+ propertySource.setProperty(key, value);
+ }
+
+ /**
+ * Convenient synonym for {@link #setProperty} that returns the current instance.
+ * Useful for method chaining and fluent-style use.
+ * @return this {@link MockEnvironment} instance
+ * @see MockPropertySource#withProperty
+ */
+ public MockEnvironment withProperty(String key, String value) {
+ this.setProperty(key, value);
+ return this;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/env/MockPropertySource.java b/spring-test/src/main/java/org/springframework/mock/env/MockPropertySource.java
new file mode 100644
index 00000000..124637e9
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/env/MockPropertySource.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2012 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.mock.env;
+
+import java.util.Properties;
+
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+
+/**
+ * Simple {@link PropertySource} implementation for use in testing. Accepts
+ * a user-provided {@link Properties} object, or if omitted during construction,
+ * the implementation will initialize its own.
+ *
+ * The {@link #setProperty} and {@link #withProperty} methods are exposed for
+ * convenience, for example:
+ * <pre class="code">
+ * {@code
+ * PropertySource<?> source = new MockPropertySource().withProperty("foo", "bar");
+ * }
+ * </pre>
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see org.springframework.mock.env.MockEnvironment
+ */
+public class MockPropertySource extends PropertiesPropertySource {
+
+ /**
+ * {@value} is the default name for {@link MockPropertySource} instances not
+ * otherwise given an explicit name.
+ * @see #MockPropertySource()
+ * @see #MockPropertySource(String)
+ */
+ public static final String MOCK_PROPERTIES_PROPERTY_SOURCE_NAME = "mockProperties";
+
+ /**
+ * Create a new {@code MockPropertySource} named {@value #MOCK_PROPERTIES_PROPERTY_SOURCE_NAME}
+ * that will maintain its own internal {@link Properties} instance.
+ */
+ public MockPropertySource() {
+ this(new Properties());
+ }
+
+ /**
+ * Create a new {@code MockPropertySource} with the given name that will
+ * maintain its own internal {@link Properties} instance.
+ * @param name the {@linkplain #getName() name} of the property source
+ */
+ public MockPropertySource(String name) {
+ this(name, new Properties());
+ }
+
+ /**
+ * Create a new {@code MockPropertySource} named {@value #MOCK_PROPERTIES_PROPERTY_SOURCE_NAME}
+ * and backed by the given {@link Properties} object.
+ * @param properties the properties to use
+ */
+ public MockPropertySource(Properties properties) {
+ this(MOCK_PROPERTIES_PROPERTY_SOURCE_NAME, properties);
+ }
+
+ /**
+ * Create a new {@code MockPropertySource} with the given name and backed by the given
+ * {@link Properties} object.
+ * @param name the {@linkplain #getName() name} of the property source
+ * @param properties the properties to use
+ */
+ public MockPropertySource(String name, Properties properties) {
+ super(name, properties);
+ }
+
+ /**
+ * Set the given property on the underlying {@link Properties} object.
+ */
+ public void setProperty(String name, Object value) {
+ this.source.put(name, value);
+ }
+
+ /**
+ * Convenient synonym for {@link #setProperty} that returns the current instance.
+ * Useful for method chaining and fluent-style use.
+ * @return this {@link MockPropertySource} instance
+ */
+ public MockPropertySource withProperty(String name, Object value) {
+ this.setProperty(name, value);
+ return this;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/env/package-info.java b/spring-test/src/main/java/org/springframework/mock/env/package-info.java
new file mode 100644
index 00000000..1772b5c9
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/env/package-info.java
@@ -0,0 +1,10 @@
+/**
+ * This package contains mock implementations of the
+ * {@link org.springframework.core.env.Environment Environment} and
+ * {@link org.springframework.core.env.PropertySource PropertySource}
+ * abstractions introduced in Spring 3.1.
+ *
+ * <p>These <em>mocks</em> are useful for developing <em>out-of-container</em>
+ * unit tests for code that depends on environment-specific properties.
+ */
+package org.springframework.mock.env;
diff --git a/spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java b/spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java
new file mode 100644
index 00000000..5de14e99
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2012 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.mock.http;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of {@link HttpInputMessage}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+public class MockHttpInputMessage implements HttpInputMessage {
+
+ private final HttpHeaders headers = new HttpHeaders();
+
+ private final InputStream body;
+
+
+ public MockHttpInputMessage(byte[] contents) {
+ this.body = (contents != null) ? new ByteArrayInputStream(contents) : null;
+ }
+
+ public MockHttpInputMessage(InputStream body) {
+ Assert.notNull(body, "'body' must not be null");
+ this.body = body;
+ }
+
+ public HttpHeaders getHeaders() {
+ return this.headers;
+ }
+
+ public InputStream getBody() throws IOException {
+ return this.body;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java b/spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java
new file mode 100644
index 00000000..a4ef87e7
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java
@@ -0,0 +1,86 @@
+/*
+ * 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.mock.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpOutputMessage;
+
+/**
+ * Mock implementation of {@link HttpOutputMessage}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+public class MockHttpOutputMessage implements HttpOutputMessage {
+
+ private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+ private final HttpHeaders headers = new HttpHeaders();
+
+ private final ByteArrayOutputStream body = new ByteArrayOutputStream(1024);
+
+
+ /**
+ * Return the headers.
+ */
+ public HttpHeaders getHeaders() {
+ return this.headers;
+ }
+
+ /**
+ * Return the body content.
+ */
+ public OutputStream getBody() throws IOException {
+ return this.body;
+ }
+
+ /**
+ * Return body content as a byte array.
+ */
+ public byte[] getBodyAsBytes() {
+ return this.body.toByteArray();
+ }
+
+ /**
+ * Return the body content interpreted as a UTF-8 string.
+ */
+ public String getBodyAsString() {
+ return getBodyAsString(DEFAULT_CHARSET);
+ }
+
+ /**
+ * Return the body content as a string.
+ * @param charset the charset to use to turn the body content to a String
+ */
+ public String getBodyAsString(Charset charset) {
+ byte[] bytes = getBodyAsBytes();
+ try {
+ return new String(bytes, charset.name());
+ }
+ catch (UnsupportedEncodingException ex) {
+ // should not occur
+ throw new IllegalStateException(ex);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java
new file mode 100644
index 00000000..0f46bbbc
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2002-2012 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.mock.http.client;
+
+import java.io.IOException;
+import java.net.URI;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.http.client.ClientHttpRequest;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.mock.http.MockHttpOutputMessage;
+
+/**
+ * Mock implementation of {@link ClientHttpRequest}.
+ *
+ * @author Rossen Stoyanchev
+ * @author Sam Brannen
+ * @since 3.2
+ */
+public class MockClientHttpRequest extends MockHttpOutputMessage implements ClientHttpRequest {
+
+ private URI uri;
+
+ private HttpMethod httpMethod;
+
+ private boolean executed = false;
+
+ private ClientHttpResponse clientHttpResponse;
+
+
+ /**
+ * Default constructor.
+ */
+ public MockClientHttpRequest() {
+ }
+
+ /**
+ * Create an instance with the given HttpMethod and URI.
+ */
+ public MockClientHttpRequest(HttpMethod httpMethod, URI uri) {
+ this.httpMethod = httpMethod;
+ this.uri = uri;
+ }
+
+ public URI getURI() {
+ return this.uri;
+ }
+
+ public void setURI(URI uri) {
+ this.uri = uri;
+ }
+
+ public HttpMethod getMethod() {
+ return this.httpMethod;
+ }
+
+ public void setMethod(HttpMethod httpMethod) {
+ this.httpMethod = httpMethod;
+ }
+
+ public void setResponse(ClientHttpResponse clientHttpResponse) {
+ this.clientHttpResponse = clientHttpResponse;
+ }
+
+ public boolean isExecuted() {
+ return this.executed;
+ }
+
+ /**
+ * Set the {@link #isExecuted() executed} flag to {@code true} and return the
+ * configured {@link #setResponse(ClientHttpResponse) response}.
+ * @see #executeInternal()
+ */
+ public final ClientHttpResponse execute() throws IOException {
+ this.executed = true;
+ return executeInternal();
+ }
+
+ /**
+ * The default implementation returns the configured
+ * {@link #setResponse(ClientHttpResponse) response}.
+ *
+ * <p>Override this method to execute the request and provide a response,
+ * potentially different than the configured response.
+ */
+ protected ClientHttpResponse executeInternal() throws IOException {
+ return this.clientHttpResponse;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (this.httpMethod != null) {
+ sb.append(this.httpMethod);
+ }
+ if (this.uri != null) {
+ sb.append(" ").append(this.uri);
+ }
+ if (!getHeaders().isEmpty()) {
+ sb.append(", headers : ").append(getHeaders());
+ }
+ if (sb.length() == 0) {
+ sb.append("Not yet initialized");
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpResponse.java b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpResponse.java
new file mode 100644
index 00000000..9ed40774
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/http/client/MockClientHttpResponse.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2012 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.mock.http.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.mock.http.MockHttpInputMessage;
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of {@link ClientHttpResponse}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ */
+public class MockClientHttpResponse extends MockHttpInputMessage implements ClientHttpResponse {
+
+ private final HttpStatus status;
+
+
+ /**
+ * Constructor with response body as a byte array.
+ */
+ public MockClientHttpResponse(byte[] body, HttpStatus statusCode) {
+ super(body);
+ Assert.notNull(statusCode, "statisCode is required");
+ this.status = statusCode;
+ }
+
+ /**
+ * Constructor with response body as InputStream.
+ */
+ public MockClientHttpResponse(InputStream body, HttpStatus statusCode) {
+ super(body);
+ Assert.notNull(statusCode, "statisCode is required");
+ this.status = statusCode;
+ }
+
+ public HttpStatus getStatusCode() throws IOException {
+ return this.status;
+ }
+
+ public int getRawStatusCode() throws IOException {
+ return this.status.value();
+ }
+
+ public String getStatusText() throws IOException {
+ return this.status.getReasonPhrase();
+ }
+
+ public void close() {
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/http/client/package-info.java b/spring-test/src/main/java/org/springframework/mock/http/client/package-info.java
new file mode 100644
index 00000000..927dd34e
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/http/client/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2002-2012 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.
+ */
+
+/**
+ * Mock implementations of client-side HTTP abstractions.
+ * This package contains the {@code MockClientHttpRequest} and
+ * {@code MockClientHttpResponse}.
+ */
+package org.springframework.mock.http.client;
+
diff --git a/spring-test/src/main/java/org/springframework/mock/http/package-info.java b/spring-test/src/main/java/org/springframework/mock/http/package-info.java
new file mode 100644
index 00000000..13cf4967
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/http/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2002-2012 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.
+ */
+
+/**
+ * Mock implementations of client/server-side HTTP abstractions.
+ * This package contains {@code MockHttpInputMessage} and
+ * {@code MockHttpOutputMessage}.
+ */
+package org.springframework.mock.http;
+
diff --git a/spring-test/src/main/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java b/spring-test/src/main/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java
new file mode 100644
index 00000000..b19aaf5c
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2012 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.mock.jndi;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.naming.NamingException;
+
+import org.springframework.jndi.JndiTemplate;
+
+/**
+ * Simple extension of the JndiTemplate class that always returns a given object.
+ *
+ * <p>Very useful for testing. Effectively a mock object.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ */
+public class ExpectedLookupTemplate extends JndiTemplate {
+
+ private final Map<String, Object> jndiObjects = new ConcurrentHashMap<String, Object>(16);
+
+
+ /**
+ * Construct a new JndiTemplate that will always return given objects for
+ * given names. To be populated through {@code addObject} calls.
+ * @see #addObject(String, Object)
+ */
+ public ExpectedLookupTemplate() {
+ }
+
+ /**
+ * Construct a new JndiTemplate that will always return the given object,
+ * but honour only requests for the given name.
+ * @param name the name the client is expected to look up
+ * @param object the object that will be returned
+ */
+ public ExpectedLookupTemplate(String name, Object object) {
+ addObject(name, object);
+ }
+
+ /**
+ * Add the given object to the list of JNDI objects that this template will expose.
+ * @param name the name the client is expected to look up
+ * @param object the object that will be returned
+ */
+ public void addObject(String name, Object object) {
+ this.jndiObjects.put(name, object);
+ }
+
+ /**
+ * If the name is the expected name specified in the constructor, return the
+ * object provided in the constructor. If the name is unexpected, a
+ * respective NamingException gets thrown.
+ */
+ public Object lookup(String name) throws NamingException {
+ Object object = this.jndiObjects.get(name);
+ if (object == null) {
+ throw new NamingException("Unexpected JNDI name '" + name + "': expecting " + this.jndiObjects.keySet());
+ }
+ return object;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContext.java b/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContext.java
new file mode 100644
index 00000000..d35fbdc8
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContext.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2002-2012 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.mock.jndi;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import javax.naming.Binding;
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
+import javax.naming.NameParser;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.OperationNotSupportedException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * Simple implementation of a JNDI naming context.
+ * Only supports binding plain Objects to String names.
+ * Mainly for test environments, but also usable for standalone applications.
+ *
+ * <p>This class is not intended for direct usage by applications, although it
+ * can be used for example to override JndiTemplate's {@code createInitialContext}
+ * method in unit tests. Typically, SimpleNamingContextBuilder will be used to
+ * set up a JVM-level JNDI environment.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see SimpleNamingContextBuilder
+ * @see org.springframework.jndi.JndiTemplate#createInitialContext
+ */
+public class SimpleNamingContext implements Context {
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final String root;
+
+ private final Hashtable<String, Object> boundObjects;
+
+ private final Hashtable<String, Object> environment = new Hashtable<String, Object>();
+
+
+ /**
+ * Create a new naming context.
+ */
+ public SimpleNamingContext() {
+ this("");
+ }
+
+ /**
+ * Create a new naming context with the given naming root.
+ */
+ public SimpleNamingContext(String root) {
+ this.root = root;
+ this.boundObjects = new Hashtable<String, Object>();
+ }
+
+ /**
+ * Create a new naming context with the given naming root,
+ * the given name/object map, and the JNDI environment entries.
+ */
+ public SimpleNamingContext(String root, Hashtable<String, Object> boundObjects, Hashtable<String, Object> env) {
+ this.root = root;
+ this.boundObjects = boundObjects;
+ if (env != null) {
+ this.environment.putAll(env);
+ }
+ }
+
+
+ // Actual implementations of Context methods follow
+
+ public NamingEnumeration<NameClassPair> list(String root) throws NamingException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Listing name/class pairs under [" + root + "]");
+ }
+ return new NameClassPairEnumeration(this, root);
+ }
+
+ public NamingEnumeration<Binding> listBindings(String root) throws NamingException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Listing bindings under [" + root + "]");
+ }
+ return new BindingEnumeration(this, root);
+ }
+
+ /**
+ * Look up the object with the given name.
+ * <p>Note: Not intended for direct use by applications.
+ * Will be used by any standard InitialContext JNDI lookups.
+ * @throws javax.naming.NameNotFoundException if the object could not be found
+ */
+ public Object lookup(String lookupName) throws NameNotFoundException {
+ String name = this.root + lookupName;
+ if (logger.isDebugEnabled()) {
+ logger.debug("Static JNDI lookup: [" + name + "]");
+ }
+ if ("".equals(name)) {
+ return new SimpleNamingContext(this.root, this.boundObjects, this.environment);
+ }
+ Object found = this.boundObjects.get(name);
+ if (found == null) {
+ if (!name.endsWith("/")) {
+ name = name + "/";
+ }
+ for (String boundName : this.boundObjects.keySet()) {
+ if (boundName.startsWith(name)) {
+ return new SimpleNamingContext(name, this.boundObjects, this.environment);
+ }
+ }
+ throw new NameNotFoundException(
+ "Name [" + this.root + lookupName + "] not bound; " + this.boundObjects.size() + " bindings: [" +
+ StringUtils.collectionToDelimitedString(this.boundObjects.keySet(), ",") + "]");
+ }
+ return found;
+ }
+
+ public Object lookupLink(String name) throws NameNotFoundException {
+ return lookup(name);
+ }
+
+ /**
+ * Bind the given object to the given name.
+ * Note: Not intended for direct use by applications
+ * if setting up a JVM-level JNDI environment.
+ * Use SimpleNamingContextBuilder to set up JNDI bindings then.
+ * @see org.springframework.mock.jndi.SimpleNamingContextBuilder#bind
+ */
+ public void bind(String name, Object obj) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Static JNDI binding: [" + this.root + name + "] = [" + obj + "]");
+ }
+ this.boundObjects.put(this.root + name, obj);
+ }
+
+ public void unbind(String name) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Static JNDI remove: [" + this.root + name + "]");
+ }
+ this.boundObjects.remove(this.root + name);
+ }
+
+ public void rebind(String name, Object obj) {
+ bind(name, obj);
+ }
+
+ public void rename(String oldName, String newName) throws NameNotFoundException {
+ Object obj = lookup(oldName);
+ unbind(oldName);
+ bind(newName, obj);
+ }
+
+ public Context createSubcontext(String name) {
+ String subcontextName = this.root + name;
+ if (!subcontextName.endsWith("/")) {
+ subcontextName += "/";
+ }
+ Context subcontext = new SimpleNamingContext(subcontextName, this.boundObjects, this.environment);
+ bind(name, subcontext);
+ return subcontext;
+ }
+
+ public void destroySubcontext(String name) {
+ unbind(name);
+ }
+
+ public String composeName(String name, String prefix) {
+ return prefix + name;
+ }
+
+ public Hashtable<String, Object> getEnvironment() {
+ return this.environment;
+ }
+
+ public Object addToEnvironment(String propName, Object propVal) {
+ return this.environment.put(propName, propVal);
+ }
+
+ public Object removeFromEnvironment(String propName) {
+ return this.environment.remove(propName);
+ }
+
+ public void close() {
+ }
+
+
+ // Unsupported methods follow: no support for javax.naming.Name
+
+ public NamingEnumeration<NameClassPair> list(Name name) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public NamingEnumeration<Binding> listBindings(Name name) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public Object lookup(Name name) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public Object lookupLink(Name name) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public void bind(Name name, Object obj) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public void unbind(Name name) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public void rebind(Name name, Object obj) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public void rename(Name oldName, Name newName) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public Context createSubcontext(Name name) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public void destroySubcontext(Name name) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public String getNameInNamespace() throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public NameParser getNameParser(Name name) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public NameParser getNameParser(String name) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public Name composeName(Name name, Name prefix) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+
+ private static abstract class AbstractNamingEnumeration<T> implements NamingEnumeration<T> {
+
+ private Iterator<T> iterator;
+
+ private AbstractNamingEnumeration(SimpleNamingContext context, String proot) throws NamingException {
+ if (!"".equals(proot) && !proot.endsWith("/")) {
+ proot = proot + "/";
+ }
+ String root = context.root + proot;
+ Map<String, T> contents = new HashMap<String, T>();
+ for (String boundName : context.boundObjects.keySet()) {
+ if (boundName.startsWith(root)) {
+ int startIndex = root.length();
+ int endIndex = boundName.indexOf('/', startIndex);
+ String strippedName =
+ (endIndex != -1 ? boundName.substring(startIndex, endIndex) : boundName.substring(startIndex));
+ if (!contents.containsKey(strippedName)) {
+ try {
+ contents.put(strippedName, createObject(strippedName, context.lookup(proot + strippedName)));
+ }
+ catch (NameNotFoundException ex) {
+ // cannot happen
+ }
+ }
+ }
+ }
+ if (contents.size() == 0) {
+ throw new NamingException("Invalid root: [" + context.root + proot + "]");
+ }
+ this.iterator = contents.values().iterator();
+ }
+
+ protected abstract T createObject(String strippedName, Object obj);
+
+ public boolean hasMore() {
+ return this.iterator.hasNext();
+ }
+
+ public T next() {
+ return this.iterator.next();
+ }
+
+ public boolean hasMoreElements() {
+ return this.iterator.hasNext();
+ }
+
+ public T nextElement() {
+ return this.iterator.next();
+ }
+
+ public void close() {
+ }
+ }
+
+
+ private static class NameClassPairEnumeration extends AbstractNamingEnumeration<NameClassPair> {
+
+ private NameClassPairEnumeration(SimpleNamingContext context, String root) throws NamingException {
+ super(context, root);
+ }
+
+ protected NameClassPair createObject(String strippedName, Object obj) {
+ return new NameClassPair(strippedName, obj.getClass().getName());
+ }
+ }
+
+
+ private static class BindingEnumeration extends AbstractNamingEnumeration<Binding> {
+
+ private BindingEnumeration(SimpleNamingContext context, String root) throws NamingException {
+ super(context, root);
+ }
+
+ protected Binding createObject(String strippedName, Object obj) {
+ return new Binding(strippedName, obj);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java b/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java
new file mode 100644
index 00000000..f813203b
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java
@@ -0,0 +1,230 @@
+/*
+ * 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.mock.jndi;
+
+import java.util.Hashtable;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.spi.InitialContextFactory;
+import javax.naming.spi.InitialContextFactoryBuilder;
+import javax.naming.spi.NamingManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.ClassUtils;
+
+/**
+ * Simple implementation of a JNDI naming context builder.
+ *
+ * <p>Mainly targeted at test environments, where each test case can
+ * configure JNDI appropriately, so that {@code new InitialContext()}
+ * will expose the required objects. Also usable for standalone applications,
+ * e.g. for binding a JDBC DataSource to a well-known JNDI location, to be
+ * able to use traditional J2EE data access code outside of a J2EE container.
+ *
+ * <p>There are various choices for DataSource implementations:
+ * <ul>
+ * <li>{@code SingleConnectionDataSource} (using the same Connection for all getConnection calls)
+ * <li>{@code DriverManagerDataSource} (creating a new Connection on each getConnection call)
+ * <li>Apache's Jakarta Commons DBCP offers {@code org.apache.commons.dbcp.BasicDataSource} (a real pool)
+ * </ul>
+ *
+ * <p>Typical usage in bootstrap code:
+ *
+ * <pre class="code">
+ * SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
+ * DataSource ds = new DriverManagerDataSource(...);
+ * builder.bind("java:comp/env/jdbc/myds", ds);
+ * builder.activate();</pre>
+ *
+ * Note that it's impossible to activate multiple builders within the same JVM,
+ * due to JNDI restrictions. Thus to configure a fresh builder repeatedly, use
+ * the following code to get a reference to either an already activated builder
+ * or a newly activated one:
+ *
+ * <pre class="code">
+ * SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
+ * DataSource ds = new DriverManagerDataSource(...);
+ * builder.bind("java:comp/env/jdbc/myds", ds);</pre>
+ *
+ * Note that you <i>should not</i> call {@code activate()} on a builder from
+ * this factory method, as there will already be an activated one in any case.
+ *
+ * <p>An instance of this class is only necessary at setup time.
+ * An application does not need to keep a reference to it after activation.
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @see #emptyActivatedContextBuilder()
+ * @see #bind(String, Object)
+ * @see #activate()
+ * @see SimpleNamingContext
+ * @see org.springframework.jdbc.datasource.SingleConnectionDataSource
+ * @see org.springframework.jdbc.datasource.DriverManagerDataSource
+ */
+public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder {
+
+ /** An instance of this class bound to JNDI */
+ private static volatile SimpleNamingContextBuilder activated;
+
+ private static boolean initialized = false;
+
+ private static final Object initializationLock = new Object();
+
+
+ /**
+ * Checks if a SimpleNamingContextBuilder is active.
+ * @return the current SimpleNamingContextBuilder instance,
+ * or {@code null} if none
+ */
+ public static SimpleNamingContextBuilder getCurrentContextBuilder() {
+ return activated;
+ }
+
+ /**
+ * If no SimpleNamingContextBuilder is already configuring JNDI,
+ * create and activate one. Otherwise take the existing activate
+ * SimpleNamingContextBuilder, clear it and return it.
+ * <p>This is mainly intended for test suites that want to
+ * reinitialize JNDI bindings from scratch repeatedly.
+ * @return an empty SimpleNamingContextBuilder that can be used
+ * to control JNDI bindings
+ */
+ public static SimpleNamingContextBuilder emptyActivatedContextBuilder() throws NamingException {
+ if (activated != null) {
+ // Clear already activated context builder.
+ activated.clear();
+ }
+ else {
+ // Create and activate new context builder.
+ SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
+ // The activate() call will cause an assignment to the activated variable.
+ builder.activate();
+ }
+ return activated;
+ }
+
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final Hashtable<String,Object> boundObjects = new Hashtable<String,Object>();
+
+
+ /**
+ * Register the context builder by registering it with the JNDI NamingManager.
+ * Note that once this has been done, {@code new InitialContext()} will always
+ * return a context from this factory. Use the {@code emptyActivatedContextBuilder()}
+ * static method to get an empty context (for example, in test methods).
+ * @throws IllegalStateException if there's already a naming context builder
+ * registered with the JNDI NamingManager
+ */
+ public void activate() throws IllegalStateException, NamingException {
+ logger.info("Activating simple JNDI environment");
+ synchronized (initializationLock) {
+ if (!initialized) {
+ if (NamingManager.hasInitialContextFactoryBuilder()) {
+ throw new IllegalStateException(
+ "Cannot activate SimpleNamingContextBuilder: there is already a JNDI provider registered. " +
+ "Note that JNDI is a JVM-wide service, shared at the JVM system class loader level, " +
+ "with no reset option. As a consequence, a JNDI provider must only be registered once per JVM.");
+ }
+ NamingManager.setInitialContextFactoryBuilder(this);
+ initialized = true;
+ }
+ }
+ activated = this;
+ }
+
+ /**
+ * Temporarily deactivate this context builder. It will remain registered with
+ * the JNDI NamingManager but will delegate to the standard JNDI InitialContextFactory
+ * (if configured) instead of exposing its own bound objects.
+ * <p>Call {@code activate()} again in order to expose this context builder's own
+ * bound objects again. Such activate/deactivate sequences can be applied any number
+ * of times (e.g. within a larger integration test suite running in the same VM).
+ * @see #activate()
+ */
+ public void deactivate() {
+ logger.info("Deactivating simple JNDI environment");
+ activated = null;
+ }
+
+ /**
+ * Clear all bindings in this context builder, while keeping it active.
+ */
+ public void clear() {
+ this.boundObjects.clear();
+ }
+
+ /**
+ * Bind the given object under the given name, for all naming contexts
+ * that this context builder will generate.
+ * @param name the JNDI name of the object (e.g. "java:comp/env/jdbc/myds")
+ * @param obj the object to bind (e.g. a DataSource implementation)
+ */
+ public void bind(String name, Object obj) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Static JNDI binding: [" + name + "] = [" + obj + "]");
+ }
+ this.boundObjects.put(name, obj);
+ }
+
+
+ /**
+ * Simple InitialContextFactoryBuilder implementation,
+ * creating a new SimpleNamingContext instance.
+ * @see SimpleNamingContext
+ */
+ public InitialContextFactory createInitialContextFactory(Hashtable<?,?> environment) {
+ if (activated == null && environment != null) {
+ Object icf = environment.get(Context.INITIAL_CONTEXT_FACTORY);
+ if (icf != null) {
+ Class<?> icfClass;
+ if (icf instanceof Class) {
+ icfClass = (Class<?>) icf;
+ }
+ else if (icf instanceof String) {
+ icfClass = ClassUtils.resolveClassName((String) icf, getClass().getClassLoader());
+ }
+ else {
+ throw new IllegalArgumentException("Invalid value type for environment key [" +
+ Context.INITIAL_CONTEXT_FACTORY + "]: " + icf.getClass().getName());
+ }
+ if (!InitialContextFactory.class.isAssignableFrom(icfClass)) {
+ throw new IllegalArgumentException(
+ "Specified class does not implement [" + InitialContextFactory.class.getName() + "]: " + icf);
+ }
+ try {
+ return (InitialContextFactory) icfClass.newInstance();
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Cannot instantiate specified InitialContextFactory: " + icf, ex);
+ }
+ }
+ }
+
+ // Default case...
+ return new InitialContextFactory() {
+ @SuppressWarnings("unchecked")
+ public Context getInitialContext(Hashtable<?,?> environment) {
+ return new SimpleNamingContext("", boundObjects, (Hashtable<String, Object>) environment);
+ }
+ };
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/jndi/package-info.java b/spring-test/src/main/java/org/springframework/mock/jndi/package-info.java
new file mode 100644
index 00000000..992e2369
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/jndi/package-info.java
@@ -0,0 +1,13 @@
+
+/**
+ *
+ * The simplest implementation of the JNDI SPI that could possibly work.
+ *
+ * <p>Useful for setting up a simple JNDI environment for test suites
+ * or stand-alone applications. If, for example, JDBC DataSources get bound to the
+ * same JNDI names as within a Java EE container, both application code and
+ * configuration can be reused without changes.
+ *
+ */
+package org.springframework.mock.jndi;
+
diff --git a/spring-test/src/main/java/org/springframework/mock/web/DelegatingServletInputStream.java b/spring-test/src/main/java/org/springframework/mock/web/DelegatingServletInputStream.java
new file mode 100644
index 00000000..f97e60e2
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/DelegatingServletInputStream.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2012 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.mock.web;
+
+import java.io.IOException;
+import java.io.InputStream;
+import javax.servlet.ServletInputStream;
+
+import org.springframework.util.Assert;
+
+/**
+ * Delegating implementation of {@link javax.servlet.ServletInputStream}.
+ *
+ * <p>Used by {@link MockHttpServletRequest}; typically not directly
+ * used for testing application controllers.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ * @see MockHttpServletRequest
+ */
+public class DelegatingServletInputStream extends ServletInputStream {
+
+ private final InputStream sourceStream;
+
+
+ /**
+ * Create a DelegatingServletInputStream for the given source stream.
+ * @param sourceStream the source stream (never {@code null})
+ */
+ public DelegatingServletInputStream(InputStream sourceStream) {
+ Assert.notNull(sourceStream, "Source InputStream must not be null");
+ this.sourceStream = sourceStream;
+ }
+
+ /**
+ * Return the underlying source stream (never {@code null}).
+ */
+ public final InputStream getSourceStream() {
+ return this.sourceStream;
+ }
+
+
+ public int read() throws IOException {
+ return this.sourceStream.read();
+ }
+
+ public void close() throws IOException {
+ super.close();
+ this.sourceStream.close();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/DelegatingServletOutputStream.java b/spring-test/src/main/java/org/springframework/mock/web/DelegatingServletOutputStream.java
new file mode 100644
index 00000000..23694170
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/DelegatingServletOutputStream.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2012 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.mock.web;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import javax.servlet.ServletOutputStream;
+
+import org.springframework.util.Assert;
+
+/**
+ * Delegating implementation of {@link javax.servlet.ServletOutputStream}.
+ *
+ * <p>Used by {@link MockHttpServletResponse}; typically not directly
+ * used for testing application controllers.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ * @see MockHttpServletResponse
+ */
+public class DelegatingServletOutputStream extends ServletOutputStream {
+
+ private final OutputStream targetStream;
+
+
+ /**
+ * Create a DelegatingServletOutputStream for the given target stream.
+ * @param targetStream the target stream (never {@code null})
+ */
+ public DelegatingServletOutputStream(OutputStream targetStream) {
+ Assert.notNull(targetStream, "Target OutputStream must not be null");
+ this.targetStream = targetStream;
+ }
+
+ /**
+ * Return the underlying target stream (never {@code null}).
+ */
+ public final OutputStream getTargetStream() {
+ return this.targetStream;
+ }
+
+
+ public void write(int b) throws IOException {
+ this.targetStream.write(b);
+ }
+
+ public void flush() throws IOException {
+ super.flush();
+ this.targetStream.flush();
+ }
+
+ public void close() throws IOException {
+ super.close();
+ this.targetStream.close();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/HeaderValueHolder.java b/spring-test/src/main/java/org/springframework/mock/web/HeaderValueHolder.java
new file mode 100644
index 00000000..e8f5e91d
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/HeaderValueHolder.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2012 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.mock.web;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Internal helper class that serves as value holder for request headers.
+ *
+ * @author Juergen Hoeller
+ * @author Rick Evans
+ * @since 2.0.1
+ */
+class HeaderValueHolder {
+
+ private final List<Object> values = new LinkedList<Object>();
+
+
+ public void setValue(Object value) {
+ this.values.clear();
+ this.values.add(value);
+ }
+
+ public void addValue(Object value) {
+ this.values.add(value);
+ }
+
+ public void addValues(Collection<?> values) {
+ this.values.addAll(values);
+ }
+
+ public void addValueArray(Object values) {
+ CollectionUtils.mergeArrayIntoCollection(values, this.values);
+ }
+
+ public List<Object> getValues() {
+ return Collections.unmodifiableList(this.values);
+ }
+
+ public List<String> getStringValues() {
+ List<String> stringList = new ArrayList<String>(this.values.size());
+ for (Object value : this.values) {
+ stringList.add(value.toString());
+ }
+ return Collections.unmodifiableList(stringList);
+ }
+
+ public Object getValue() {
+ return (!this.values.isEmpty() ? this.values.get(0) : null);
+ }
+
+ public String getStringValue() {
+ return (!this.values.isEmpty() ? this.values.get(0).toString() : null);
+ }
+
+
+ /**
+ * Find a HeaderValueHolder by name, ignoring casing.
+ * @param headers the Map of header names to HeaderValueHolders
+ * @param name the name of the desired header
+ * @return the corresponding HeaderValueHolder,
+ * or {@code null} if none found
+ */
+ public static HeaderValueHolder getByName(Map<String, HeaderValueHolder> headers, String name) {
+ Assert.notNull(name, "Header name must not be null");
+ for (String headerName : headers.keySet()) {
+ if (headerName.equalsIgnoreCase(name)) {
+ return headers.get(headerName);
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockBodyContent.java b/spring-test/src/main/java/org/springframework/mock/web/MockBodyContent.java
new file mode 100644
index 00000000..3fb6ca77
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockBodyContent.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2002-2009 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.mock.web;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.Writer;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.tagext.BodyContent;
+
+/**
+ * Mock implementation of the {@link javax.servlet.jsp.tagext.BodyContent} class.
+ *
+ * <p>Used for testing the web framework; only necessary for testing
+ * applications when testing custom JSP tags.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class MockBodyContent extends BodyContent {
+
+ private final String content;
+
+
+ /**
+ * Create a MockBodyContent for the given response.
+ * @param content the body content to expose
+ * @param response the servlet response to wrap
+ */
+ public MockBodyContent(String content, HttpServletResponse response) {
+ this(content, response, null);
+ }
+
+ /**
+ * Create a MockBodyContent for the given response.
+ * @param content the body content to expose
+ * @param targetWriter the target Writer to wrap
+ */
+ public MockBodyContent(String content, Writer targetWriter) {
+ this(content, null, targetWriter);
+ }
+
+ /**
+ * Create a MockBodyContent for the given response.
+ * @param content the body content to expose
+ * @param response the servlet response to wrap
+ * @param targetWriter the target Writer to wrap
+ */
+ public MockBodyContent(String content, HttpServletResponse response, Writer targetWriter) {
+ super(adaptJspWriter(targetWriter, response));
+ this.content = content;
+ }
+
+ private static JspWriter adaptJspWriter(Writer targetWriter, HttpServletResponse response) {
+ if (targetWriter instanceof JspWriter) {
+ return (JspWriter) targetWriter;
+ }
+ else {
+ return new MockJspWriter(response, targetWriter);
+ }
+ }
+
+
+ public Reader getReader() {
+ return new StringReader(this.content);
+ }
+
+ public String getString() {
+ return this.content;
+ }
+
+ public void writeOut(Writer writer) throws IOException {
+ writer.write(this.content);
+ }
+
+
+ //---------------------------------------------------------------------
+ // Delegating implementations of JspWriter's abstract methods
+ //---------------------------------------------------------------------
+
+ public void clear() throws IOException {
+ getEnclosingWriter().clear();
+ }
+
+ public void clearBuffer() throws IOException {
+ getEnclosingWriter().clearBuffer();
+ }
+
+ public void close() throws IOException {
+ getEnclosingWriter().close();
+ }
+
+ public int getRemaining() {
+ return getEnclosingWriter().getRemaining();
+ }
+
+ public void newLine() throws IOException {
+ getEnclosingWriter().println();
+ }
+
+ public void write(char value[], int offset, int length) throws IOException {
+ getEnclosingWriter().write(value, offset, length);
+ }
+
+ public void print(boolean value) throws IOException {
+ getEnclosingWriter().print(value);
+ }
+
+ public void print(char value) throws IOException {
+ getEnclosingWriter().print(value);
+ }
+
+ public void print(char[] value) throws IOException {
+ getEnclosingWriter().print(value);
+ }
+
+ public void print(double value) throws IOException {
+ getEnclosingWriter().print(value);
+ }
+
+ public void print(float value) throws IOException {
+ getEnclosingWriter().print(value);
+ }
+
+ public void print(int value) throws IOException {
+ getEnclosingWriter().print(value);
+ }
+
+ public void print(long value) throws IOException {
+ getEnclosingWriter().print(value);
+ }
+
+ public void print(Object value) throws IOException {
+ getEnclosingWriter().print(value);
+ }
+
+ public void print(String value) throws IOException {
+ getEnclosingWriter().print(value);
+ }
+
+ public void println() throws IOException {
+ getEnclosingWriter().println();
+ }
+
+ public void println(boolean value) throws IOException {
+ getEnclosingWriter().println(value);
+ }
+
+ public void println(char value) throws IOException {
+ getEnclosingWriter().println(value);
+ }
+
+ public void println(char[] value) throws IOException {
+ getEnclosingWriter().println(value);
+ }
+
+ public void println(double value) throws IOException {
+ getEnclosingWriter().println(value);
+ }
+
+ public void println(float value) throws IOException {
+ getEnclosingWriter().println(value);
+ }
+
+ public void println(int value) throws IOException {
+ getEnclosingWriter().println(value);
+ }
+
+ public void println(long value) throws IOException {
+ getEnclosingWriter().println(value);
+ }
+
+ public void println(Object value) throws IOException {
+ getEnclosingWriter().println(value);
+ }
+
+ public void println(String value) throws IOException {
+ getEnclosingWriter().println(value);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockExpressionEvaluator.java b/spring-test/src/main/java/org/springframework/mock/web/MockExpressionEvaluator.java
new file mode 100644
index 00000000..6b69fdb7
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockExpressionEvaluator.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2012 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.mock.web;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.Expression;
+import javax.servlet.jsp.el.ExpressionEvaluator;
+import javax.servlet.jsp.el.FunctionMapper;
+import javax.servlet.jsp.el.VariableResolver;
+
+import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;
+
+/**
+ * Mock implementation of the JSP 2.0
+ * {@link javax.servlet.jsp.el.ExpressionEvaluator} interface, delegating to the
+ * Jakarta JSTL ExpressionEvaluatorManager.
+ * <p>
+ * Used for testing the web framework; only necessary for testing applications
+ * when testing custom JSP tags.
+ * <p>
+ * Note that the Jakarta JSTL implementation (jstl.jar, standard.jar) has to be
+ * available on the class path to use this expression evaluator.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1.5
+ * @see org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager
+ */
+@SuppressWarnings("deprecation")
+public class MockExpressionEvaluator extends ExpressionEvaluator {
+
+ private final PageContext pageContext;
+
+
+ /**
+ * Create a new MockExpressionEvaluator for the given PageContext.
+ *
+ * @param pageContext the JSP PageContext to run in
+ */
+ public MockExpressionEvaluator(PageContext pageContext) {
+ this.pageContext = pageContext;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public Expression parseExpression(final String expression, final Class expectedType,
+ final FunctionMapper functionMapper) throws ELException {
+
+ return new Expression() {
+
+ public Object evaluate(VariableResolver variableResolver) throws ELException {
+ return doEvaluate(expression, expectedType, functionMapper);
+ }
+ };
+ }
+
+ @SuppressWarnings("rawtypes")
+ public Object evaluate(String expression, Class expectedType, VariableResolver variableResolver,
+ FunctionMapper functionMapper) throws ELException {
+
+ if (variableResolver != null) {
+ throw new IllegalArgumentException("Custom VariableResolver not supported");
+ }
+ return doEvaluate(expression, expectedType, functionMapper);
+ }
+
+ @SuppressWarnings("rawtypes")
+ protected Object doEvaluate(String expression, Class expectedType, FunctionMapper functionMapper)
+ throws ELException {
+
+ if (functionMapper != null) {
+ throw new IllegalArgumentException("Custom FunctionMapper not supported");
+ }
+ try {
+ return ExpressionEvaluatorManager.evaluate("JSP EL expression", expression, expectedType, this.pageContext);
+ }
+ catch (JspException ex) {
+ throw new ELException("Parsing of JSP EL expression \"" + expression + "\" failed", ex);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockFilterChain.java b/spring-test/src/main/java/org/springframework/mock/web/MockFilterChain.java
new file mode 100644
index 00000000..a80a2fb4
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockFilterChain.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2002-2012 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.mock.web;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.springframework.mock.web.MockFilterConfig;
+import org.springframework.mock.web.PassThroughFilterChain;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * <p>Mock implementation of the {@link javax.servlet.FilterChain} interface. Used
+ * for testing the web framework; also useful for testing custom
+ * {@link javax.servlet.Filter} implementations.
+ *
+ * <p>A {@link MockFilterChain} can be configured with one or more filters and a
+ * Servlet to invoke. The first time the chain is called, it invokes all filters
+ * and the Servlet, and saves the request and response. Subsequent invocations
+ * raise an {@link IllegalStateException} unless {@link #reset()} is called.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Winch
+ * @author Rossen Stoyanchev
+ *
+ * @since 2.0.3
+ * @see MockFilterConfig
+ * @see PassThroughFilterChain
+ */
+public class MockFilterChain implements FilterChain {
+
+ private ServletRequest request;
+
+ private ServletResponse response;
+
+ private final List<Filter> filters;
+
+ private Iterator<Filter> iterator;
+
+
+ /**
+ * Register a single do-nothing {@link Filter} implementation. The first
+ * invocation saves the request and response. Subsequent invocations raise
+ * an {@link IllegalStateException} unless {@link #reset()} is called.
+ */
+ public MockFilterChain() {
+ this.filters = Collections.emptyList();
+ }
+
+ /**
+ * Create a FilterChain with a Servlet.
+ *
+ * @param servlet the Servlet to invoke
+ * @since 3.2
+ */
+ public MockFilterChain(Servlet servlet) {
+ this.filters = initFilterList(servlet);
+ }
+
+ /**
+ * Create a {@code FilterChain} with Filter's and a Servlet.
+ *
+ * @param servlet the {@link Servlet} to invoke in this {@link FilterChain}
+ * @param filters the {@link Filter}'s to invoke in this {@link FilterChain}
+ * @since 3.2
+ */
+ public MockFilterChain(Servlet servlet, Filter... filters) {
+ Assert.notNull(filters, "filters cannot be null");
+ Assert.noNullElements(filters, "filters cannot contain null values");
+ this.filters = initFilterList(servlet, filters);
+ }
+
+ private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
+ Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));
+ return Arrays.asList(allFilters);
+ }
+
+ /**
+ * Return the request that {@link #doFilter} has been called with.
+ */
+ public ServletRequest getRequest() {
+ return this.request;
+ }
+
+ /**
+ * Return the response that {@link #doFilter} has been called with.
+ */
+ public ServletResponse getResponse() {
+ return this.response;
+ }
+
+ /**
+ * Invoke registered {@link Filter}s and/or {@link Servlet} also saving the
+ * request and response.
+ */
+ public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
+ Assert.notNull(request, "Request must not be null");
+ Assert.notNull(response, "Response must not be null");
+
+ if (this.request != null) {
+ throw new IllegalStateException("This FilterChain has already been called!");
+ }
+
+ if (this.iterator == null) {
+ this.iterator = this.filters.iterator();
+ }
+
+ if (this.iterator.hasNext()) {
+ Filter nextFilter = this.iterator.next();
+ nextFilter.doFilter(request, response, this);
+ }
+
+ this.request = request;
+ this.response = response;
+ }
+
+ /**
+ * Reset the {@link MockFilterChain} allowing it to be invoked again.
+ */
+ public void reset() {
+ this.request = null;
+ this.response = null;
+ this.iterator = null;
+ }
+
+
+ /**
+ * A filter that simply delegates to a Servlet.
+ */
+ private static class ServletFilterProxy implements Filter {
+
+ private final Servlet delegateServlet;
+
+ private ServletFilterProxy(Servlet servlet) {
+ Assert.notNull(servlet, "servlet cannot be null");
+ this.delegateServlet = servlet;
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+
+ this.delegateServlet.service(request, response);
+ }
+
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ public void destroy() {
+ }
+
+ @Override
+ public String toString() {
+ return this.delegateServlet.toString();
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockFilterConfig.java b/spring-test/src/main/java/org/springframework/mock/web/MockFilterConfig.java
new file mode 100644
index 00000000..34cafd55
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockFilterConfig.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2009 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.mock.web;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.servlet.FilterConfig} interface.
+ *
+ * <p>Used for testing the web framework; also useful for testing
+ * custom {@link javax.servlet.Filter} implementations.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ * @see MockFilterChain
+ * @see PassThroughFilterChain
+ */
+public class MockFilterConfig implements FilterConfig {
+
+ private final ServletContext servletContext;
+
+ private final String filterName;
+
+ private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
+
+
+ /**
+ * Create a new MockFilterConfig with a default {@link MockServletContext}.
+ */
+ public MockFilterConfig() {
+ this(null, "");
+ }
+
+ /**
+ * Create a new MockFilterConfig with a default {@link MockServletContext}.
+ * @param filterName the name of the filter
+ */
+ public MockFilterConfig(String filterName) {
+ this(null, filterName);
+ }
+
+ /**
+ * Create a new MockFilterConfig.
+ * @param servletContext the ServletContext that the servlet runs in
+ */
+ public MockFilterConfig(ServletContext servletContext) {
+ this(servletContext, "");
+ }
+
+ /**
+ * Create a new MockFilterConfig.
+ * @param servletContext the ServletContext that the servlet runs in
+ * @param filterName the name of the filter
+ */
+ public MockFilterConfig(ServletContext servletContext, String filterName) {
+ this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
+ this.filterName = filterName;
+ }
+
+
+ public String getFilterName() {
+ return filterName;
+ }
+
+ public ServletContext getServletContext() {
+ return servletContext;
+ }
+
+ public void addInitParameter(String name, String value) {
+ Assert.notNull(name, "Parameter name must not be null");
+ this.initParameters.put(name, value);
+ }
+
+ public String getInitParameter(String name) {
+ Assert.notNull(name, "Parameter name must not be null");
+ return this.initParameters.get(name);
+ }
+
+ public Enumeration<String> getInitParameterNames() {
+ return Collections.enumeration(this.initParameters.keySet());
+ }
+
+}
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
new file mode 100644
index 00000000..178dffd6
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java
@@ -0,0 +1,917 @@
+/*
+ * 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.mock.web;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.springframework.util.Assert;
+import org.springframework.util.LinkedCaseInsensitiveMap;
+import org.springframework.util.StringUtils;
+
+/**
+ * Mock implementation of the {@link javax.servlet.http.HttpServletRequest} interface.
+ *
+ * <p>Compatible with Servlet 2.5 and partially with Servlet 3.0 (notable exceptions:
+ * the {@code getPart(s)} and {@code startAsync} families of methods).
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @author Rick Evans
+ * @author Mark Fisher
+ * @author Sam Brannen
+ * @since 1.0.2
+ */
+public class MockHttpServletRequest implements HttpServletRequest {
+
+ /**
+ * The default protocol: 'http'.
+ */
+ public static final String DEFAULT_PROTOCOL = "http";
+
+ /**
+ * The default server address: '127.0.0.1'.
+ */
+ public static final String DEFAULT_SERVER_ADDR = "127.0.0.1";
+
+ /**
+ * The default server name: 'localhost'.
+ */
+ public static final String DEFAULT_SERVER_NAME = "localhost";
+
+ /**
+ * The default server port: '80'.
+ */
+ public static final int DEFAULT_SERVER_PORT = 80;
+
+ /**
+ * The default remote address: '127.0.0.1'.
+ */
+ public static final String DEFAULT_REMOTE_ADDR = "127.0.0.1";
+
+ /**
+ * The default remote host: 'localhost'.
+ */
+ public static final String DEFAULT_REMOTE_HOST = "localhost";
+
+ private static final String CONTENT_TYPE_HEADER = "Content-Type";
+
+ private static final String CHARSET_PREFIX = "charset=";
+
+
+ private boolean active = true;
+
+
+ // ---------------------------------------------------------------------
+ // ServletRequest properties
+ // ---------------------------------------------------------------------
+
+ private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+
+ private String characterEncoding;
+
+ private byte[] content;
+
+ private String contentType;
+
+ private final Map<String, String[]> parameters = new LinkedHashMap<String, String[]>(16);
+
+ private String protocol = DEFAULT_PROTOCOL;
+
+ private String scheme = DEFAULT_PROTOCOL;
+
+ private String serverName = DEFAULT_SERVER_NAME;
+
+ private int serverPort = DEFAULT_SERVER_PORT;
+
+ private String remoteAddr = DEFAULT_REMOTE_ADDR;
+
+ private String remoteHost = DEFAULT_REMOTE_HOST;
+
+ /** List of locales in descending order */
+ private final List<Locale> locales = new LinkedList<Locale>();
+
+ private boolean secure = false;
+
+ private final ServletContext servletContext;
+
+ private int remotePort = DEFAULT_SERVER_PORT;
+
+ private String localName = DEFAULT_SERVER_NAME;
+
+ private String localAddr = DEFAULT_SERVER_ADDR;
+
+ private int localPort = DEFAULT_SERVER_PORT;
+
+
+ // ---------------------------------------------------------------------
+ // HttpServletRequest properties
+ // ---------------------------------------------------------------------
+
+ private String authType;
+
+ private Cookie[] cookies;
+
+ private final Map<String, HeaderValueHolder> headers = new LinkedCaseInsensitiveMap<HeaderValueHolder>();
+
+ private String method;
+
+ private String pathInfo;
+
+ private String contextPath = "";
+
+ private String queryString;
+
+ private String remoteUser;
+
+ private final Set<String> userRoles = new HashSet<String>();
+
+ private Principal userPrincipal;
+
+ private String requestedSessionId;
+
+ private String requestURI;
+
+ private String servletPath = "";
+
+ private HttpSession session;
+
+ private boolean requestedSessionIdValid = true;
+
+ private boolean requestedSessionIdFromCookie = true;
+
+ private boolean requestedSessionIdFromURL = false;
+
+
+ // ---------------------------------------------------------------------
+ // Constructors
+ // ---------------------------------------------------------------------
+
+ /**
+ * Create a new {@code MockHttpServletRequest} with a default
+ * {@link MockServletContext}.
+ * @see #MockHttpServletRequest(ServletContext, String, String)
+ */
+ public MockHttpServletRequest() {
+ this(null, "", "");
+ }
+
+ /**
+ * Create a new {@code MockHttpServletRequest} with a default
+ * {@link MockServletContext}.
+ * @param method the request method (may be {@code null})
+ * @param requestURI the request URI (may be {@code null})
+ * @see #setMethod
+ * @see #setRequestURI
+ * @see #MockHttpServletRequest(ServletContext, String, String)
+ */
+ public MockHttpServletRequest(String method, String requestURI) {
+ this(null, method, requestURI);
+ }
+
+ /**
+ * Create a new {@code MockHttpServletRequest} with the supplied {@link ServletContext}.
+ * @param servletContext the ServletContext that the request runs in
+ * (may be {@code null} to use a default {@link MockServletContext})
+ * @see #MockHttpServletRequest(ServletContext, String, String)
+ */
+ public MockHttpServletRequest(ServletContext servletContext) {
+ this(servletContext, "", "");
+ }
+
+ /**
+ * Create a new {@code MockHttpServletRequest} with the supplied {@link ServletContext},
+ * {@code method}, and {@code requestURI}.
+ * <p>The preferred locale will be set to {@link Locale#ENGLISH}.
+ * @param servletContext the ServletContext that the request runs in (may be
+ * {@code null} to use a default {@link MockServletContext})
+ * @param method the request method (may be {@code null})
+ * @param requestURI the request URI (may be {@code null})
+ * @see #setMethod
+ * @see #setRequestURI
+ * @see #setPreferredLocales
+ * @see MockServletContext
+ */
+ public MockHttpServletRequest(ServletContext servletContext, String method, String requestURI) {
+ this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
+ this.method = method;
+ this.requestURI = requestURI;
+ this.locales.add(Locale.ENGLISH);
+ }
+
+
+ // ---------------------------------------------------------------------
+ // Lifecycle methods
+ // ---------------------------------------------------------------------
+
+ /**
+ * Return the ServletContext that this request is associated with. (Not
+ * available in the standard HttpServletRequest interface for some reason.)
+ */
+ public ServletContext getServletContext() {
+ return this.servletContext;
+ }
+
+ /**
+ * Return whether this request is still active (that is, not completed yet).
+ */
+ public boolean isActive() {
+ return this.active;
+ }
+
+ /**
+ * Mark this request as completed, keeping its state.
+ */
+ public void close() {
+ this.active = false;
+ }
+
+ /**
+ * Invalidate this request, clearing its state.
+ */
+ public void invalidate() {
+ close();
+ clearAttributes();
+ }
+
+ /**
+ * Check whether this request is still active (that is, not completed yet),
+ * throwing an IllegalStateException if not active anymore.
+ */
+ protected void checkActive() throws IllegalStateException {
+ if (!this.active) {
+ throw new IllegalStateException("Request is not active anymore");
+ }
+ }
+
+
+ // ---------------------------------------------------------------------
+ // ServletRequest interface
+ // ---------------------------------------------------------------------
+
+ public Object getAttribute(String name) {
+ checkActive();
+ return this.attributes.get(name);
+ }
+
+ public Enumeration<String> getAttributeNames() {
+ checkActive();
+ return Collections.enumeration(new LinkedHashSet<String>(this.attributes.keySet()));
+ }
+
+ public String getCharacterEncoding() {
+ return this.characterEncoding;
+ }
+
+ public void setCharacterEncoding(String characterEncoding) {
+ this.characterEncoding = characterEncoding;
+ updateContentTypeHeader();
+ }
+
+ private void updateContentTypeHeader() {
+ if (StringUtils.hasLength(this.contentType)) {
+ StringBuilder sb = new StringBuilder(this.contentType);
+ if (!this.contentType.toLowerCase().contains(CHARSET_PREFIX) &&
+ StringUtils.hasLength(this.characterEncoding)) {
+ sb.append(";").append(CHARSET_PREFIX).append(this.characterEncoding);
+ }
+ doAddHeaderValue(CONTENT_TYPE_HEADER, sb.toString(), true);
+ }
+ }
+
+ public void setContent(byte[] content) {
+ this.content = content;
+ }
+
+ public int getContentLength() {
+ return (this.content != null ? this.content.length : -1);
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ if (contentType != null) {
+ int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX);
+ if (charsetIndex != -1) {
+ this.characterEncoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length());
+ }
+ updateContentTypeHeader();
+ }
+ }
+
+ public String getContentType() {
+ return this.contentType;
+ }
+
+ public ServletInputStream getInputStream() {
+ if (this.content != null) {
+ return new DelegatingServletInputStream(new ByteArrayInputStream(this.content));
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Set a single value for the specified HTTP parameter.
+ * <p>If there are already one or more values registered for the given
+ * parameter name, they will be replaced.
+ */
+ public void setParameter(String name, String value) {
+ setParameter(name, new String[] {value});
+ }
+
+ /**
+ * Set an array of values for the specified HTTP parameter.
+ * <p>If there are already one or more values registered for the given
+ * parameter name, they will be replaced.
+ */
+ public void setParameter(String name, String[] values) {
+ Assert.notNull(name, "Parameter name must not be null");
+ this.parameters.put(name, values);
+ }
+
+ /**
+ * Sets all provided parameters <strong>replacing</strong> any existing
+ * values for the provided parameter names. To add without replacing
+ * existing values, use {@link #addParameters(java.util.Map)}.
+ */
+ @SuppressWarnings("rawtypes")
+ public void setParameters(Map params) {
+ Assert.notNull(params, "Parameter map must not be null");
+ for (Object key : params.keySet()) {
+ Assert.isInstanceOf(String.class, key,
+ "Parameter map key must be of type [" + String.class.getName() + "]");
+ Object value = params.get(key);
+ if (value instanceof String) {
+ this.setParameter((String) key, (String) value);
+ }
+ else if (value instanceof String[]) {
+ this.setParameter((String) key, (String[]) value);
+ }
+ else {
+ throw new IllegalArgumentException(
+ "Parameter map value must be single value " + " or array of type [" + String.class.getName() + "]");
+ }
+ }
+ }
+
+ /**
+ * Add a single value for the specified HTTP parameter.
+ * <p>If there are already one or more values registered for the given
+ * parameter name, the given value will be added to the end of the list.
+ */
+ public void addParameter(String name, String value) {
+ addParameter(name, new String[] {value});
+ }
+
+ /**
+ * Add an array of values for the specified HTTP parameter.
+ * <p>If there are already one or more values registered for the given
+ * parameter name, the given values will be added to the end of the list.
+ */
+ public void addParameter(String name, String[] values) {
+ Assert.notNull(name, "Parameter name must not be null");
+ String[] oldArr = this.parameters.get(name);
+ if (oldArr != null) {
+ String[] newArr = new String[oldArr.length + values.length];
+ System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
+ System.arraycopy(values, 0, newArr, oldArr.length, values.length);
+ this.parameters.put(name, newArr);
+ }
+ else {
+ this.parameters.put(name, values);
+ }
+ }
+
+ /**
+ * Adds all provided parameters <strong>without</strong> replacing any
+ * existing values. To replace existing values, use
+ * {@link #setParameters(java.util.Map)}.
+ */
+ @SuppressWarnings("rawtypes")
+ public void addParameters(Map params) {
+ Assert.notNull(params, "Parameter map must not be null");
+ for (Object key : params.keySet()) {
+ Assert.isInstanceOf(String.class, key,
+ "Parameter map key must be of type [" + String.class.getName() + "]");
+ Object value = params.get(key);
+ if (value instanceof String) {
+ this.addParameter((String) key, (String) value);
+ }
+ else if (value instanceof String[]) {
+ this.addParameter((String) key, (String[]) value);
+ }
+ else {
+ throw new IllegalArgumentException("Parameter map value must be single value " +
+ " or array of type [" + String.class.getName() + "]");
+ }
+ }
+ }
+
+ /**
+ * Remove already registered values for the specified HTTP parameter, if any.
+ */
+ public void removeParameter(String name) {
+ Assert.notNull(name, "Parameter name must not be null");
+ this.parameters.remove(name);
+ }
+
+ /**
+ * Removes all existing parameters.
+ */
+ public void removeAllParameters() {
+ this.parameters.clear();
+ }
+
+ public String getParameter(String name) {
+ String[] arr = (name != null ? this.parameters.get(name) : null);
+ return (arr != null && arr.length > 0 ? arr[0] : null);
+ }
+
+ public Enumeration<String> getParameterNames() {
+ return Collections.enumeration(this.parameters.keySet());
+ }
+
+ public String[] getParameterValues(String name) {
+ return (name != null ? this.parameters.get(name) : null);
+ }
+
+ public Map<String, String[]> getParameterMap() {
+ return Collections.unmodifiableMap(this.parameters);
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ public String getProtocol() {
+ return this.protocol;
+ }
+
+ public void setScheme(String scheme) {
+ this.scheme = scheme;
+ }
+
+ public String getScheme() {
+ return this.scheme;
+ }
+
+ public void setServerName(String serverName) {
+ this.serverName = serverName;
+ }
+
+ public String getServerName() {
+ return this.serverName;
+ }
+
+ public void setServerPort(int serverPort) {
+ this.serverPort = serverPort;
+ }
+
+ public int getServerPort() {
+ return this.serverPort;
+ }
+
+ public BufferedReader getReader() throws UnsupportedEncodingException {
+ if (this.content != null) {
+ InputStream sourceStream = new ByteArrayInputStream(this.content);
+ Reader sourceReader = (this.characterEncoding != null) ?
+ new InputStreamReader(sourceStream, this.characterEncoding) : new InputStreamReader(sourceStream);
+ return new BufferedReader(sourceReader);
+ }
+ else {
+ return null;
+ }
+ }
+
+ public void setRemoteAddr(String remoteAddr) {
+ this.remoteAddr = remoteAddr;
+ }
+
+ public String getRemoteAddr() {
+ return this.remoteAddr;
+ }
+
+ public void setRemoteHost(String remoteHost) {
+ this.remoteHost = remoteHost;
+ }
+
+ public String getRemoteHost() {
+ return this.remoteHost;
+ }
+
+ public void setAttribute(String name, Object value) {
+ checkActive();
+ Assert.notNull(name, "Attribute name must not be null");
+ if (value != null) {
+ this.attributes.put(name, value);
+ }
+ else {
+ this.attributes.remove(name);
+ }
+ }
+
+ public void removeAttribute(String name) {
+ checkActive();
+ Assert.notNull(name, "Attribute name must not be null");
+ this.attributes.remove(name);
+ }
+
+ /**
+ * Clear all of this request's attributes.
+ */
+ public void clearAttributes() {
+ this.attributes.clear();
+ }
+
+ /**
+ * Add a new preferred locale, before any existing locales.
+ * @see #setPreferredLocales
+ */
+ public void addPreferredLocale(Locale locale) {
+ Assert.notNull(locale, "Locale must not be null");
+ this.locales.add(0, locale);
+ }
+
+ /**
+ * Set the list of preferred locales, in descending order, effectively replacing
+ * any existing locales.
+ * @see #addPreferredLocale
+ * @since 3.2
+ */
+ public void setPreferredLocales(List<Locale> locales) {
+ Assert.notEmpty(locales, "Locale list must not be empty");
+ this.locales.clear();
+ this.locales.addAll(locales);
+ }
+
+ public Locale getLocale() {
+ return this.locales.get(0);
+ }
+
+ public Enumeration<Locale> getLocales() {
+ return Collections.enumeration(this.locales);
+ }
+
+ public void setSecure(boolean secure) {
+ this.secure = secure;
+ }
+
+ public boolean isSecure() {
+ return this.secure;
+ }
+
+ public RequestDispatcher getRequestDispatcher(String path) {
+ return new MockRequestDispatcher(path);
+ }
+
+ public String getRealPath(String path) {
+ return this.servletContext.getRealPath(path);
+ }
+
+ public void setRemotePort(int remotePort) {
+ this.remotePort = remotePort;
+ }
+
+ public int getRemotePort() {
+ return this.remotePort;
+ }
+
+ public void setLocalName(String localName) {
+ this.localName = localName;
+ }
+
+ public String getLocalName() {
+ return this.localName;
+ }
+
+ public void setLocalAddr(String localAddr) {
+ this.localAddr = localAddr;
+ }
+
+ public String getLocalAddr() {
+ return this.localAddr;
+ }
+
+ public void setLocalPort(int localPort) {
+ this.localPort = localPort;
+ }
+
+ public int getLocalPort() {
+ return this.localPort;
+ }
+
+
+ // ---------------------------------------------------------------------
+ // HttpServletRequest interface
+ // ---------------------------------------------------------------------
+
+ public void setAuthType(String authType) {
+ this.authType = authType;
+ }
+
+ public String getAuthType() {
+ return this.authType;
+ }
+
+ public void setCookies(Cookie... cookies) {
+ this.cookies = cookies;
+ }
+
+ public Cookie[] getCookies() {
+ return this.cookies;
+ }
+
+ /**
+ * Add a header entry for the given name.
+ * <p>If there was no entry for that header name before, the value will be used
+ * as-is. In case of an existing entry, a String array will be created,
+ * adding the given value (more specifically, its toString representation)
+ * as further element.
+ * <p>Multiple values can only be stored as list of Strings, following the
+ * Servlet spec (see {@code getHeaders} accessor). As alternative to
+ * repeated {@code addHeader} calls for individual elements, you can
+ * use a single call with an entire array or Collection of values as
+ * parameter.
+ * @see #getHeaderNames
+ * @see #getHeader
+ * @see #getHeaders
+ * @see #getDateHeader
+ * @see #getIntHeader
+ */
+ public void addHeader(String name, Object value) {
+ if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) {
+ setContentType((String) value);
+ return;
+ }
+ doAddHeaderValue(name, value, false);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private void doAddHeaderValue(String name, Object value, boolean replace) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ Assert.notNull(value, "Header value must not be null");
+ if (header == null || replace) {
+ header = new HeaderValueHolder();
+ this.headers.put(name, header);
+ }
+ if (value instanceof Collection) {
+ header.addValues((Collection) value);
+ }
+ else if (value.getClass().isArray()) {
+ header.addValueArray(value);
+ }
+ else {
+ header.addValue(value);
+ }
+ }
+
+ public long getDateHeader(String name) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ Object value = (header != null ? header.getValue() : null);
+ if (value instanceof Date) {
+ return ((Date) value).getTime();
+ }
+ else if (value instanceof Number) {
+ return ((Number) value).longValue();
+ }
+ else if (value != null) {
+ throw new IllegalArgumentException(
+ "Value for header '" + name + "' is neither a Date nor a Number: " + value);
+ }
+ else {
+ return -1L;
+ }
+ }
+
+ public String getHeader(String name) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ return (header != null ? header.getStringValue() : null);
+ }
+
+ public Enumeration<String> getHeaders(String name) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ return Collections.enumeration(header != null ? header.getStringValues() : new LinkedList<String>());
+ }
+
+ public Enumeration<String> getHeaderNames() {
+ return Collections.enumeration(this.headers.keySet());
+ }
+
+ public int getIntHeader(String name) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ Object value = (header != null ? header.getValue() : null);
+ if (value instanceof Number) {
+ return ((Number) value).intValue();
+ }
+ else if (value instanceof String) {
+ return Integer.parseInt((String) value);
+ }
+ else if (value != null) {
+ throw new NumberFormatException("Value for header '" + name + "' is not a Number: " + value);
+ }
+ else {
+ return -1;
+ }
+ }
+
+ public void setMethod(String method) {
+ this.method = method;
+ }
+
+ public String getMethod() {
+ return this.method;
+ }
+
+ public void setPathInfo(String pathInfo) {
+ this.pathInfo = pathInfo;
+ }
+
+ public String getPathInfo() {
+ return this.pathInfo;
+ }
+
+ public String getPathTranslated() {
+ return (this.pathInfo != null ? getRealPath(this.pathInfo) : null);
+ }
+
+ public void setContextPath(String contextPath) {
+ this.contextPath = contextPath;
+ }
+
+ public String getContextPath() {
+ return this.contextPath;
+ }
+
+ public void setQueryString(String queryString) {
+ this.queryString = queryString;
+ }
+
+ public String getQueryString() {
+ return this.queryString;
+ }
+
+ public void setRemoteUser(String remoteUser) {
+ this.remoteUser = remoteUser;
+ }
+
+ public String getRemoteUser() {
+ return this.remoteUser;
+ }
+
+ public void addUserRole(String role) {
+ this.userRoles.add(role);
+ }
+
+ public boolean isUserInRole(String role) {
+ return (this.userRoles.contains(role) || (this.servletContext instanceof MockServletContext &&
+ ((MockServletContext) this.servletContext).getDeclaredRoles().contains(role)));
+ }
+
+ public void setUserPrincipal(Principal userPrincipal) {
+ this.userPrincipal = userPrincipal;
+ }
+
+ public Principal getUserPrincipal() {
+ return this.userPrincipal;
+ }
+
+ public void setRequestedSessionId(String requestedSessionId) {
+ this.requestedSessionId = requestedSessionId;
+ }
+
+ public String getRequestedSessionId() {
+ return this.requestedSessionId;
+ }
+
+ public void setRequestURI(String requestURI) {
+ this.requestURI = requestURI;
+ }
+
+ public String getRequestURI() {
+ return this.requestURI;
+ }
+
+ public StringBuffer getRequestURL() {
+ StringBuffer url = new StringBuffer(this.scheme);
+ url.append("://").append(this.serverName).append(':').append(this.serverPort);
+ url.append(getRequestURI());
+ return url;
+ }
+
+ public void setServletPath(String servletPath) {
+ this.servletPath = servletPath;
+ }
+
+ public String getServletPath() {
+ return this.servletPath;
+ }
+
+ public void setSession(HttpSession session) {
+ this.session = session;
+ if (session instanceof MockHttpSession) {
+ MockHttpSession mockSession = ((MockHttpSession) session);
+ mockSession.access();
+ }
+ }
+
+ public HttpSession getSession(boolean create) {
+ checkActive();
+ // Reset session if invalidated.
+ if (this.session instanceof MockHttpSession && ((MockHttpSession) this.session).isInvalid()) {
+ this.session = null;
+ }
+ // Create new session if necessary.
+ if (this.session == null && create) {
+ this.session = new MockHttpSession(this.servletContext);
+ }
+ return this.session;
+ }
+
+ public HttpSession getSession() {
+ return getSession(true);
+ }
+
+ public void setRequestedSessionIdValid(boolean requestedSessionIdValid) {
+ this.requestedSessionIdValid = requestedSessionIdValid;
+ }
+
+ public boolean isRequestedSessionIdValid() {
+ return this.requestedSessionIdValid;
+ }
+
+ public void setRequestedSessionIdFromCookie(boolean requestedSessionIdFromCookie) {
+ this.requestedSessionIdFromCookie = requestedSessionIdFromCookie;
+ }
+
+ public boolean isRequestedSessionIdFromCookie() {
+ return this.requestedSessionIdFromCookie;
+ }
+
+ public void setRequestedSessionIdFromURL(boolean requestedSessionIdFromURL) {
+ this.requestedSessionIdFromURL = requestedSessionIdFromURL;
+ }
+
+ public boolean isRequestedSessionIdFromURL() {
+ return this.requestedSessionIdFromURL;
+ }
+
+ public boolean isRequestedSessionIdFromUrl() {
+ return isRequestedSessionIdFromURL();
+ }
+
+ public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
+ return (this.userPrincipal != null && this.remoteUser != null && this.authType != null);
+ }
+
+ public void login(String username, String password) throws ServletException {
+ throw new ServletException("Username-password authentication not supported - override the login method");
+ }
+
+ public void logout() throws ServletException {
+ this.userPrincipal = null;
+ this.remoteUser = null;
+ this.authType = null;
+ }
+
+}
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
new file mode 100644
index 00000000..b575c402
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java
@@ -0,0 +1,603 @@
+/*
+ * 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.mock.web;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.util.Assert;
+import org.springframework.util.LinkedCaseInsensitiveMap;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Mock implementation of the {@link javax.servlet.http.HttpServletResponse} interface.
+ *
+ * <p>Compatible with Servlet 2.5 as well as Servlet 3.0.
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @since 1.0.2
+ */
+public class MockHttpServletResponse implements HttpServletResponse {
+
+ private static final String CHARSET_PREFIX = "charset=";
+
+ private static final String CONTENT_TYPE_HEADER = "Content-Type";
+
+ private static final String CONTENT_LENGTH_HEADER = "Content-Length";
+
+ private static final String LOCATION_HEADER = "Location";
+
+
+ //---------------------------------------------------------------------
+ // ServletResponse properties
+ //---------------------------------------------------------------------
+
+ private boolean outputStreamAccessAllowed = true;
+
+ private boolean writerAccessAllowed = true;
+
+ private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
+
+ private boolean charset = false;
+
+ private final ByteArrayOutputStream content = new ByteArrayOutputStream(1024);
+
+ private final ServletOutputStream outputStream = new ResponseServletOutputStream(this.content);
+
+ private PrintWriter writer;
+
+ private int contentLength = 0;
+
+ private String contentType;
+
+ private int bufferSize = 4096;
+
+ private boolean committed;
+
+ private Locale locale = Locale.getDefault();
+
+
+ //---------------------------------------------------------------------
+ // HttpServletResponse properties
+ //---------------------------------------------------------------------
+
+ private final List<Cookie> cookies = new ArrayList<Cookie>();
+
+ private final Map<String, HeaderValueHolder> headers = new LinkedCaseInsensitiveMap<HeaderValueHolder>();
+
+ private int status = HttpServletResponse.SC_OK;
+
+ private String errorMessage;
+
+ private String forwardedUrl;
+
+ private final List<String> includedUrls = new ArrayList<String>();
+
+
+ //---------------------------------------------------------------------
+ // ServletResponse interface
+ //---------------------------------------------------------------------
+
+ /**
+ * Set whether {@link #getOutputStream()} access is allowed.
+ * <p>Default is {@code true}.
+ */
+ public void setOutputStreamAccessAllowed(boolean outputStreamAccessAllowed) {
+ this.outputStreamAccessAllowed = outputStreamAccessAllowed;
+ }
+
+ /**
+ * Return whether {@link #getOutputStream()} access is allowed.
+ */
+ public boolean isOutputStreamAccessAllowed() {
+ return this.outputStreamAccessAllowed;
+ }
+
+ /**
+ * Set whether {@link #getWriter()} access is allowed.
+ * <p>Default is {@code true}.
+ */
+ public void setWriterAccessAllowed(boolean writerAccessAllowed) {
+ this.writerAccessAllowed = writerAccessAllowed;
+ }
+
+ /**
+ * Return whether {@link #getOutputStream()} access is allowed.
+ */
+ public boolean isWriterAccessAllowed() {
+ return this.writerAccessAllowed;
+ }
+
+ public void setCharacterEncoding(String characterEncoding) {
+ this.characterEncoding = characterEncoding;
+ this.charset = true;
+ updateContentTypeHeader();
+ }
+
+ private void updateContentTypeHeader() {
+ if (this.contentType != null) {
+ StringBuilder sb = new StringBuilder(this.contentType);
+ if (!this.contentType.toLowerCase().contains(CHARSET_PREFIX) && this.charset) {
+ sb.append(";").append(CHARSET_PREFIX).append(this.characterEncoding);
+ }
+ doAddHeaderValue(CONTENT_TYPE_HEADER, sb.toString(), true);
+ }
+ }
+
+ public String getCharacterEncoding() {
+ return this.characterEncoding;
+ }
+
+ public ServletOutputStream getOutputStream() {
+ if (!this.outputStreamAccessAllowed) {
+ throw new IllegalStateException("OutputStream access not allowed");
+ }
+ return this.outputStream;
+ }
+
+ public PrintWriter getWriter() throws UnsupportedEncodingException {
+ if (!this.writerAccessAllowed) {
+ throw new IllegalStateException("Writer access not allowed");
+ }
+ if (this.writer == null) {
+ Writer targetWriter = (this.characterEncoding != null ?
+ new OutputStreamWriter(this.content, this.characterEncoding) : new OutputStreamWriter(this.content));
+ this.writer = new ResponsePrintWriter(targetWriter);
+ }
+ return this.writer;
+ }
+
+ public byte[] getContentAsByteArray() {
+ flushBuffer();
+ return this.content.toByteArray();
+ }
+
+ public String getContentAsString() throws UnsupportedEncodingException {
+ flushBuffer();
+ return (this.characterEncoding != null ?
+ this.content.toString(this.characterEncoding) : this.content.toString());
+ }
+
+ public void setContentLength(int contentLength) {
+ this.contentLength = contentLength;
+ doAddHeaderValue(CONTENT_LENGTH_HEADER, contentLength, true);
+ }
+
+ public int getContentLength() {
+ return this.contentLength;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ if (contentType != null) {
+ int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX);
+ if (charsetIndex != -1) {
+ this.characterEncoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length());
+ this.charset = true;
+ }
+ updateContentTypeHeader();
+ }
+ }
+
+ public String getContentType() {
+ return this.contentType;
+ }
+
+ public void setBufferSize(int bufferSize) {
+ this.bufferSize = bufferSize;
+ }
+
+ public int getBufferSize() {
+ return this.bufferSize;
+ }
+
+ public void flushBuffer() {
+ setCommitted(true);
+ }
+
+ public void resetBuffer() {
+ if (isCommitted()) {
+ throw new IllegalStateException("Cannot reset buffer - response is already committed");
+ }
+ this.content.reset();
+ }
+
+ private void setCommittedIfBufferSizeExceeded() {
+ int bufSize = getBufferSize();
+ if (bufSize > 0 && this.content.size() > bufSize) {
+ setCommitted(true);
+ }
+ }
+
+ public void setCommitted(boolean committed) {
+ this.committed = committed;
+ }
+
+ public boolean isCommitted() {
+ return this.committed;
+ }
+
+ public void reset() {
+ resetBuffer();
+ this.characterEncoding = null;
+ this.contentLength = 0;
+ this.contentType = null;
+ this.locale = null;
+ this.cookies.clear();
+ this.headers.clear();
+ this.status = HttpServletResponse.SC_OK;
+ this.errorMessage = null;
+ }
+
+ public void setLocale(Locale locale) {
+ this.locale = locale;
+ }
+
+ public Locale getLocale() {
+ return this.locale;
+ }
+
+
+ //---------------------------------------------------------------------
+ // HttpServletResponse interface
+ //---------------------------------------------------------------------
+
+ public void addCookie(Cookie cookie) {
+ Assert.notNull(cookie, "Cookie must not be null");
+ this.cookies.add(cookie);
+ }
+
+ public Cookie[] getCookies() {
+ return this.cookies.toArray(new Cookie[this.cookies.size()]);
+ }
+
+ public Cookie getCookie(String name) {
+ Assert.notNull(name, "Cookie name must not be null");
+ for (Cookie cookie : this.cookies) {
+ if (name.equals(cookie.getName())) {
+ return cookie;
+ }
+ }
+ return null;
+ }
+
+ public boolean containsHeader(String name) {
+ return (HeaderValueHolder.getByName(this.headers, name) != null);
+ }
+
+ /**
+ * Return the names of all specified headers as a Set of Strings.
+ * <p>As of Servlet 3.0, this method is also defined HttpServletResponse.
+ * @return the {@code Set} of header name {@code Strings}, or an empty {@code Set} if none
+ */
+ public Collection<String> getHeaderNames() {
+ return this.headers.keySet();
+ }
+
+ /**
+ * Return the primary value for the given header as a String, if any.
+ * Will return the first value in case of multiple values.
+ * <p>As of Servlet 3.0, this method is also defined in HttpServletResponse.
+ * As of Spring 3.1, it returns a stringified value for Servlet 3.0 compatibility.
+ * Consider using {@link #getHeaderValue(String)} for raw Object access.
+ * @param name the name of the header
+ * @return the associated header value, or {@code null} if none
+ */
+ public String getHeader(String name) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ return (header != null ? header.getStringValue() : null);
+ }
+
+ /**
+ * Return all values for the given header as a List of Strings.
+ * <p>As of Servlet 3.0, this method is also defined in HttpServletResponse.
+ * As of Spring 3.1, it returns a List of stringified values for Servlet 3.0 compatibility.
+ * Consider using {@link #getHeaderValues(String)} for raw Object access.
+ * @param name the name of the header
+ * @return the associated header values, or an empty List if none
+ */
+ public List<String> getHeaders(String name) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ if (header != null) {
+ return header.getStringValues();
+ }
+ else {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Return the primary value for the given header, if any.
+ * <p>Will return the first value in case of multiple values.
+ * @param name the name of the header
+ * @return the associated header value, or {@code null} if none
+ */
+ public Object getHeaderValue(String name) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ return (header != null ? header.getValue() : null);
+ }
+
+ /**
+ * Return all values for the given header as a List of value objects.
+ * @param name the name of the header
+ * @return the associated header values, or an empty List if none
+ */
+ public List<Object> getHeaderValues(String name) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ if (header != null) {
+ return header.getValues();
+ }
+ else {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * The default implementation returns the given URL String as-is.
+ * <p>Can be overridden in subclasses, appending a session id or the like.
+ */
+ public String encodeURL(String url) {
+ return url;
+ }
+
+ /**
+ * The default implementation delegates to {@link #encodeURL},
+ * returning the given URL String as-is.
+ * <p>Can be overridden in subclasses, appending a session id or the like
+ * in a redirect-specific fashion. For general URL encoding rules,
+ * override the common {@link #encodeURL} method instead, applying
+ * to redirect URLs as well as to general URLs.
+ */
+ public String encodeRedirectURL(String url) {
+ return encodeURL(url);
+ }
+
+ public String encodeUrl(String url) {
+ return encodeURL(url);
+ }
+
+ public String encodeRedirectUrl(String url) {
+ return encodeRedirectURL(url);
+ }
+
+ public void sendError(int status, String errorMessage) throws IOException {
+ if (isCommitted()) {
+ throw new IllegalStateException("Cannot set error status - response is already committed");
+ }
+ this.status = status;
+ this.errorMessage = errorMessage;
+ setCommitted(true);
+ }
+
+ public void sendError(int status) throws IOException {
+ if (isCommitted()) {
+ throw new IllegalStateException("Cannot set error status - response is already committed");
+ }
+ this.status = status;
+ setCommitted(true);
+ }
+
+ public void sendRedirect(String url) throws IOException {
+ if (isCommitted()) {
+ throw new IllegalStateException("Cannot send redirect - response is already committed");
+ }
+ Assert.notNull(url, "Redirect URL must not be null");
+ setHeader(LOCATION_HEADER, url);
+ setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+ setCommitted(true);
+ }
+
+ public String getRedirectedUrl() {
+ return getHeader(LOCATION_HEADER);
+ }
+
+ public void setDateHeader(String name, long value) {
+ setHeaderValue(name, value);
+ }
+
+ public void addDateHeader(String name, long value) {
+ addHeaderValue(name, value);
+ }
+
+ public void setHeader(String name, String value) {
+ setHeaderValue(name, value);
+ }
+
+ public void addHeader(String name, String value) {
+ addHeaderValue(name, value);
+ }
+
+ public void setIntHeader(String name, int value) {
+ setHeaderValue(name, value);
+ }
+
+ public void addIntHeader(String name, int value) {
+ addHeaderValue(name, value);
+ }
+
+ private void setHeaderValue(String name, Object value) {
+ if (setSpecialHeader(name, value)) {
+ return;
+ }
+ doAddHeaderValue(name, value, true);
+ }
+
+ private void addHeaderValue(String name, Object value) {
+ if (setSpecialHeader(name, value)) {
+ return;
+ }
+ doAddHeaderValue(name, value, false);
+ }
+
+ private boolean setSpecialHeader(String name, Object value) {
+ if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) {
+ setContentType((String) value);
+ return true;
+ }
+ else if (CONTENT_LENGTH_HEADER.equalsIgnoreCase(name)) {
+ setContentLength(Integer.parseInt((String) value));
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ private void doAddHeaderValue(String name, Object value, boolean replace) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ Assert.notNull(value, "Header value must not be null");
+ if (header == null) {
+ header = new HeaderValueHolder();
+ this.headers.put(name, header);
+ }
+ if (replace) {
+ header.setValue(value);
+ }
+ else {
+ header.addValue(value);
+ }
+ }
+
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ public void setStatus(int status, String errorMessage) {
+ this.status = status;
+ this.errorMessage = errorMessage;
+ }
+
+ public int getStatus() {
+ return this.status;
+ }
+
+ public String getErrorMessage() {
+ return this.errorMessage;
+ }
+
+
+ //---------------------------------------------------------------------
+ // Methods for MockRequestDispatcher
+ //---------------------------------------------------------------------
+
+ public void setForwardedUrl(String forwardedUrl) {
+ this.forwardedUrl = forwardedUrl;
+ }
+
+ public String getForwardedUrl() {
+ return this.forwardedUrl;
+ }
+
+ public void setIncludedUrl(String includedUrl) {
+ this.includedUrls.clear();
+ if (includedUrl != null) {
+ this.includedUrls.add(includedUrl);
+ }
+ }
+
+ public String getIncludedUrl() {
+ int count = this.includedUrls.size();
+ if (count > 1) {
+ throw new IllegalStateException(
+ "More than 1 URL included - check getIncludedUrls instead: " + this.includedUrls);
+ }
+ return (count == 1 ? this.includedUrls.get(0) : null);
+ }
+
+ public void addIncludedUrl(String includedUrl) {
+ Assert.notNull(includedUrl, "Included URL must not be null");
+ this.includedUrls.add(includedUrl);
+ }
+
+ public List<String> getIncludedUrls() {
+ return this.includedUrls;
+ }
+
+
+ /**
+ * Inner class that adapts the ServletOutputStream to mark the
+ * response as committed once the buffer size is exceeded.
+ */
+ private class ResponseServletOutputStream extends DelegatingServletOutputStream {
+
+ public ResponseServletOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ public void write(int b) throws IOException {
+ super.write(b);
+ super.flush();
+ setCommittedIfBufferSizeExceeded();
+ }
+
+ public void flush() throws IOException {
+ super.flush();
+ setCommitted(true);
+ }
+ }
+
+
+ /**
+ * Inner class that adapts the PrintWriter to mark the
+ * response as committed once the buffer size is exceeded.
+ */
+ private class ResponsePrintWriter extends PrintWriter {
+
+ public ResponsePrintWriter(Writer out) {
+ super(out, true);
+ }
+
+ public void write(char buf[], int off, int len) {
+ super.write(buf, off, len);
+ super.flush();
+ setCommittedIfBufferSizeExceeded();
+ }
+
+ public void write(String s, int off, int len) {
+ super.write(s, off, len);
+ super.flush();
+ setCommittedIfBufferSizeExceeded();
+ }
+
+ public void write(int c) {
+ super.write(c);
+ super.flush();
+ setCommittedIfBufferSizeExceeded();
+ }
+
+ public void flush() {
+ super.flush();
+ setCommitted(true);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java
new file mode 100644
index 00000000..548c2016
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpSession.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.mock.web;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionContext;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.servlet.http.HttpSession} interface.
+ *
+ * <p>Compatible with Servlet 2.5 as well as Servlet 3.0.
+ *
+ * <p>Used for testing the web framework; also useful for testing application
+ * controllers.
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @author Mark Fisher
+ * @author Sam Brannen
+ * @since 1.0.2
+ */
+@SuppressWarnings("deprecation")
+public class MockHttpSession implements HttpSession {
+
+ public static final String SESSION_COOKIE_NAME = "JSESSION";
+
+
+ private static int nextId = 1;
+
+ private final String id;
+
+ private final long creationTime = System.currentTimeMillis();
+
+ private int maxInactiveInterval;
+
+ private long lastAccessedTime = System.currentTimeMillis();
+
+ private final ServletContext servletContext;
+
+ private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+
+ private boolean invalid = false;
+
+ private boolean isNew = true;
+
+
+ /**
+ * Create a new MockHttpSession with a default {@link MockServletContext}.
+ *
+ * @see MockServletContext
+ */
+ public MockHttpSession() {
+ this(null);
+ }
+
+ /**
+ * Create a new MockHttpSession.
+ *
+ * @param servletContext the ServletContext that the session runs in
+ */
+ public MockHttpSession(ServletContext servletContext) {
+ this(servletContext, null);
+ }
+
+ /**
+ * Create a new MockHttpSession.
+ *
+ * @param servletContext the ServletContext that the session runs in
+ * @param id a unique identifier for this session
+ */
+ public MockHttpSession(ServletContext servletContext, String id) {
+ this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
+ this.id = (id != null ? id : Integer.toString(nextId++));
+ }
+
+ public long getCreationTime() {
+ return this.creationTime;
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ public void access() {
+ this.lastAccessedTime = System.currentTimeMillis();
+ this.isNew = false;
+ }
+
+ public long getLastAccessedTime() {
+ return this.lastAccessedTime;
+ }
+
+ public ServletContext getServletContext() {
+ return this.servletContext;
+ }
+
+ public void setMaxInactiveInterval(int interval) {
+ this.maxInactiveInterval = interval;
+ }
+
+ public int getMaxInactiveInterval() {
+ return this.maxInactiveInterval;
+ }
+
+ public HttpSessionContext getSessionContext() {
+ throw new UnsupportedOperationException("getSessionContext");
+ }
+
+ public Object getAttribute(String name) {
+ Assert.notNull(name, "Attribute name must not be null");
+ return this.attributes.get(name);
+ }
+
+ public Object getValue(String name) {
+ return getAttribute(name);
+ }
+
+ public Enumeration<String> getAttributeNames() {
+ return Collections.enumeration(new LinkedHashSet<String>(this.attributes.keySet()));
+ }
+
+ public String[] getValueNames() {
+ return this.attributes.keySet().toArray(new String[this.attributes.size()]);
+ }
+
+ public void setAttribute(String name, Object value) {
+ Assert.notNull(name, "Attribute name must not be null");
+ if (value != null) {
+ this.attributes.put(name, value);
+ if (value instanceof HttpSessionBindingListener) {
+ ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value));
+ }
+ }
+ else {
+ removeAttribute(name);
+ }
+ }
+
+ public void putValue(String name, Object value) {
+ setAttribute(name, value);
+ }
+
+ public void removeAttribute(String name) {
+ Assert.notNull(name, "Attribute name must not be null");
+ Object value = this.attributes.remove(name);
+ if (value instanceof HttpSessionBindingListener) {
+ ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
+ }
+ }
+
+ public void removeValue(String name) {
+ removeAttribute(name);
+ }
+
+ /**
+ * Clear all of this session's attributes.
+ */
+ public void clearAttributes() {
+ for (Iterator<Map.Entry<String, Object>> it = this.attributes.entrySet().iterator(); it.hasNext();) {
+ Map.Entry<String, Object> entry = it.next();
+ String name = entry.getKey();
+ Object value = entry.getValue();
+ it.remove();
+ if (value instanceof HttpSessionBindingListener) {
+ ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
+ }
+ }
+ }
+
+ /**
+ * Invalidates this session then unbinds any objects bound to it.
+ *
+ * @throws IllegalStateException if this method is called on an already invalidated session
+ */
+ public void invalidate() {
+ if (this.invalid) {
+ throw new IllegalStateException("The session has already been invalidated");
+ }
+
+ // else
+ this.invalid = true;
+ clearAttributes();
+ }
+
+ public boolean isInvalid() {
+ return this.invalid;
+ }
+
+ public void setNew(boolean value) {
+ this.isNew = value;
+ }
+
+ public boolean isNew() {
+ return this.isNew;
+ }
+
+ /**
+ * Serialize the attributes of this session into an object that can be
+ * turned into a byte array with standard Java serialization.
+ *
+ * @return a representation of this session's serialized state
+ */
+ public Serializable serializeState() {
+ HashMap<String, Serializable> state = new HashMap<String, Serializable>();
+ for (Iterator<Map.Entry<String, Object>> it = this.attributes.entrySet().iterator(); it.hasNext();) {
+ Map.Entry<String, Object> entry = it.next();
+ String name = entry.getKey();
+ Object value = entry.getValue();
+ it.remove();
+ if (value instanceof Serializable) {
+ state.put(name, (Serializable) value);
+ }
+ else {
+ // Not serializable... Servlet containers usually automatically
+ // unbind the attribute in this case.
+ if (value instanceof HttpSessionBindingListener) {
+ ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
+ }
+ }
+ }
+ return state;
+ }
+
+ /**
+ * Deserialize the attributes of this session from a state object created by
+ * {@link #serializeState()}.
+ *
+ * @param state a representation of this session's serialized state
+ */
+ @SuppressWarnings("unchecked")
+ public void deserializeState(Serializable state) {
+ Assert.isTrue(state instanceof Map, "Serialized state needs to be of type [java.util.Map]");
+ this.attributes.putAll((Map<String, Object>) state);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockJspWriter.java b/spring-test/src/main/java/org/springframework/mock/web/MockJspWriter.java
new file mode 100644
index 00000000..1d5bf928
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockJspWriter.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2002-2009 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.mock.web;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.JspWriter;
+
+/**
+ * Mock implementation of the {@link javax.servlet.jsp.JspWriter} class.
+ *
+ * <p>Used for testing the web framework; only necessary for testing
+ * applications when testing custom JSP tags.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class MockJspWriter extends JspWriter {
+
+ private final HttpServletResponse response;
+
+ private PrintWriter targetWriter;
+
+
+ /**
+ * Create a MockJspWriter for the given response,
+ * using the response's default Writer.
+ * @param response the servlet response to wrap
+ */
+ public MockJspWriter(HttpServletResponse response) {
+ this(response, null);
+ }
+
+ /**
+ * Create a MockJspWriter for the given plain Writer.
+ * @param targetWriter the target Writer to wrap
+ */
+ public MockJspWriter(Writer targetWriter) {
+ this(null, targetWriter);
+ }
+
+ /**
+ * Create a MockJspWriter for the given response.
+ * @param response the servlet response to wrap
+ * @param targetWriter the target Writer to wrap
+ */
+ public MockJspWriter(HttpServletResponse response, Writer targetWriter) {
+ super(DEFAULT_BUFFER, true);
+ this.response = (response != null ? response : new MockHttpServletResponse());
+ if (targetWriter instanceof PrintWriter) {
+ this.targetWriter = (PrintWriter) targetWriter;
+ }
+ else if (targetWriter != null) {
+ this.targetWriter = new PrintWriter(targetWriter);
+ }
+ }
+
+ /**
+ * Lazily initialize the target Writer.
+ */
+ protected PrintWriter getTargetWriter() throws IOException {
+ if (this.targetWriter == null) {
+ this.targetWriter = this.response.getWriter();
+ }
+ return this.targetWriter;
+ }
+
+
+ public void clear() throws IOException {
+ if (this.response.isCommitted()) {
+ throw new IOException("Response already committed");
+ }
+ this.response.resetBuffer();
+ }
+
+ public void clearBuffer() throws IOException {
+ }
+
+ public void flush() throws IOException {
+ this.response.flushBuffer();
+ }
+
+ public void close() throws IOException {
+ flush();
+ }
+
+ public int getRemaining() {
+ return Integer.MAX_VALUE;
+ }
+
+ public void newLine() throws IOException {
+ getTargetWriter().println();
+ }
+
+ public void write(char value[], int offset, int length) throws IOException {
+ getTargetWriter().write(value, offset, length);
+ }
+
+ public void print(boolean value) throws IOException {
+ getTargetWriter().print(value);
+ }
+
+ public void print(char value) throws IOException {
+ getTargetWriter().print(value);
+ }
+
+ public void print(char[] value) throws IOException {
+ getTargetWriter().print(value);
+ }
+
+ public void print(double value) throws IOException {
+ getTargetWriter().print(value);
+ }
+
+ public void print(float value) throws IOException {
+ getTargetWriter().print(value);
+ }
+
+ public void print(int value) throws IOException {
+ getTargetWriter().print(value);
+ }
+
+ public void print(long value) throws IOException {
+ getTargetWriter().print(value);
+ }
+
+ public void print(Object value) throws IOException {
+ getTargetWriter().print(value);
+ }
+
+ public void print(String value) throws IOException {
+ getTargetWriter().print(value);
+ }
+
+ public void println() throws IOException {
+ getTargetWriter().println();
+ }
+
+ public void println(boolean value) throws IOException {
+ getTargetWriter().println(value);
+ }
+
+ public void println(char value) throws IOException {
+ getTargetWriter().println(value);
+ }
+
+ public void println(char[] value) throws IOException {
+ getTargetWriter().println(value);
+ }
+
+ public void println(double value) throws IOException {
+ getTargetWriter().println(value);
+ }
+
+ public void println(float value) throws IOException {
+ getTargetWriter().println(value);
+ }
+
+ public void println(int value) throws IOException {
+ getTargetWriter().println(value);
+ }
+
+ public void println(long value) throws IOException {
+ getTargetWriter().println(value);
+ }
+
+ public void println(Object value) throws IOException {
+ getTargetWriter().println(value);
+ }
+
+ public void println(String value) throws IOException {
+ getTargetWriter().println(value);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockMultipartFile.java b/spring-test/src/main/java/org/springframework/mock/web/MockMultipartFile.java
new file mode 100644
index 00000000..fb57d1f1
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockMultipartFile.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2002-2011 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.mock.web;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.springframework.util.Assert;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * Mock implementation of the {@link org.springframework.web.multipart.MultipartFile}
+ * interface.
+ *
+ * <p>Useful in conjunction with a {@link MockMultipartHttpServletRequest}
+ * for testing application controllers that access multipart uploads.
+ *
+ * @author Juergen Hoeller
+ * @author Eric Crampton
+ * @since 2.0
+ * @see MockMultipartHttpServletRequest
+ */
+public class MockMultipartFile implements MultipartFile {
+
+ private final String name;
+
+ private String originalFilename;
+
+ private String contentType;
+
+ private final byte[] content;
+
+
+ /**
+ * Create a new MockMultipartFile with the given content.
+ * @param name the name of the file
+ * @param content the content of the file
+ */
+ public MockMultipartFile(String name, byte[] content) {
+ this(name, "", null, content);
+ }
+
+ /**
+ * Create a new MockMultipartFile with the given content.
+ * @param name the name of the file
+ * @param contentStream the content of the file as stream
+ * @throws IOException if reading from the stream failed
+ */
+ public MockMultipartFile(String name, InputStream contentStream) throws IOException {
+ this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));
+ }
+
+ /**
+ * Create a new MockMultipartFile with the given content.
+ * @param name the name of the file
+ * @param originalFilename the original filename (as on the client's machine)
+ * @param contentType the content type (if known)
+ * @param content the content of the file
+ */
+ public MockMultipartFile(String name, String originalFilename, String contentType, byte[] content) {
+ Assert.hasLength(name, "Name must not be null");
+ this.name = name;
+ this.originalFilename = (originalFilename != null ? originalFilename : "");
+ this.contentType = contentType;
+ this.content = (content != null ? content : new byte[0]);
+ }
+
+ /**
+ * Create a new MockMultipartFile with the given content.
+ * @param name the name of the file
+ * @param originalFilename the original filename (as on the client's machine)
+ * @param contentType the content type (if known)
+ * @param contentStream the content of the file as stream
+ * @throws IOException if reading from the stream failed
+ */
+ public MockMultipartFile(String name, String originalFilename, String contentType, InputStream contentStream)
+ throws IOException {
+
+ this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getOriginalFilename() {
+ return this.originalFilename;
+ }
+
+ public String getContentType() {
+ return this.contentType;
+ }
+
+ public boolean isEmpty() {
+ return (this.content.length == 0);
+ }
+
+ public long getSize() {
+ return this.content.length;
+ }
+
+ public byte[] getBytes() throws IOException {
+ return this.content;
+ }
+
+ public InputStream getInputStream() throws IOException {
+ return new ByteArrayInputStream(this.content);
+ }
+
+ public void transferTo(File dest) throws IOException, IllegalStateException {
+ FileCopyUtils.copy(this.content, dest);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java b/spring-test/src/main/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java
new file mode 100644
index 00000000..b01c4fdc
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2002-2011 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.mock.web;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.util.Assert;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+
+/**
+ * Mock implementation of the
+ * {@link org.springframework.web.multipart.MultipartHttpServletRequest} interface.
+ *
+ * <p>Useful for testing application controllers that access multipart uploads.
+ * The {@link MockMultipartFile} can be used to populate these mock requests
+ * with files.
+ *
+ * @author Juergen Hoeller
+ * @author Eric Crampton
+ * @author Arjen Poutsma
+ * @since 2.0
+ * @see MockMultipartFile
+ */
+public class MockMultipartHttpServletRequest extends MockHttpServletRequest implements MultipartHttpServletRequest {
+
+ private final MultiValueMap<String, MultipartFile> multipartFiles =
+ new LinkedMultiValueMap<String, MultipartFile>();
+
+
+ public MockMultipartHttpServletRequest() {
+ setMethod("POST");
+ setContentType("multipart/form-data");
+ }
+
+
+ /**
+ * Add a file to this request. The parameter name from the multipart
+ * form is taken from the {@link MultipartFile#getName()}.
+ * @param file multipart file to be added
+ */
+ public void addFile(MultipartFile file) {
+ Assert.notNull(file, "MultipartFile must not be null");
+ this.multipartFiles.add(file.getName(), file);
+ }
+
+ public Iterator<String> getFileNames() {
+ return this.multipartFiles.keySet().iterator();
+ }
+
+ public MultipartFile getFile(String name) {
+ return this.multipartFiles.getFirst(name);
+ }
+
+ public List<MultipartFile> getFiles(String name) {
+ List<MultipartFile> multipartFiles = this.multipartFiles.get(name);
+ if (multipartFiles != null) {
+ return multipartFiles;
+ }
+ else {
+ return Collections.emptyList();
+ }
+ }
+
+ public Map<String, MultipartFile> getFileMap() {
+ return this.multipartFiles.toSingleValueMap();
+ }
+
+ public MultiValueMap<String, MultipartFile> getMultiFileMap() {
+ return new LinkedMultiValueMap<String, MultipartFile>(this.multipartFiles);
+ }
+
+ public String getMultipartContentType(String paramOrFileName) {
+ MultipartFile file = getFile(paramOrFileName);
+ if (file != null) {
+ return file.getContentType();
+ }
+ else {
+ return null;
+ }
+ }
+
+ public HttpMethod getRequestMethod() {
+ return HttpMethod.valueOf(getMethod());
+ }
+
+ public HttpHeaders getRequestHeaders() {
+ HttpHeaders headers = new HttpHeaders();
+ Enumeration<String> headerNames = getHeaderNames();
+ while (headerNames.hasMoreElements()) {
+ String headerName = headerNames.nextElement();
+ headers.put(headerName, Collections.list(getHeaders(headerName)));
+ }
+ return headers;
+ }
+
+ public HttpHeaders getMultipartHeaders(String paramOrFileName) {
+ String contentType = getMultipartContentType(paramOrFileName);
+ if (contentType != null) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("Content-Type", contentType);
+ return headers;
+ }
+ else {
+ return null;
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java b/spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java
new file mode 100644
index 00000000..2c5d07e8
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockPageContext.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.mock.web;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import javax.el.ELContext;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.el.ExpressionEvaluator;
+import javax.servlet.jsp.el.VariableResolver;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.servlet.jsp.PageContext} interface.
+ *
+ * <p>Used for testing the web framework; only necessary for testing
+ * applications when testing custom JSP tags.
+ *
+ * <p>Note: Expects initialization via the constructor rather than via the
+ * {@code PageContext.initialize} method. Does not support writing to
+ * a JspWriter, request dispatching, and {@code handlePageException} calls.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ */
+@SuppressWarnings("deprecation")
+public class MockPageContext extends PageContext {
+
+ private final ServletContext servletContext;
+
+ private final HttpServletRequest request;
+
+ private final HttpServletResponse response;
+
+ private final ServletConfig servletConfig;
+
+ private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+
+ private JspWriter out;
+
+
+ /**
+ * Create new MockPageContext with a default {@link MockServletContext},
+ * {@link MockHttpServletRequest}, {@link MockHttpServletResponse},
+ * {@link MockServletConfig}.
+ */
+ public MockPageContext() {
+ this(null, null, null, null);
+ }
+
+ /**
+ * Create new MockPageContext with a default {@link MockHttpServletRequest},
+ * {@link MockHttpServletResponse}, {@link MockServletConfig}.
+ * @param servletContext the ServletContext that the JSP page runs in
+ * (only necessary when actually accessing the ServletContext)
+ */
+ public MockPageContext(ServletContext servletContext) {
+ this(servletContext, null, null, null);
+ }
+
+ /**
+ * Create new MockPageContext with a MockHttpServletResponse,
+ * MockServletConfig.
+ * @param servletContext the ServletContext that the JSP page runs in
+ * @param request the current HttpServletRequest
+ * (only necessary when actually accessing the request)
+ */
+ public MockPageContext(ServletContext servletContext, HttpServletRequest request) {
+ this(servletContext, request, null, null);
+ }
+
+ /**
+ * Create new MockPageContext with a MockServletConfig.
+ * @param servletContext the ServletContext that the JSP page runs in
+ * @param request the current HttpServletRequest
+ * @param response the current HttpServletResponse
+ * (only necessary when actually writing to the response)
+ */
+ public MockPageContext(ServletContext servletContext, HttpServletRequest request, HttpServletResponse response) {
+ this(servletContext, request, response, null);
+ }
+
+ /**
+ * Create new MockServletConfig.
+ * @param servletContext the ServletContext that the JSP page runs in
+ * @param request the current HttpServletRequest
+ * @param response the current HttpServletResponse
+ * @param servletConfig the ServletConfig (hardly ever accessed from within a tag)
+ */
+ public MockPageContext(ServletContext servletContext, HttpServletRequest request,
+ HttpServletResponse response, ServletConfig servletConfig) {
+
+ this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
+ this.request = (request != null ? request : new MockHttpServletRequest(servletContext));
+ this.response = (response != null ? response : new MockHttpServletResponse());
+ this.servletConfig = (servletConfig != null ? servletConfig : new MockServletConfig(servletContext));
+ }
+
+
+ public void initialize(
+ Servlet servlet, ServletRequest request, ServletResponse response,
+ String errorPageURL, boolean needsSession, int bufferSize, boolean autoFlush) {
+
+ throw new UnsupportedOperationException("Use appropriate constructor");
+ }
+
+ public void release() {
+ }
+
+ public void setAttribute(String name, Object value) {
+ Assert.notNull(name, "Attribute name must not be null");
+ if (value != null) {
+ this.attributes.put(name, value);
+ }
+ else {
+ this.attributes.remove(name);
+ }
+ }
+
+ public void setAttribute(String name, Object value, int scope) {
+ Assert.notNull(name, "Attribute name must not be null");
+ switch (scope) {
+ case PAGE_SCOPE:
+ setAttribute(name, value);
+ break;
+ case REQUEST_SCOPE:
+ this.request.setAttribute(name, value);
+ break;
+ case SESSION_SCOPE:
+ this.request.getSession().setAttribute(name, value);
+ break;
+ case APPLICATION_SCOPE:
+ this.servletContext.setAttribute(name, value);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid scope: " + scope);
+ }
+ }
+
+ public Object getAttribute(String name) {
+ Assert.notNull(name, "Attribute name must not be null");
+ return this.attributes.get(name);
+ }
+
+ public Object getAttribute(String name, int scope) {
+ Assert.notNull(name, "Attribute name must not be null");
+ switch (scope) {
+ case PAGE_SCOPE:
+ return getAttribute(name);
+ case REQUEST_SCOPE:
+ return this.request.getAttribute(name);
+ case SESSION_SCOPE:
+ HttpSession session = this.request.getSession(false);
+ return (session != null ? session.getAttribute(name) : null);
+ case APPLICATION_SCOPE:
+ return this.servletContext.getAttribute(name);
+ default:
+ throw new IllegalArgumentException("Invalid scope: " + scope);
+ }
+ }
+
+ public Object findAttribute(String name) {
+ Object value = getAttribute(name);
+ if (value == null) {
+ value = getAttribute(name, REQUEST_SCOPE);
+ if (value == null) {
+ value = getAttribute(name, SESSION_SCOPE);
+ if (value == null) {
+ value = getAttribute(name, APPLICATION_SCOPE);
+ }
+ }
+ }
+ return value;
+ }
+
+ public void removeAttribute(String name) {
+ Assert.notNull(name, "Attribute name must not be null");
+ this.removeAttribute(name, PageContext.PAGE_SCOPE);
+ this.removeAttribute(name, PageContext.REQUEST_SCOPE);
+ this.removeAttribute(name, PageContext.SESSION_SCOPE);
+ this.removeAttribute(name, PageContext.APPLICATION_SCOPE);
+ }
+
+ public void removeAttribute(String name, int scope) {
+ Assert.notNull(name, "Attribute name must not be null");
+ switch (scope) {
+ case PAGE_SCOPE:
+ this.attributes.remove(name);
+ break;
+ case REQUEST_SCOPE:
+ this.request.removeAttribute(name);
+ break;
+ case SESSION_SCOPE:
+ this.request.getSession().removeAttribute(name);
+ break;
+ case APPLICATION_SCOPE:
+ this.servletContext.removeAttribute(name);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid scope: " + scope);
+ }
+ }
+
+ public int getAttributesScope(String name) {
+ if (getAttribute(name) != null) {
+ return PAGE_SCOPE;
+ }
+ else if (getAttribute(name, REQUEST_SCOPE) != null) {
+ return REQUEST_SCOPE;
+ }
+ else if (getAttribute(name, SESSION_SCOPE) != null) {
+ return SESSION_SCOPE;
+ }
+ else if (getAttribute(name, APPLICATION_SCOPE) != null) {
+ return APPLICATION_SCOPE;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ public Enumeration<String> getAttributeNames() {
+ return Collections.enumeration(new LinkedHashSet<String>(this.attributes.keySet()));
+ }
+
+ @SuppressWarnings("unchecked")
+ public Enumeration<String> getAttributeNamesInScope(int scope) {
+ switch (scope) {
+ case PAGE_SCOPE:
+ return getAttributeNames();
+ case REQUEST_SCOPE:
+ return this.request.getAttributeNames();
+ case SESSION_SCOPE:
+ HttpSession session = this.request.getSession(false);
+ return (session != null ? session.getAttributeNames() : null);
+ case APPLICATION_SCOPE:
+ return this.servletContext.getAttributeNames();
+ default:
+ throw new IllegalArgumentException("Invalid scope: " + scope);
+ }
+ }
+
+ public JspWriter getOut() {
+ if (this.out == null) {
+ this.out = new MockJspWriter(this.response);
+ }
+ return this.out;
+ }
+
+ public ExpressionEvaluator getExpressionEvaluator() {
+ return new MockExpressionEvaluator(this);
+ }
+
+ public ELContext getELContext() {
+ return null;
+ }
+
+ public VariableResolver getVariableResolver() {
+ return null;
+ }
+
+ public HttpSession getSession() {
+ return this.request.getSession();
+ }
+
+ public Object getPage() {
+ return this;
+ }
+
+ public ServletRequest getRequest() {
+ return this.request;
+ }
+
+ public ServletResponse getResponse() {
+ return this.response;
+ }
+
+ public Exception getException() {
+ return null;
+ }
+
+ public ServletConfig getServletConfig() {
+ return this.servletConfig;
+ }
+
+ public ServletContext getServletContext() {
+ return this.servletContext;
+ }
+
+ public void forward(String path) throws ServletException, IOException {
+ this.request.getRequestDispatcher(path).forward(this.request, this.response);
+ }
+
+ public void include(String path) throws ServletException, IOException {
+ this.request.getRequestDispatcher(path).include(this.request, this.response);
+ }
+
+ public void include(String path, boolean flush) throws ServletException, IOException {
+ this.request.getRequestDispatcher(path).include(this.request, this.response);
+ if (flush) {
+ this.response.flushBuffer();
+ }
+ }
+
+ public byte[] getContentAsByteArray() {
+ Assert.isTrue(this.response instanceof MockHttpServletResponse);
+ return ((MockHttpServletResponse) this.response).getContentAsByteArray();
+ }
+
+ public String getContentAsString() throws UnsupportedEncodingException {
+ Assert.isTrue(this.response instanceof MockHttpServletResponse);
+ return ((MockHttpServletResponse) this.response).getContentAsString();
+ }
+
+ public void handlePageException(Exception ex) throws ServletException, IOException {
+ throw new ServletException("Page exception", ex);
+ }
+
+ public void handlePageException(Throwable ex) throws ServletException, IOException {
+ throw new ServletException("Page exception", ex);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockRequestDispatcher.java b/spring-test/src/main/java/org/springframework/mock/web/MockRequestDispatcher.java
new file mode 100644
index 00000000..88660bb3
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockRequestDispatcher.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2002-2012 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.mock.web;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.servlet.RequestDispatcher} interface.
+ *
+ * <p>Used for testing the web framework; typically not necessary for
+ * testing application controllers.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @since 1.0.2
+ */
+public class MockRequestDispatcher implements RequestDispatcher {
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final String resource;
+
+
+ /**
+ * Create a new MockRequestDispatcher for the given resource.
+ * @param resource the server resource to dispatch to, located at a
+ * particular path or given by a particular name
+ */
+ public MockRequestDispatcher(String resource) {
+ Assert.notNull(resource, "resource must not be null");
+ this.resource = resource;
+ }
+
+
+ public void forward(ServletRequest request, ServletResponse response) {
+ Assert.notNull(request, "Request must not be null");
+ Assert.notNull(response, "Response must not be null");
+ if (response.isCommitted()) {
+ throw new IllegalStateException("Cannot perform forward - response is already committed");
+ }
+ getMockHttpServletResponse(response).setForwardedUrl(this.resource);
+ if (logger.isDebugEnabled()) {
+ logger.debug("MockRequestDispatcher: forwarding to [" + this.resource + "]");
+ }
+ }
+
+ public void include(ServletRequest request, ServletResponse response) {
+ Assert.notNull(request, "Request must not be null");
+ Assert.notNull(response, "Response must not be null");
+ getMockHttpServletResponse(response).addIncludedUrl(this.resource);
+ if (logger.isDebugEnabled()) {
+ logger.debug("MockRequestDispatcher: including [" + this.resource + "]");
+ }
+ }
+
+ /**
+ * Obtain the underlying {@link MockHttpServletResponse}, unwrapping
+ * {@link HttpServletResponseWrapper} decorators if necessary.
+ */
+ protected MockHttpServletResponse getMockHttpServletResponse(ServletResponse response) {
+ if (response instanceof MockHttpServletResponse) {
+ return (MockHttpServletResponse) response;
+ }
+ if (response instanceof HttpServletResponseWrapper) {
+ return getMockHttpServletResponse(((HttpServletResponseWrapper) response).getResponse());
+ }
+ throw new IllegalArgumentException("MockRequestDispatcher requires MockHttpServletResponse");
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockServletConfig.java b/spring-test/src/main/java/org/springframework/mock/web/MockServletConfig.java
new file mode 100644
index 00000000..c62ec152
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockServletConfig.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2009 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.mock.web;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.servlet.ServletConfig} interface.
+ *
+ * <p>Used for testing the web framework; typically not necessary for
+ * testing application controllers.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ */
+public class MockServletConfig implements ServletConfig {
+
+ private final ServletContext servletContext;
+
+ private final String servletName;
+
+ private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
+
+
+ /**
+ * Create a new MockServletConfig with a default {@link MockServletContext}.
+ */
+ public MockServletConfig() {
+ this(null, "");
+ }
+
+ /**
+ * Create a new MockServletConfig with a default {@link MockServletContext}.
+ * @param servletName the name of the servlet
+ */
+ public MockServletConfig(String servletName) {
+ this(null, servletName);
+ }
+
+ /**
+ * Create a new MockServletConfig.
+ * @param servletContext the ServletContext that the servlet runs in
+ */
+ public MockServletConfig(ServletContext servletContext) {
+ this(servletContext, "");
+ }
+
+ /**
+ * Create a new MockServletConfig.
+ * @param servletContext the ServletContext that the servlet runs in
+ * @param servletName the name of the servlet
+ */
+ public MockServletConfig(ServletContext servletContext, String servletName) {
+ this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
+ this.servletName = servletName;
+ }
+
+
+ public String getServletName() {
+ return this.servletName;
+ }
+
+ public ServletContext getServletContext() {
+ return this.servletContext;
+ }
+
+ public void addInitParameter(String name, String value) {
+ Assert.notNull(name, "Parameter name must not be null");
+ this.initParameters.put(name, value);
+ }
+
+ public String getInitParameter(String name) {
+ Assert.notNull(name, "Parameter name must not be null");
+ return this.initParameters.get(name);
+ }
+
+ public Enumeration<String> getInitParameterNames() {
+ return Collections.enumeration(this.initParameters.keySet());
+ }
+
+}
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
new file mode 100644
index 00000000..49b49e58
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockServletContext.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.mock.web;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.activation.FileTypeMap;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Mock implementation of the {@link javax.servlet.ServletContext} interface.
+ *
+ * <p>Compatible with Servlet 2.5 and partially with Servlet 3.0. Can be configured to
+ * expose a specific version through {@link #setMajorVersion}/{@link #setMinorVersion};
+ * default is 2.5. Note that Servlet 3.0 support is limited: servlet, filter and listener
+ * registration methods are not supported; neither is cookie or JSP configuration.
+ * We generally do not recommend to unit-test your ServletContainerInitializers and
+ * WebApplicationInitializers which is where those registration methods would be used.
+ *
+ * <p>Used for testing the Spring web framework; only rarely necessary for testing
+ * application controllers. As long as application components don't explicitly
+ * access the {@code ServletContext}, {@code ClassPathXmlApplicationContext} or
+ * {@code FileSystemXmlApplicationContext} can be used to load the context files
+ * for testing, even for {@code DispatcherServlet} context definitions.
+ *
+ * <p>For setting up a full {@code WebApplicationContext} in a test environment,
+ * you can use {@code AnnotationConfigWebApplicationContext},
+ * {@code XmlWebApplicationContext}, or {@code GenericWebApplicationContext},
+ * passing in an appropriate {@code MockServletContext} instance. You might want
+ * to configure your {@code MockServletContext} with a {@code FileSystemResourceLoader}
+ * in that case to ensure that resource paths are interpreted as relative filesystem
+ * locations.
+ *
+ * <p>A common setup is to point your JVM working directory to the root of your
+ * web application directory, in combination with filesystem-based resource loading.
+ * This allows to load the context files as used in the web application, with
+ * relative paths getting interpreted correctly. Such a setup will work with both
+ * {@code FileSystemXmlApplicationContext} (which will load straight from the
+ * filesystem) and {@code XmlWebApplicationContext} with an underlying
+ * {@code MockServletContext} (as long as the {@code MockServletContext} has been
+ * configured with a {@code FileSystemResourceLoader}).
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @since 1.0.2
+ * @see #MockServletContext(org.springframework.core.io.ResourceLoader)
+ * @see org.springframework.web.context.support.AnnotationConfigWebApplicationContext
+ * @see org.springframework.web.context.support.XmlWebApplicationContext
+ * @see org.springframework.web.context.support.GenericWebApplicationContext
+ * @see org.springframework.context.support.ClassPathXmlApplicationContext
+ * @see org.springframework.context.support.FileSystemXmlApplicationContext
+ */
+public class MockServletContext implements ServletContext {
+
+ /** Default Servlet name used by Tomcat, Jetty, JBoss, and GlassFish: {@value}. */
+ private static final String COMMON_DEFAULT_SERVLET_NAME = "default";
+
+ private static final String TEMP_DIR_SYSTEM_PROPERTY = "java.io.tmpdir";
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final Map<String, ServletContext> contexts = new HashMap<String, ServletContext>();
+
+ private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
+
+ private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+
+ private final Set<String> declaredRoles = new HashSet<String>();
+
+ private final Map<String, RequestDispatcher> namedRequestDispatchers = new HashMap<String, RequestDispatcher>();
+
+ private final ResourceLoader resourceLoader;
+
+ private final String resourceBasePath;
+
+ private String contextPath = "";
+
+ private int majorVersion = 2;
+
+ private int minorVersion = 5;
+
+ private int effectiveMajorVersion = 2;
+
+ private int effectiveMinorVersion = 5;
+
+ private String servletContextName = "MockServletContext";
+
+ private String defaultServletName = COMMON_DEFAULT_SERVLET_NAME;
+
+
+ /**
+ * Create a new MockServletContext, using no base path and a
+ * DefaultResourceLoader (i.e. the classpath root as WAR root).
+ * @see org.springframework.core.io.DefaultResourceLoader
+ */
+ public MockServletContext() {
+ this("", null);
+ }
+
+ /**
+ * Create a new MockServletContext, using a DefaultResourceLoader.
+ * @param resourceBasePath the root directory of the WAR (should not end with a slash)
+ * @see org.springframework.core.io.DefaultResourceLoader
+ */
+ public MockServletContext(String resourceBasePath) {
+ this(resourceBasePath, null);
+ }
+
+ /**
+ * Create a new MockServletContext, using the specified ResourceLoader
+ * and no base path.
+ * @param resourceLoader the ResourceLoader to use (or null for the default)
+ */
+ public MockServletContext(ResourceLoader resourceLoader) {
+ this("", resourceLoader);
+ }
+
+ /**
+ * Create a new MockServletContext using the supplied resource base path and
+ * resource loader.
+ * <p>Registers a {@link MockRequestDispatcher} for the Servlet named
+ * {@linkplain #COMMON_DEFAULT_SERVLET_NAME "default"}.
+ * @param resourceBasePath the root directory of the WAR (should not end with a slash)
+ * @param resourceLoader the ResourceLoader to use (or null for the default)
+ * @see #registerNamedDispatcher
+ */
+ @SuppressWarnings("javadoc")
+ public MockServletContext(String resourceBasePath, ResourceLoader resourceLoader) {
+ this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
+ this.resourceBasePath = (resourceBasePath != null ? resourceBasePath : "");
+
+ // Use JVM temp dir as ServletContext temp dir.
+ String tempDir = System.getProperty(TEMP_DIR_SYSTEM_PROPERTY);
+ if (tempDir != null) {
+ this.attributes.put(WebUtils.TEMP_DIR_CONTEXT_ATTRIBUTE, new File(tempDir));
+ }
+
+ registerNamedDispatcher(this.defaultServletName, new MockRequestDispatcher(this.defaultServletName));
+ }
+
+ /**
+ * Build a full resource location for the given path,
+ * prepending the resource base path of this MockServletContext.
+ * @param path the path as specified
+ * @return the full resource path
+ */
+ protected String getResourceLocation(String path) {
+ if (!path.startsWith("/")) {
+ path = "/" + path;
+ }
+ return this.resourceBasePath + path;
+ }
+
+ public void setContextPath(String contextPath) {
+ this.contextPath = (contextPath != null ? contextPath : "");
+ }
+
+ /* This is a Servlet API 2.5 method. */
+ public String getContextPath() {
+ return this.contextPath;
+ }
+
+ public void registerContext(String contextPath, ServletContext context) {
+ this.contexts.put(contextPath, context);
+ }
+
+ public ServletContext getContext(String contextPath) {
+ if (this.contextPath.equals(contextPath)) {
+ return this;
+ }
+ return this.contexts.get(contextPath);
+ }
+
+ public void setMajorVersion(int majorVersion) {
+ this.majorVersion = majorVersion;
+ }
+
+ public int getMajorVersion() {
+ return this.majorVersion;
+ }
+
+ public void setMinorVersion(int minorVersion) {
+ this.minorVersion = minorVersion;
+ }
+
+ public int getMinorVersion() {
+ return this.minorVersion;
+ }
+
+ public void setEffectiveMajorVersion(int effectiveMajorVersion) {
+ this.effectiveMajorVersion = effectiveMajorVersion;
+ }
+
+ public int getEffectiveMajorVersion() {
+ return this.effectiveMajorVersion;
+ }
+
+ public void setEffectiveMinorVersion(int effectiveMinorVersion) {
+ this.effectiveMinorVersion = effectiveMinorVersion;
+ }
+
+ public int getEffectiveMinorVersion() {
+ return this.effectiveMinorVersion;
+ }
+
+ /**
+ * This method uses the Java Activation framework, which returns "application/octet-stream"
+ * when the mime type is unknown (i.e. it never returns {@code null}). In order to maintain
+ * the {@link ServletContext#getMimeType(String)} contract, this method returns {@code null}
+ * if the mimeType is "application/octet-stream", as of Spring 3.2.2.
+ */
+ public String getMimeType(String filePath) {
+ String mimeType = MimeTypeResolver.getMimeType(filePath);
+ return ("application/octet-stream".equals(mimeType)) ? null : mimeType;
+ }
+
+ public Set<String> getResourcePaths(String path) {
+ String actualPath = (path.endsWith("/") ? path : path + "/");
+ Resource resource = this.resourceLoader.getResource(getResourceLocation(actualPath));
+ try {
+ File file = resource.getFile();
+ String[] fileList = file.list();
+ if (ObjectUtils.isEmpty(fileList)) {
+ return null;
+ }
+ Set<String> resourcePaths = new LinkedHashSet<String>(fileList.length);
+ for (String fileEntry : fileList) {
+ String resultPath = actualPath + fileEntry;
+ if (resource.createRelative(fileEntry).getFile().isDirectory()) {
+ resultPath += "/";
+ }
+ resourcePaths.add(resultPath);
+ }
+ return resourcePaths;
+ }
+ catch (IOException ex) {
+ logger.warn("Couldn't get resource paths for " + resource, ex);
+ return null;
+ }
+ }
+
+ public URL getResource(String path) throws MalformedURLException {
+ Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
+ if (!resource.exists()) {
+ return null;
+ }
+ try {
+ return resource.getURL();
+ }
+ catch (MalformedURLException ex) {
+ throw ex;
+ }
+ catch (IOException ex) {
+ logger.warn("Couldn't get URL for " + resource, ex);
+ return null;
+ }
+ }
+
+ public InputStream getResourceAsStream(String path) {
+ Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
+ if (!resource.exists()) {
+ return null;
+ }
+ try {
+ return resource.getInputStream();
+ }
+ catch (IOException ex) {
+ logger.warn("Couldn't open InputStream for " + resource, ex);
+ return null;
+ }
+ }
+
+ public RequestDispatcher getRequestDispatcher(String path) {
+ if (!path.startsWith("/")) {
+ throw new IllegalArgumentException("RequestDispatcher path at ServletContext level must start with '/'");
+ }
+ return new MockRequestDispatcher(path);
+ }
+
+ public RequestDispatcher getNamedDispatcher(String path) {
+ return this.namedRequestDispatchers.get(path);
+ }
+
+ /**
+ * 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
+ * @see #unregisterNamedDispatcher
+ */
+ public void registerNamedDispatcher(String name, RequestDispatcher requestDispatcher) {
+ Assert.notNull(name, "RequestDispatcher name must not be null");
+ Assert.notNull(requestDispatcher, "RequestDispatcher must not be null");
+ this.namedRequestDispatchers.put(name, requestDispatcher);
+ }
+
+ /**
+ * Unregister the {@link RequestDispatcher} with the given name.
+ *
+ * @param name the name of the dispatcher to unregister
+ * @see #getNamedDispatcher
+ * @see #registerNamedDispatcher
+ */
+ public void unregisterNamedDispatcher(String name) {
+ Assert.notNull(name, "RequestDispatcher name must not be null");
+ this.namedRequestDispatchers.remove(name);
+ }
+
+ /**
+ * Get the name of the <em>default</em> {@code Servlet}.
+ * <p>Defaults to {@linkplain #COMMON_DEFAULT_SERVLET_NAME "default"}.
+ * @see #setDefaultServletName
+ */
+ @SuppressWarnings("javadoc")
+ public String getDefaultServletName() {
+ return this.defaultServletName;
+ }
+
+ /**
+ * Set the name of the <em>default</em> {@code Servlet}.
+ * <p>Also {@link #unregisterNamedDispatcher unregisters} the current default
+ * {@link RequestDispatcher} and {@link #registerNamedDispatcher replaces}
+ * it with a {@link MockRequestDispatcher} for the provided
+ * {@code defaultServletName}.
+ * @param defaultServletName the name of the <em>default</em> {@code Servlet};
+ * never {@code null} or empty
+ * @see #getDefaultServletName
+ */
+ public void setDefaultServletName(String defaultServletName) {
+ Assert.hasText(defaultServletName, "defaultServletName must not be null or empty");
+ unregisterNamedDispatcher(this.defaultServletName);
+ this.defaultServletName = defaultServletName;
+ registerNamedDispatcher(this.defaultServletName, new MockRequestDispatcher(this.defaultServletName));
+ }
+
+ public Servlet getServlet(String name) {
+ return null;
+ }
+
+ public Enumeration<Servlet> getServlets() {
+ return Collections.enumeration(new HashSet<Servlet>());
+ }
+
+ public Enumeration<String> getServletNames() {
+ return Collections.enumeration(new HashSet<String>());
+ }
+
+ public void log(String message) {
+ logger.info(message);
+ }
+
+ public void log(Exception ex, String message) {
+ logger.info(message, ex);
+ }
+
+ public void log(String message, Throwable ex) {
+ logger.info(message, ex);
+ }
+
+ public String getRealPath(String path) {
+ Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
+ try {
+ return resource.getFile().getAbsolutePath();
+ }
+ catch (IOException ex) {
+ logger.warn("Couldn't determine real path of resource " + resource, ex);
+ return null;
+ }
+ }
+
+ public String getServerInfo() {
+ return "MockServletContext";
+ }
+
+ public String getInitParameter(String name) {
+ Assert.notNull(name, "Parameter name must not be null");
+ return this.initParameters.get(name);
+ }
+
+ public Enumeration<String> getInitParameterNames() {
+ return Collections.enumeration(this.initParameters.keySet());
+ }
+
+ public boolean setInitParameter(String name, String value) {
+ Assert.notNull(name, "Parameter name must not be null");
+ if (this.initParameters.containsKey(name)) {
+ return false;
+ }
+ this.initParameters.put(name, value);
+ return true;
+ }
+
+ public void addInitParameter(String name, String value) {
+ Assert.notNull(name, "Parameter name must not be null");
+ this.initParameters.put(name, value);
+ }
+
+ public Object getAttribute(String name) {
+ Assert.notNull(name, "Attribute name must not be null");
+ return this.attributes.get(name);
+ }
+
+ public Enumeration<String> getAttributeNames() {
+ return Collections.enumeration(new LinkedHashSet<String>(this.attributes.keySet()));
+ }
+
+ public void setAttribute(String name, Object value) {
+ Assert.notNull(name, "Attribute name must not be null");
+ if (value != null) {
+ this.attributes.put(name, value);
+ }
+ else {
+ this.attributes.remove(name);
+ }
+ }
+
+ public void removeAttribute(String name) {
+ Assert.notNull(name, "Attribute name must not be null");
+ this.attributes.remove(name);
+ }
+
+ public void setServletContextName(String servletContextName) {
+ this.servletContextName = servletContextName;
+ }
+
+ public String getServletContextName() {
+ return this.servletContextName;
+ }
+
+ public ClassLoader getClassLoader() {
+ return ClassUtils.getDefaultClassLoader();
+ }
+
+ public void declareRoles(String... roleNames) {
+ Assert.notNull(roleNames, "Role names array must not be null");
+ for (String roleName : roleNames) {
+ Assert.hasLength(roleName, "Role name must not be empty");
+ this.declaredRoles.add(roleName);
+ }
+ }
+
+ public Set<String> getDeclaredRoles() {
+ return Collections.unmodifiableSet(this.declaredRoles);
+ }
+
+
+ /**
+ * Inner factory class used to introduce a Java Activation Framework
+ * dependency when actually asked to resolve a MIME type.
+ */
+ private static class MimeTypeResolver {
+
+ public static String getMimeType(String filePath) {
+ return FileTypeMap.getDefaultFileTypeMap().getContentType(filePath);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/PassThroughFilterChain.java b/spring-test/src/main/java/org/springframework/mock/web/PassThroughFilterChain.java
new file mode 100644
index 00000000..b3484a0a
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/PassThroughFilterChain.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2002-2009 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.mock.web;
+
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.springframework.util.Assert;
+
+/**
+ * Implementation of the {@link javax.servlet.FilterConfig} interface which
+ * simply passes the call through to a given Filter/FilterChain combination
+ * (indicating the next Filter in the chain along with the FilterChain that it is
+ * supposed to work on) or to a given Servlet (indicating the end of the chain).
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.3
+ * @see javax.servlet.Filter
+ * @see javax.servlet.Servlet
+ * @see MockFilterChain
+ */
+public class PassThroughFilterChain implements FilterChain {
+
+ private Filter filter;
+
+ private FilterChain nextFilterChain;
+
+ private Servlet servlet;
+
+
+ /**
+ * Create a new PassThroughFilterChain that delegates to the given Filter,
+ * calling it with the given FilterChain.
+ * @param filter the Filter to delegate to
+ * @param nextFilterChain the FilterChain to use for that next Filter
+ */
+ public PassThroughFilterChain(Filter filter, FilterChain nextFilterChain) {
+ Assert.notNull(filter, "Filter must not be null");
+ Assert.notNull(nextFilterChain, "'FilterChain must not be null");
+ this.filter = filter;
+ this.nextFilterChain = nextFilterChain;
+ }
+
+ /**
+ * Create a new PassThroughFilterChain that delegates to the given Servlet.
+ * @param servlet the Servlet to delegate to
+ */
+ public PassThroughFilterChain(Servlet servlet) {
+ Assert.notNull(servlet, "Servlet must not be null");
+ this.servlet = servlet;
+ }
+
+
+ /**
+ * Pass the call on to the Filter/Servlet.
+ */
+ public void doFilter(ServletRequest request, ServletResponse response) throws ServletException, IOException {
+ if (this.filter != null) {
+ this.filter.doFilter(request, response, this.nextFilterChain);
+ }
+ else {
+ this.servlet.service(request, response);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/package-info.java b/spring-test/src/main/java/org/springframework/mock/web/package-info.java
new file mode 100644
index 00000000..be8cb860
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/package-info.java
@@ -0,0 +1,15 @@
+
+/**
+ *
+ * A comprehensive set of Servlet API 2.5 mock objects,
+ * targeted at usage with Spring's web MVC framework.
+ * Useful for testing web contexts and controllers.
+ *
+ * <p>More convenient to use than dynamic mock objects
+ * (<a href="http://www.easymock.org">EasyMock</a>) or
+ * existing Servlet API mock objects
+ * (<a href="http://www.mockobjects.com">MockObjects</a>).
+ *
+ */
+package org.springframework.mock.web;
+
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockActionRequest.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockActionRequest.java
new file mode 100644
index 00000000..a687ccb6
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockActionRequest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletMode;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ActionRequest} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockActionRequest extends MockClientDataRequest implements ActionRequest {
+
+ /**
+ * Create a new MockActionRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @see org.springframework.mock.web.portlet.MockPortalContext
+ * @see org.springframework.mock.web.portlet.MockPortletContext
+ */
+ public MockActionRequest() {
+ super();
+ }
+
+ /**
+ * Create a new MockActionRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @param actionName the name of the action to trigger
+ */
+ public MockActionRequest(String actionName) {
+ super();
+ setParameter(ActionRequest.ACTION_NAME, actionName);
+ }
+
+ /**
+ * Create a new MockActionRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @param portletMode the mode that the portlet runs in
+ */
+ public MockActionRequest(PortletMode portletMode) {
+ super();
+ setPortletMode(portletMode);
+ }
+
+ /**
+ * Create a new MockActionRequest with a default {@link MockPortalContext}.
+ * @param portletContext the PortletContext that the request runs in
+ */
+ public MockActionRequest(PortletContext portletContext) {
+ super(portletContext);
+ }
+
+ /**
+ * Create a new MockActionRequest.
+ * @param portalContext the PortalContext that the request runs in
+ * @param portletContext the PortletContext that the request runs in
+ */
+ public MockActionRequest(PortalContext portalContext, PortletContext portletContext) {
+ super(portalContext, portletContext);
+ }
+
+
+ @Override
+ protected String getLifecyclePhase() {
+ return ACTION_PHASE;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockActionResponse.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockActionResponse.java
new file mode 100644
index 00000000..bec66878
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockActionResponse.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.io.IOException;
+import java.util.Map;
+import javax.portlet.ActionResponse;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletModeException;
+import javax.portlet.WindowState;
+import javax.portlet.WindowStateException;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ActionResponse} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockActionResponse extends MockStateAwareResponse implements ActionResponse {
+
+ private boolean redirectAllowed = true;
+
+ private String redirectedUrl;
+
+
+ /**
+ * Create a new MockActionResponse with a default {@link MockPortalContext}.
+ * @see MockPortalContext
+ */
+ public MockActionResponse() {
+ super();
+ }
+
+ /**
+ * Create a new MockActionResponse.
+ * @param portalContext the PortalContext defining the supported
+ * PortletModes and WindowStates
+ */
+ public MockActionResponse(PortalContext portalContext) {
+ super(portalContext);
+ }
+
+
+ public void setWindowState(WindowState windowState) throws WindowStateException {
+ if (this.redirectedUrl != null) {
+ throw new IllegalStateException("Cannot set WindowState after sendRedirect has been called");
+ }
+ super.setWindowState(windowState);
+ this.redirectAllowed = false;
+ }
+
+ public void setPortletMode(PortletMode portletMode) throws PortletModeException {
+ if (this.redirectedUrl != null) {
+ throw new IllegalStateException("Cannot set PortletMode after sendRedirect has been called");
+ }
+ super.setPortletMode(portletMode);
+ this.redirectAllowed = false;
+ }
+
+ public void setRenderParameters(Map<String, String[]> parameters) {
+ if (this.redirectedUrl != null) {
+ throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
+ }
+ super.setRenderParameters(parameters);
+ this.redirectAllowed = false;
+ }
+
+ public void setRenderParameter(String key, String value) {
+ if (this.redirectedUrl != null) {
+ throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
+ }
+ super.setRenderParameter(key, value);
+ this.redirectAllowed = false;
+ }
+
+ public void setRenderParameter(String key, String[] values) {
+ if (this.redirectedUrl != null) {
+ throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
+ }
+ super.setRenderParameter(key, values);
+ this.redirectAllowed = false;
+ }
+
+ public void sendRedirect(String location) throws IOException {
+ if (!this.redirectAllowed) {
+ throw new IllegalStateException(
+ "Cannot call sendRedirect after windowState, portletMode, or renderParameters have been set");
+ }
+ Assert.notNull(location, "Redirect URL must not be null");
+ this.redirectedUrl = location;
+ }
+
+ public void sendRedirect(String location, String renderUrlParamName) throws IOException {
+ sendRedirect(location);
+ if (renderUrlParamName != null) {
+ setRenderParameter(renderUrlParamName, location);
+ }
+ }
+
+ public String getRedirectedUrl() {
+ return this.redirectedUrl;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockBaseURL.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockBaseURL.java
new file mode 100644
index 00000000..aad99205
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockBaseURL.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.URLEncoder;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.portlet.BaseURL;
+import javax.portlet.PortletSecurityException;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.BaseURL} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public abstract class MockBaseURL implements BaseURL {
+
+ public static final String URL_TYPE_RENDER = "render";
+
+ public static final String URL_TYPE_ACTION = "action";
+
+ private static final String ENCODING = "UTF-8";
+
+
+ protected final Map<String, String[]> parameters = new LinkedHashMap<String, String[]>();
+
+ private boolean secure = false;
+
+ private final Map<String, String[]> properties = new LinkedHashMap<String, String[]>();
+
+
+ //---------------------------------------------------------------------
+ // BaseURL methods
+ //---------------------------------------------------------------------
+
+ public void setParameter(String key, String value) {
+ Assert.notNull(key, "Parameter key must be null");
+ Assert.notNull(value, "Parameter value must not be null");
+ this.parameters.put(key, new String[] {value});
+ }
+
+ public void setParameter(String key, String[] values) {
+ Assert.notNull(key, "Parameter key must be null");
+ Assert.notNull(values, "Parameter values must not be null");
+ this.parameters.put(key, values);
+ }
+
+ public void setParameters(Map<String, String[]> parameters) {
+ Assert.notNull(parameters, "Parameters Map must not be null");
+ this.parameters.clear();
+ this.parameters.putAll(parameters);
+ }
+
+ public Set<String> getParameterNames() {
+ return this.parameters.keySet();
+ }
+
+ public String getParameter(String name) {
+ String[] arr = this.parameters.get(name);
+ return (arr != null && arr.length > 0 ? arr[0] : null);
+ }
+
+ public String[] getParameterValues(String name) {
+ return this.parameters.get(name);
+ }
+
+ public Map<String, String[]> getParameterMap() {
+ return Collections.unmodifiableMap(this.parameters);
+ }
+
+ public void setSecure(boolean secure) throws PortletSecurityException {
+ this.secure = secure;
+ }
+
+ public boolean isSecure() {
+ return this.secure;
+ }
+
+ public void write(Writer out) throws IOException {
+ out.write(toString());
+ }
+
+ public void write(Writer out, boolean escapeXML) throws IOException {
+ out.write(toString());
+ }
+
+ public void addProperty(String key, String value) {
+ String[] values = this.properties.get(key);
+ if (values != null) {
+ this.properties.put(key, StringUtils.addStringToArray(values, value));
+ }
+ else {
+ this.properties.put(key, new String[] {value});
+ }
+ }
+
+ public void setProperty(String key, String value) {
+ this.properties.put(key, new String[] {value});
+ }
+
+ public Map<String, String[]> getProperties() {
+ return Collections.unmodifiableMap(this.properties);
+ }
+
+
+ protected String encodeParameter(String name, String value) {
+ try {
+ return URLEncoder.encode(name, ENCODING) + "=" + URLEncoder.encode(value, ENCODING);
+ }
+ catch (UnsupportedEncodingException ex) {
+ return null;
+ }
+ }
+
+ protected String encodeParameter(String name, String[] values) {
+ try {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0, n = values.length; i < n; i++) {
+ sb.append(i > 0 ? ";" : "").append(URLEncoder.encode(name, ENCODING)).append("=")
+ .append(URLEncoder.encode(values[i], ENCODING));
+ }
+ return sb.toString();
+ }
+ catch (UnsupportedEncodingException ex) {
+ return null;
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockCacheControl.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockCacheControl.java
new file mode 100644
index 00000000..0ee9fbeb
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockCacheControl.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import javax.portlet.CacheControl;
+
+/**
+ * Mock implementation of the {@link javax.portlet.CacheControl} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockCacheControl implements CacheControl {
+
+ private int expirationTime = 0;
+
+ private boolean publicScope = false;
+
+ private String etag;
+
+ private boolean useCachedContent = false;
+
+
+ public int getExpirationTime() {
+ return this.expirationTime;
+ }
+
+ public void setExpirationTime(int time) {
+ this.expirationTime = time;
+ }
+
+ public boolean isPublicScope() {
+ return this.publicScope;
+ }
+
+ public void setPublicScope(boolean publicScope) {
+ this.publicScope = publicScope;
+ }
+
+ public String getETag() {
+ return this.etag;
+ }
+
+ public void setETag(String token) {
+ this.etag = token;
+ }
+
+ public boolean useCachedContent() {
+ return this.useCachedContent;
+ }
+
+ public void setUseCachedContent(boolean useCachedContent) {
+ this.useCachedContent = useCachedContent;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockClientDataRequest.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockClientDataRequest.java
new file mode 100644
index 00000000..406c263e
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockClientDataRequest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import javax.portlet.ClientDataRequest;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ClientDataRequest} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockClientDataRequest extends MockPortletRequest implements ClientDataRequest {
+
+ private String characterEncoding;
+
+ private byte[] content;
+
+ private String contentType;
+
+ private String method;
+
+
+ /**
+ * Create a new MockClientDataRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @see org.springframework.mock.web.portlet.MockPortalContext
+ * @see org.springframework.mock.web.portlet.MockPortletContext
+ */
+ public MockClientDataRequest() {
+ super();
+ }
+
+ /**
+ * Create a new MockClientDataRequest with a default {@link MockPortalContext}.
+ * @param portletContext the PortletContext that the request runs in
+ */
+ public MockClientDataRequest(PortletContext portletContext) {
+ super(portletContext);
+ }
+
+ /**
+ * Create a new MockClientDataRequest.
+ * @param portalContext the PortalContext that the request runs in
+ * @param portletContext the PortletContext that the request runs in
+ */
+ public MockClientDataRequest(PortalContext portalContext, PortletContext portletContext) {
+ super(portalContext, portletContext);
+ }
+
+
+ public void setContent(byte[] content) {
+ this.content = content;
+ }
+
+ public InputStream getPortletInputStream() throws IOException {
+ if (this.content != null) {
+ return new ByteArrayInputStream(this.content);
+ }
+ else {
+ return null;
+ }
+ }
+
+ public void setCharacterEncoding(String characterEncoding) {
+ this.characterEncoding = characterEncoding;
+ }
+
+ public BufferedReader getReader() throws UnsupportedEncodingException {
+ if (this.content != null) {
+ InputStream sourceStream = new ByteArrayInputStream(this.content);
+ Reader sourceReader = (this.characterEncoding != null) ?
+ new InputStreamReader(sourceStream, this.characterEncoding) : new InputStreamReader(sourceStream);
+ return new BufferedReader(sourceReader);
+ }
+ else {
+ return null;
+ }
+ }
+
+ public String getCharacterEncoding() {
+ return this.characterEncoding;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ public String getContentType() {
+ return this.contentType;
+ }
+
+ public int getContentLength() {
+ return (this.content != null ? content.length : -1);
+ }
+
+ public void setMethod(String method) {
+ this.method = method;
+ }
+
+ public String getMethod() {
+ return this.method;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockEvent.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockEvent.java
new file mode 100644
index 00000000..41e72da9
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockEvent.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.io.Serializable;
+import javax.portlet.Event;
+import javax.xml.namespace.QName;
+
+/**
+ * Mock implementation of the {@link javax.portlet.Event} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see MockEventRequest
+ */
+public class MockEvent implements Event {
+
+ private final QName name;
+
+ private final Serializable value;
+
+
+ /**
+ * Create a new MockEvent with the given name.
+ * @param name the name of the event
+ */
+ public MockEvent(QName name) {
+ this.name = name;
+ this.value = null;
+ }
+
+ /**
+ * Create a new MockEvent with the given name and value.
+ * @param name the name of the event
+ * @param value the associated payload of the event
+ */
+ public MockEvent(QName name, Serializable value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ /**
+ * Create a new MockEvent with the given name.
+ * @param name the name of the event
+ */
+ public MockEvent(String name) {
+ this.name = new QName(name);
+ this.value = null;
+ }
+
+ /**
+ * Create a new MockEvent with the given name and value.
+ * @param name the name of the event
+ * @param value the associated payload of the event
+ */
+ public MockEvent(String name, Serializable value) {
+ this.name = new QName(name);
+ this.value = value;
+ }
+
+
+ public QName getQName() {
+ return this.name;
+ }
+
+ public String getName() {
+ return this.name.getLocalPart();
+ }
+
+ public Serializable getValue() {
+ return this.value;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockEventRequest.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockEventRequest.java
new file mode 100644
index 00000000..a2850e3c
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockEventRequest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import javax.portlet.Event;
+import javax.portlet.EventRequest;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+
+/**
+ * Mock implementation of the {@link javax.portlet.EventRequest} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockEventRequest extends MockPortletRequest implements EventRequest {
+
+ private final Event event;
+
+ private String method;
+
+
+ /**
+ * Create a new MockEventRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @param event the event that this request wraps
+ * @see MockEvent
+ */
+ public MockEventRequest(Event event) {
+ super();
+ this.event = event;
+ }
+
+ /**
+ * Create a new MockEventRequest with a default {@link MockPortalContext}.
+ * @param event the event that this request wraps
+ * @param portletContext the PortletContext that the request runs in
+ * @see MockEvent
+ */
+ public MockEventRequest(Event event, PortletContext portletContext) {
+ super(portletContext);
+ this.event = event;
+ }
+
+ /**
+ * Create a new MockEventRequest.
+ * @param event the event that this request wraps
+ * @param portalContext the PortletContext that the request runs in
+ * @param portletContext the PortletContext that the request runs in
+ */
+ public MockEventRequest(Event event, PortalContext portalContext, PortletContext portletContext) {
+ super(portalContext, portletContext);
+ this.event = event;
+ }
+
+
+ @Override
+ protected String getLifecyclePhase() {
+ return EVENT_PHASE;
+ }
+
+ public Event getEvent() {
+ return this.event;
+ }
+
+ public void setMethod(String method) {
+ this.method = method;
+ }
+
+ public String getMethod() {
+ return this.method;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockEventResponse.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockEventResponse.java
new file mode 100644
index 00000000..1a00e8b2
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockEventResponse.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import javax.portlet.EventRequest;
+import javax.portlet.EventResponse;
+
+/**
+ * Mock implementation of the {@link javax.portlet.EventResponse} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockEventResponse extends MockStateAwareResponse implements EventResponse {
+
+ public void setRenderParameters(EventRequest request) {
+ setRenderParameters(request.getParameterMap());
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockMimeResponse.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockMimeResponse.java
new file mode 100644
index 00000000..4944ab64
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockMimeResponse.java
@@ -0,0 +1,240 @@
+/*
+ * 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.mock.web.portlet;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+import javax.portlet.CacheControl;
+import javax.portlet.MimeResponse;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletURL;
+import javax.portlet.ResourceURL;
+
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.MimeResponse} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockMimeResponse extends MockPortletResponse implements MimeResponse {
+
+ private PortletRequest request;
+
+ private String contentType;
+
+ private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
+
+ private PrintWriter writer;
+
+ private Locale locale = Locale.getDefault();
+
+ private int bufferSize = 4096;
+
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024);
+
+ private final CacheControl cacheControl = new MockCacheControl();
+
+ private boolean committed;
+
+ private String includedUrl;
+
+ private String forwardedUrl;
+
+
+ /**
+ * Create a new MockMimeResponse with a default {@link MockPortalContext}.
+ * @see org.springframework.mock.web.portlet.MockPortalContext
+ */
+ public MockMimeResponse() {
+ super();
+ }
+
+ /**
+ * Create a new MockMimeResponse.
+ * @param portalContext the PortalContext defining the supported
+ * PortletModes and WindowStates
+ */
+ public MockMimeResponse(PortalContext portalContext) {
+ super(portalContext);
+ }
+
+ /**
+ * Create a new MockMimeResponse.
+ * @param portalContext the PortalContext defining the supported
+ * PortletModes and WindowStates
+ * @param request the corresponding render/resource request that this response
+ * is being generated for
+ */
+ public MockMimeResponse(PortalContext portalContext, PortletRequest request) {
+ super(portalContext);
+ this.request = request;
+ }
+
+
+ //---------------------------------------------------------------------
+ // RenderResponse methods
+ //---------------------------------------------------------------------
+
+ public void setContentType(String contentType) {
+ if (this.request != null) {
+ Enumeration<String> supportedTypes = this.request.getResponseContentTypes();
+ if (!CollectionUtils.contains(supportedTypes, contentType)) {
+ throw new IllegalArgumentException("Content type [" + contentType + "] not in supported list: " +
+ Collections.list(supportedTypes));
+ }
+ }
+ this.contentType = contentType;
+ }
+
+ public String getContentType() {
+ return this.contentType;
+ }
+
+ public void setCharacterEncoding(String characterEncoding) {
+ this.characterEncoding = characterEncoding;
+ }
+
+ public String getCharacterEncoding() {
+ return this.characterEncoding;
+ }
+
+ public PrintWriter getWriter() throws UnsupportedEncodingException {
+ if (this.writer == null) {
+ Writer targetWriter = (this.characterEncoding != null ?
+ new OutputStreamWriter(this.outputStream, this.characterEncoding) :
+ new OutputStreamWriter(this.outputStream));
+ this.writer = new PrintWriter(targetWriter);
+ }
+ return this.writer;
+ }
+
+ public byte[] getContentAsByteArray() {
+ flushBuffer();
+ return this.outputStream.toByteArray();
+ }
+
+ public String getContentAsString() throws UnsupportedEncodingException {
+ flushBuffer();
+ return (this.characterEncoding != null ?
+ this.outputStream.toString(this.characterEncoding) : this.outputStream.toString());
+ }
+
+ public void setLocale(Locale locale) {
+ this.locale = locale;
+ }
+
+ public Locale getLocale() {
+ return this.locale;
+ }
+
+ public void setBufferSize(int bufferSize) {
+ this.bufferSize = bufferSize;
+ }
+
+ public int getBufferSize() {
+ return this.bufferSize;
+ }
+
+ public void flushBuffer() {
+ if (this.writer != null) {
+ this.writer.flush();
+ }
+ try {
+ this.outputStream.flush();
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException("Could not flush OutputStream: " + ex.getMessage());
+ }
+ this.committed = true;
+ }
+
+ public void resetBuffer() {
+ if (this.committed) {
+ throw new IllegalStateException("Cannot reset buffer - response is already committed");
+ }
+ this.outputStream.reset();
+ }
+
+ public void setCommitted(boolean committed) {
+ this.committed = committed;
+ }
+
+ public boolean isCommitted() {
+ return this.committed;
+ }
+
+ public void reset() {
+ resetBuffer();
+ this.characterEncoding = null;
+ this.contentType = null;
+ this.locale = null;
+ }
+
+ public OutputStream getPortletOutputStream() throws IOException {
+ return this.outputStream;
+ }
+
+ public PortletURL createRenderURL() {
+ return new MockPortletURL(getPortalContext(), MockPortletURL.URL_TYPE_RENDER);
+ }
+
+ public PortletURL createActionURL() {
+ return new MockPortletURL(getPortalContext(), MockPortletURL.URL_TYPE_ACTION);
+ }
+
+ public ResourceURL createResourceURL() {
+ return new MockResourceURL();
+ }
+
+ public CacheControl getCacheControl() {
+ return this.cacheControl;
+ }
+
+
+ //---------------------------------------------------------------------
+ // Methods for MockPortletRequestDispatcher
+ //---------------------------------------------------------------------
+
+ public void setIncludedUrl(String includedUrl) {
+ this.includedUrl = includedUrl;
+ }
+
+ public String getIncludedUrl() {
+ return this.includedUrl;
+ }
+
+ public void setForwardedUrl(String forwardedUrl) {
+ this.forwardedUrl = forwardedUrl;
+ }
+
+ public String getForwardedUrl() {
+ return this.forwardedUrl;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java
new file mode 100644
index 00000000..7df8b547
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2002-2011 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.mock.web.portlet;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.portlet.multipart.MultipartActionRequest;
+
+/**
+ * Mock implementation of the
+ * {@link org.springframework.web.portlet.multipart.MultipartActionRequest} interface.
+ *
+ * <p>Useful for testing application controllers that access multipart uploads.
+ * The {@link org.springframework.mock.web.MockMultipartFile} can be used to
+ * populate these mock requests with files.
+ *
+ * @author Juergen Hoeller
+ * @author Arjen Poutsma
+ * @since 2.0
+ * @see org.springframework.mock.web.MockMultipartFile
+ */
+public class MockMultipartActionRequest extends MockActionRequest implements MultipartActionRequest {
+
+ private final MultiValueMap<String, MultipartFile> multipartFiles =
+ new LinkedMultiValueMap<String, MultipartFile>();
+
+
+ /**
+ * Add a file to this request. The parameter name from the multipart
+ * form is taken from the {@link org.springframework.web.multipart.MultipartFile#getName()}.
+ * @param file multipart file to be added
+ */
+ public void addFile(MultipartFile file) {
+ Assert.notNull(file, "MultipartFile must not be null");
+ this.multipartFiles.add(file.getName(), file);
+ }
+
+ public Iterator<String> getFileNames() {
+ return this.multipartFiles.keySet().iterator();
+ }
+
+ public MultipartFile getFile(String name) {
+ return this.multipartFiles.getFirst(name);
+ }
+
+ public List<MultipartFile> getFiles(String name) {
+ List<MultipartFile> multipartFiles = this.multipartFiles.get(name);
+ if (multipartFiles != null) {
+ return multipartFiles;
+ }
+ else {
+ return Collections.emptyList();
+ }
+ }
+
+ public Map<String, MultipartFile> getFileMap() {
+ return this.multipartFiles.toSingleValueMap();
+ }
+
+ public MultiValueMap<String, MultipartFile> getMultiFileMap() {
+ return new LinkedMultiValueMap<String, MultipartFile>(this.multipartFiles);
+ }
+
+ public String getMultipartContentType(String paramOrFileName) {
+ MultipartFile file = getFile(paramOrFileName);
+ if (file != null) {
+ return file.getContentType();
+ }
+ else {
+ return null;
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortalContext.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortalContext.java
new file mode 100644
index 00000000..949d077b
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortalContext.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.WindowState;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortalContext} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortalContext implements PortalContext {
+
+ private final Map<String, String> properties = new HashMap<String, String>();
+
+ private final List<PortletMode> portletModes;
+
+ private final List<WindowState> windowStates;
+
+
+ /**
+ * Create a new MockPortalContext
+ * with default PortletModes (VIEW, EDIT, HELP)
+ * and default WindowStates (NORMAL, MAXIMIZED, MINIMIZED).
+ * @see javax.portlet.PortletMode
+ * @see javax.portlet.WindowState
+ */
+ public MockPortalContext() {
+ this.portletModes = new ArrayList<PortletMode>(3);
+ this.portletModes.add(PortletMode.VIEW);
+ this.portletModes.add(PortletMode.EDIT);
+ this.portletModes.add(PortletMode.HELP);
+
+ this.windowStates = new ArrayList<WindowState>(3);
+ this.windowStates.add(WindowState.NORMAL);
+ this.windowStates.add(WindowState.MAXIMIZED);
+ this.windowStates.add(WindowState.MINIMIZED);
+ }
+
+ /**
+ * Create a new MockPortalContext with the given PortletModes and WindowStates.
+ * @param supportedPortletModes the List of supported PortletMode instances
+ * @param supportedWindowStates the List of supported WindowState instances
+ * @see javax.portlet.PortletMode
+ * @see javax.portlet.WindowState
+ */
+ public MockPortalContext(List<PortletMode> supportedPortletModes, List<WindowState> supportedWindowStates) {
+ this.portletModes = new ArrayList<PortletMode>(supportedPortletModes);
+ this.windowStates = new ArrayList<WindowState>(supportedWindowStates);
+ }
+
+
+ public String getPortalInfo() {
+ return "MockPortal/1.0";
+ }
+
+ public void setProperty(String name, String value) {
+ this.properties.put(name, value);
+ }
+
+ public String getProperty(String name) {
+ return this.properties.get(name);
+ }
+
+ public Enumeration<String> getPropertyNames() {
+ return Collections.enumeration(this.properties.keySet());
+ }
+
+ public Enumeration<PortletMode> getSupportedPortletModes() {
+ return Collections.enumeration(this.portletModes);
+ }
+
+ public Enumeration<WindowState> getSupportedWindowStates() {
+ return Collections.enumeration(this.windowStates);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletConfig.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletConfig.java
new file mode 100644
index 00000000..9d3a8780
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletConfig.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2002-2012 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.mock.web.portlet;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletContext;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletConfig} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletConfig implements PortletConfig {
+
+ private final PortletContext portletContext;
+
+ private final String portletName;
+
+ private final Map<Locale, ResourceBundle> resourceBundles = new HashMap<Locale, ResourceBundle>();
+
+ private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
+
+ private final Set<String> publicRenderParameterNames = new LinkedHashSet<String>();
+
+ private String defaultNamespace = XMLConstants.NULL_NS_URI;
+
+ private final Set<QName> publishingEventQNames = new LinkedHashSet<QName>();
+
+ private final Set<QName> processingEventQNames = new LinkedHashSet<QName>();
+
+ private final Set<Locale> supportedLocales = new LinkedHashSet<Locale>();
+
+ private final Map<String, String[]> containerRuntimeOptions = new LinkedHashMap<String, String[]>();
+
+
+ /**
+ * Create a new MockPortletConfig with a default {@link MockPortletContext}.
+ */
+ public MockPortletConfig() {
+ this(null, "");
+ }
+
+ /**
+ * Create a new MockPortletConfig with a default {@link MockPortletContext}.
+ * @param portletName the name of the portlet
+ */
+ public MockPortletConfig(String portletName) {
+ this(null, portletName);
+ }
+
+ /**
+ * Create a new MockPortletConfig.
+ * @param portletContext the PortletContext that the portlet runs in
+ */
+ public MockPortletConfig(PortletContext portletContext) {
+ this(portletContext, "");
+ }
+
+ /**
+ * Create a new MockPortletConfig.
+ * @param portletContext the PortletContext that the portlet runs in
+ * @param portletName the name of the portlet
+ */
+ public MockPortletConfig(PortletContext portletContext, String portletName) {
+ this.portletContext = (portletContext != null ? portletContext : new MockPortletContext());
+ this.portletName = portletName;
+ }
+
+
+ public String getPortletName() {
+ return this.portletName;
+ }
+
+ public PortletContext getPortletContext() {
+ return this.portletContext;
+ }
+
+ public void setResourceBundle(Locale locale, ResourceBundle resourceBundle) {
+ Assert.notNull(locale, "Locale must not be null");
+ this.resourceBundles.put(locale, resourceBundle);
+ }
+
+ public ResourceBundle getResourceBundle(Locale locale) {
+ Assert.notNull(locale, "Locale must not be null");
+ return this.resourceBundles.get(locale);
+ }
+
+ public void addInitParameter(String name, String value) {
+ Assert.notNull(name, "Parameter name must not be null");
+ this.initParameters.put(name, value);
+ }
+
+ public String getInitParameter(String name) {
+ Assert.notNull(name, "Parameter name must not be null");
+ return this.initParameters.get(name);
+ }
+
+ public Enumeration<String> getInitParameterNames() {
+ return Collections.enumeration(this.initParameters.keySet());
+ }
+
+ public void addPublicRenderParameterName(String name) {
+ this.publicRenderParameterNames.add(name);
+ }
+
+ public Enumeration<String> getPublicRenderParameterNames() {
+ return Collections.enumeration(this.publicRenderParameterNames);
+ }
+
+ public void setDefaultNamespace(String defaultNamespace) {
+ this.defaultNamespace = defaultNamespace;
+ }
+
+ public String getDefaultNamespace() {
+ return this.defaultNamespace;
+ }
+
+ public void addPublishingEventQName(QName name) {
+ this.publishingEventQNames.add(name);
+ }
+
+ public Enumeration<QName> getPublishingEventQNames() {
+ return Collections.enumeration(this.publishingEventQNames);
+ }
+
+ public void addProcessingEventQName(QName name) {
+ this.processingEventQNames.add(name);
+ }
+
+ public Enumeration<QName> getProcessingEventQNames() {
+ return Collections.enumeration(this.processingEventQNames);
+ }
+
+ public void addSupportedLocale(Locale locale) {
+ this.supportedLocales.add(locale);
+ }
+
+ public Enumeration<Locale> getSupportedLocales() {
+ return Collections.enumeration(this.supportedLocales);
+ }
+
+ public void addContainerRuntimeOption(String key, String value) {
+ this.containerRuntimeOptions.put(key, new String[] {value});
+ }
+
+ public void addContainerRuntimeOption(String key, String[] values) {
+ this.containerRuntimeOptions.put(key, values);
+ }
+
+ public Map<String, String[]> getContainerRuntimeOptions() {
+ return Collections.unmodifiableMap(this.containerRuntimeOptions);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletContext.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletContext.java
new file mode 100644
index 00000000..39dc9f5b
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletContext.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.mock.web.portlet;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.activation.FileTypeMap;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletRequestDispatcher;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.util.Assert;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletContext} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletContext implements PortletContext {
+
+ private static final String TEMP_DIR_SYSTEM_PROPERTY = "java.io.tmpdir";
+
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final String resourceBasePath;
+
+ private final ResourceLoader resourceLoader;
+
+ private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+
+ private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
+
+ private String portletContextName = "MockPortletContext";
+
+ private Set<String> containerRuntimeOptions = new LinkedHashSet<String>();
+
+
+ /**
+ * Create a new MockPortletContext with no base path and a
+ * DefaultResourceLoader (i.e. the classpath root as WAR root).
+ * @see org.springframework.core.io.DefaultResourceLoader
+ */
+ public MockPortletContext() {
+ this("", null);
+ }
+
+ /**
+ * Create a new MockPortletContext using a DefaultResourceLoader.
+ * @param resourceBasePath the WAR root directory (should not end with a slash)
+ * @see org.springframework.core.io.DefaultResourceLoader
+ */
+ public MockPortletContext(String resourceBasePath) {
+ this(resourceBasePath, null);
+ }
+
+ /**
+ * Create a new MockPortletContext, using the specified ResourceLoader
+ * and no base path.
+ * @param resourceLoader the ResourceLoader to use (or null for the default)
+ */
+ public MockPortletContext(ResourceLoader resourceLoader) {
+ this("", resourceLoader);
+ }
+
+ /**
+ * Create a new MockPortletContext.
+ * @param resourceBasePath the WAR root directory (should not end with a slash)
+ * @param resourceLoader the ResourceLoader to use (or null for the default)
+ */
+ public MockPortletContext(String resourceBasePath, ResourceLoader resourceLoader) {
+ this.resourceBasePath = (resourceBasePath != null ? resourceBasePath : "");
+ this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
+
+ // Use JVM temp dir as PortletContext temp dir.
+ String tempDir = System.getProperty(TEMP_DIR_SYSTEM_PROPERTY);
+ if (tempDir != null) {
+ this.attributes.put(WebUtils.TEMP_DIR_CONTEXT_ATTRIBUTE, new File(tempDir));
+ }
+ }
+
+ /**
+ * Build a full resource location for the given path,
+ * prepending the resource base path of this MockPortletContext.
+ * @param path the path as specified
+ * @return the full resource path
+ */
+ protected String getResourceLocation(String path) {
+ if (!path.startsWith("/")) {
+ path = "/" + path;
+ }
+ return this.resourceBasePath + path;
+ }
+
+
+ public String getServerInfo() {
+ return "MockPortal/1.0";
+ }
+
+ public PortletRequestDispatcher getRequestDispatcher(String path) {
+ if (!path.startsWith("/")) {
+ throw new IllegalArgumentException(
+ "PortletRequestDispatcher path at PortletContext level must start with '/'");
+ }
+ return new MockPortletRequestDispatcher(path);
+ }
+
+ public PortletRequestDispatcher getNamedDispatcher(String path) {
+ return null;
+ }
+
+ public InputStream getResourceAsStream(String path) {
+ Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
+ try {
+ return resource.getInputStream();
+ }
+ catch (IOException ex) {
+ logger.info("Couldn't open InputStream for " + resource, ex);
+ return null;
+ }
+ }
+
+ public int getMajorVersion() {
+ return 2;
+ }
+
+ public int getMinorVersion() {
+ return 0;
+ }
+
+ public String getMimeType(String filePath) {
+ return MimeTypeResolver.getMimeType(filePath);
+ }
+
+ public String getRealPath(String path) {
+ Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
+ try {
+ return resource.getFile().getAbsolutePath();
+ }
+ catch (IOException ex) {
+ logger.info("Couldn't determine real path of resource " + resource, ex);
+ return null;
+ }
+ }
+
+ public Set<String> getResourcePaths(String path) {
+ Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
+ try {
+ File file = resource.getFile();
+ String[] fileList = file.list();
+ String prefix = (path.endsWith("/") ? path : path + "/");
+ Set<String> resourcePaths = new HashSet<String>(fileList.length);
+ for (String fileEntry : fileList) {
+ resourcePaths.add(prefix + fileEntry);
+ }
+ return resourcePaths;
+ }
+ catch (IOException ex) {
+ logger.info("Couldn't get resource paths for " + resource, ex);
+ return null;
+ }
+ }
+
+ public URL getResource(String path) throws MalformedURLException {
+ Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
+ try {
+ return resource.getURL();
+ }
+ catch (IOException ex) {
+ logger.info("Couldn't get URL for " + resource, ex);
+ return null;
+ }
+ }
+
+ public Object getAttribute(String name) {
+ return this.attributes.get(name);
+ }
+
+ public Enumeration<String> getAttributeNames() {
+ return Collections.enumeration(new LinkedHashSet<String>(this.attributes.keySet()));
+ }
+
+ public void setAttribute(String name, Object value) {
+ if (value != null) {
+ this.attributes.put(name, value);
+ }
+ else {
+ this.attributes.remove(name);
+ }
+ }
+
+ public void removeAttribute(String name) {
+ this.attributes.remove(name);
+ }
+
+ public void addInitParameter(String name, String value) {
+ Assert.notNull(name, "Parameter name must not be null");
+ this.initParameters.put(name, value);
+ }
+
+ public String getInitParameter(String name) {
+ Assert.notNull(name, "Parameter name must not be null");
+ return this.initParameters.get(name);
+ }
+
+ public Enumeration<String> getInitParameterNames() {
+ return Collections.enumeration(this.initParameters.keySet());
+ }
+
+ public void log(String message) {
+ logger.info(message);
+ }
+
+ public void log(String message, Throwable t) {
+ logger.info(message, t);
+ }
+
+ public void setPortletContextName(String portletContextName) {
+ this.portletContextName = portletContextName;
+ }
+
+ public String getPortletContextName() {
+ return this.portletContextName;
+ }
+
+ public void addContainerRuntimeOption(String key) {
+ this.containerRuntimeOptions.add(key);
+ }
+
+ public Enumeration<String> getContainerRuntimeOptions() {
+ return Collections.enumeration(this.containerRuntimeOptions);
+ }
+
+
+ /**
+ * Inner factory class used to just introduce a Java Activation Framework
+ * dependency when actually asked to resolve a MIME type.
+ */
+ private static class MimeTypeResolver {
+
+ public static String getMimeType(String filePath) {
+ return FileTypeMap.getDefaultFileTypeMap().getContentType(filePath);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletPreferences.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletPreferences.java
new file mode 100644
index 00000000..92ee6c0a
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletPreferences.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.portlet.PortletPreferences;
+import javax.portlet.PreferencesValidator;
+import javax.portlet.ReadOnlyException;
+import javax.portlet.ValidatorException;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletPreferences} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletPreferences implements PortletPreferences {
+
+ private PreferencesValidator preferencesValidator;
+
+ private final Map<String, String[]> preferences = new LinkedHashMap<String, String[]>();
+
+ private final Set<String> readOnly = new HashSet<String>();
+
+
+ public void setReadOnly(String key, boolean readOnly) {
+ Assert.notNull(key, "Key must not be null");
+ if (readOnly) {
+ this.readOnly.add(key);
+ }
+ else {
+ this.readOnly.remove(key);
+ }
+ }
+
+ public boolean isReadOnly(String key) {
+ Assert.notNull(key, "Key must not be null");
+ return this.readOnly.contains(key);
+ }
+
+ public String getValue(String key, String def) {
+ Assert.notNull(key, "Key must not be null");
+ String[] values = this.preferences.get(key);
+ return (values != null && values.length > 0 ? values[0] : def);
+ }
+
+ public String[] getValues(String key, String[] def) {
+ Assert.notNull(key, "Key must not be null");
+ String[] values = this.preferences.get(key);
+ return (values != null && values.length > 0 ? values : def);
+ }
+
+ public void setValue(String key, String value) throws ReadOnlyException {
+ setValues(key, new String[] {value});
+ }
+
+ public void setValues(String key, String[] values) throws ReadOnlyException {
+ Assert.notNull(key, "Key must not be null");
+ if (isReadOnly(key)) {
+ throw new ReadOnlyException("Preference '" + key + "' is read-only");
+ }
+ this.preferences.put(key, values);
+ }
+
+ public Enumeration<String> getNames() {
+ return Collections.enumeration(this.preferences.keySet());
+ }
+
+ public Map<String, String[]> getMap() {
+ return Collections.unmodifiableMap(this.preferences);
+ }
+
+ public void reset(String key) throws ReadOnlyException {
+ Assert.notNull(key, "Key must not be null");
+ if (isReadOnly(key)) {
+ throw new ReadOnlyException("Preference '" + key + "' is read-only");
+ }
+ this.preferences.remove(key);
+ }
+
+ public void setPreferencesValidator(PreferencesValidator preferencesValidator) {
+ this.preferencesValidator = preferencesValidator;
+ }
+
+ public void store() throws IOException, ValidatorException {
+ if (this.preferencesValidator != null) {
+ this.preferencesValidator.validate(this);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletRequest.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletRequest.java
new file mode 100644
index 00000000..38b51735
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletRequest.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.mock.web.portlet;
+
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletPreferences;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletSession;
+import javax.portlet.WindowState;
+import javax.servlet.http.Cookie;
+
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletRequest} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletRequest implements PortletRequest {
+
+ private boolean active = true;
+
+ private final PortalContext portalContext;
+
+ private final PortletContext portletContext;
+
+ private PortletSession session;
+
+ private WindowState windowState = WindowState.NORMAL;
+
+ private PortletMode portletMode = PortletMode.VIEW;
+
+ private PortletPreferences portletPreferences = new MockPortletPreferences();
+
+ private final Map<String, List<String>> properties = new LinkedHashMap<String, List<String>>();
+
+ private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+
+ private final Map<String, String[]> parameters = new LinkedHashMap<String, String[]>();
+
+ private String authType = null;
+
+ private String contextPath = "";
+
+ private String remoteUser = null;
+
+ private Principal userPrincipal = null;
+
+ private final Set<String> userRoles = new HashSet<String>();
+
+ private boolean secure = false;
+
+ private boolean requestedSessionIdValid = true;
+
+ private final List<String> responseContentTypes = new LinkedList<String>();
+
+ private final List<Locale> locales = new LinkedList<Locale>();
+
+ private String scheme = "http";
+
+ private String serverName = "localhost";
+
+ private int serverPort = 80;
+
+ private String windowID;
+
+ private Cookie[] cookies;
+
+ private final Set<String> publicParameterNames = new HashSet<String>();
+
+
+ /**
+ * Create a new MockPortletRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ *
+ * @see MockPortalContext
+ * @see MockPortletContext
+ */
+ public MockPortletRequest() {
+ this(null, null);
+ }
+
+ /**
+ * Create a new MockPortletRequest with a default {@link MockPortalContext}.
+ *
+ * @param portletContext the PortletContext that the request runs in
+ * @see MockPortalContext
+ */
+ public MockPortletRequest(PortletContext portletContext) {
+ this(null, portletContext);
+ }
+
+ /**
+ * Create a new MockPortletRequest.
+ *
+ * @param portalContext the PortalContext that the request runs in
+ * @param portletContext the PortletContext that the request runs in
+ */
+ public MockPortletRequest(PortalContext portalContext, PortletContext portletContext) {
+ this.portalContext = (portalContext != null ? portalContext : new MockPortalContext());
+ this.portletContext = (portletContext != null ? portletContext : new MockPortletContext());
+ this.responseContentTypes.add("text/html");
+ this.locales.add(Locale.ENGLISH);
+ this.attributes.put(LIFECYCLE_PHASE, getLifecyclePhase());
+ }
+
+ // ---------------------------------------------------------------------
+ // Lifecycle methods
+ // ---------------------------------------------------------------------
+
+ /**
+ * Return the Portlet 2.0 lifecycle id for the current phase.
+ */
+ protected String getLifecyclePhase() {
+ return null;
+ }
+
+ /**
+ * Return whether this request is still active (that is, not completed yet).
+ */
+ public boolean isActive() {
+ return this.active;
+ }
+
+ /**
+ * Mark this request as completed.
+ */
+ public void close() {
+ this.active = false;
+ }
+
+ /**
+ * Check whether this request is still active (that is, not completed yet),
+ * throwing an IllegalStateException if not active anymore.
+ */
+ protected void checkActive() throws IllegalStateException {
+ if (!this.active) {
+ throw new IllegalStateException("Request is not active anymore");
+ }
+ }
+
+ // ---------------------------------------------------------------------
+ // PortletRequest methods
+ // ---------------------------------------------------------------------
+
+ public boolean isWindowStateAllowed(WindowState windowState) {
+ return CollectionUtils.contains(this.portalContext.getSupportedWindowStates(), windowState);
+ }
+
+ public boolean isPortletModeAllowed(PortletMode portletMode) {
+ return CollectionUtils.contains(this.portalContext.getSupportedPortletModes(), portletMode);
+ }
+
+ public void setPortletMode(PortletMode portletMode) {
+ Assert.notNull(portletMode, "PortletMode must not be null");
+ this.portletMode = portletMode;
+ }
+
+ public PortletMode getPortletMode() {
+ return this.portletMode;
+ }
+
+ public void setWindowState(WindowState windowState) {
+ Assert.notNull(windowState, "WindowState must not be null");
+ this.windowState = windowState;
+ }
+
+ public WindowState getWindowState() {
+ return this.windowState;
+ }
+
+ public void setPreferences(PortletPreferences preferences) {
+ Assert.notNull(preferences, "PortletPreferences must not be null");
+ this.portletPreferences = preferences;
+ }
+
+ public PortletPreferences getPreferences() {
+ return this.portletPreferences;
+ }
+
+ public void setSession(PortletSession session) {
+ this.session = session;
+ if (session instanceof MockPortletSession) {
+ MockPortletSession mockSession = ((MockPortletSession) session);
+ mockSession.access();
+ }
+ }
+
+ public PortletSession getPortletSession() {
+ return getPortletSession(true);
+ }
+
+ public PortletSession getPortletSession(boolean create) {
+ checkActive();
+ // Reset session if invalidated.
+ if (this.session instanceof MockPortletSession && ((MockPortletSession) this.session).isInvalid()) {
+ this.session = null;
+ }
+ // Create new session if necessary.
+ if (this.session == null && create) {
+ this.session = new MockPortletSession(this.portletContext);
+ }
+ return this.session;
+ }
+
+ /**
+ * Set a single value for the specified property.
+ * <p>
+ * If there are already one or more values registered for the given property
+ * key, they will be replaced.
+ */
+ public void setProperty(String key, String value) {
+ Assert.notNull(key, "Property key must not be null");
+ List<String> list = new LinkedList<String>();
+ list.add(value);
+ this.properties.put(key, list);
+ }
+
+ /**
+ * Add a single value for the specified property.
+ * <p>
+ * If there are already one or more values registered for the given property
+ * key, the given value will be added to the end of the list.
+ */
+ public void addProperty(String key, String value) {
+ Assert.notNull(key, "Property key must not be null");
+ List<String> oldList = this.properties.get(key);
+ if (oldList != null) {
+ oldList.add(value);
+ }
+ else {
+ List<String> list = new LinkedList<String>();
+ list.add(value);
+ this.properties.put(key, list);
+ }
+ }
+
+ public String getProperty(String key) {
+ Assert.notNull(key, "Property key must not be null");
+ List<String> list = this.properties.get(key);
+ return (list != null && list.size() > 0 ? list.get(0) : null);
+ }
+
+ public Enumeration<String> getProperties(String key) {
+ Assert.notNull(key, "property key must not be null");
+ return Collections.enumeration(this.properties.get(key));
+ }
+
+ public Enumeration<String> getPropertyNames() {
+ return Collections.enumeration(this.properties.keySet());
+ }
+
+ public PortalContext getPortalContext() {
+ return this.portalContext;
+ }
+
+ public void setAuthType(String authType) {
+ this.authType = authType;
+ }
+
+ public String getAuthType() {
+ return this.authType;
+ }
+
+ public void setContextPath(String contextPath) {
+ this.contextPath = contextPath;
+ }
+
+ public String getContextPath() {
+ return this.contextPath;
+ }
+
+ public void setRemoteUser(String remoteUser) {
+ this.remoteUser = remoteUser;
+ }
+
+ public String getRemoteUser() {
+ return this.remoteUser;
+ }
+
+ public void setUserPrincipal(Principal userPrincipal) {
+ this.userPrincipal = userPrincipal;
+ }
+
+ public Principal getUserPrincipal() {
+ return this.userPrincipal;
+ }
+
+ public void addUserRole(String role) {
+ this.userRoles.add(role);
+ }
+
+ public boolean isUserInRole(String role) {
+ return this.userRoles.contains(role);
+ }
+
+ public Object getAttribute(String name) {
+ checkActive();
+ return this.attributes.get(name);
+ }
+
+ public Enumeration<String> getAttributeNames() {
+ checkActive();
+ return Collections.enumeration(new LinkedHashSet<String>(this.attributes.keySet()));
+ }
+
+ public void setParameters(Map<String, String[]> parameters) {
+ Assert.notNull(parameters, "Parameters Map must not be null");
+ this.parameters.clear();
+ this.parameters.putAll(parameters);
+ }
+
+ public void setParameter(String key, String value) {
+ Assert.notNull(key, "Parameter key must be null");
+ Assert.notNull(value, "Parameter value must not be null");
+ this.parameters.put(key, new String[] { value });
+ }
+
+ public void setParameter(String key, String[] values) {
+ Assert.notNull(key, "Parameter key must be null");
+ Assert.notNull(values, "Parameter values must not be null");
+ this.parameters.put(key, values);
+ }
+
+ public void addParameter(String name, String value) {
+ addParameter(name, new String[] { value });
+ }
+
+ public void addParameter(String name, String[] values) {
+ String[] oldArr = this.parameters.get(name);
+ if (oldArr != null) {
+ String[] newArr = new String[oldArr.length + values.length];
+ System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
+ System.arraycopy(values, 0, newArr, oldArr.length, values.length);
+ this.parameters.put(name, newArr);
+ }
+ else {
+ this.parameters.put(name, values);
+ }
+ }
+
+ public String getParameter(String name) {
+ String[] arr = this.parameters.get(name);
+ return (arr != null && arr.length > 0 ? arr[0] : null);
+ }
+
+ public Enumeration<String> getParameterNames() {
+ return Collections.enumeration(this.parameters.keySet());
+ }
+
+ public String[] getParameterValues(String name) {
+ return this.parameters.get(name);
+ }
+
+ public Map<String, String[]> getParameterMap() {
+ return Collections.unmodifiableMap(this.parameters);
+ }
+
+ public void setSecure(boolean secure) {
+ this.secure = secure;
+ }
+
+ public boolean isSecure() {
+ return this.secure;
+ }
+
+ public void setAttribute(String name, Object value) {
+ checkActive();
+ if (value != null) {
+ this.attributes.put(name, value);
+ }
+ else {
+ this.attributes.remove(name);
+ }
+ }
+
+ public void removeAttribute(String name) {
+ checkActive();
+ this.attributes.remove(name);
+ }
+
+ public String getRequestedSessionId() {
+ PortletSession session = this.getPortletSession();
+ return (session != null ? session.getId() : null);
+ }
+
+ public void setRequestedSessionIdValid(boolean requestedSessionIdValid) {
+ this.requestedSessionIdValid = requestedSessionIdValid;
+ }
+
+ public boolean isRequestedSessionIdValid() {
+ return this.requestedSessionIdValid;
+ }
+
+ public void addResponseContentType(String responseContentType) {
+ this.responseContentTypes.add(responseContentType);
+ }
+
+ public void addPreferredResponseContentType(String responseContentType) {
+ this.responseContentTypes.add(0, responseContentType);
+ }
+
+ public String getResponseContentType() {
+ return this.responseContentTypes.get(0);
+ }
+
+ public Enumeration<String> getResponseContentTypes() {
+ return Collections.enumeration(this.responseContentTypes);
+ }
+
+ public void addLocale(Locale locale) {
+ this.locales.add(locale);
+ }
+
+ public void addPreferredLocale(Locale locale) {
+ this.locales.add(0, locale);
+ }
+
+ public Locale getLocale() {
+ return this.locales.get(0);
+ }
+
+ public Enumeration<Locale> getLocales() {
+ return Collections.enumeration(this.locales);
+ }
+
+ public void setScheme(String scheme) {
+ this.scheme = scheme;
+ }
+
+ public String getScheme() {
+ return this.scheme;
+ }
+
+ public void setServerName(String serverName) {
+ this.serverName = serverName;
+ }
+
+ public String getServerName() {
+ return this.serverName;
+ }
+
+ public void setServerPort(int serverPort) {
+ this.serverPort = serverPort;
+ }
+
+ public int getServerPort() {
+ return this.serverPort;
+ }
+
+ public void setWindowID(String windowID) {
+ this.windowID = windowID;
+ }
+
+ public String getWindowID() {
+ return this.windowID;
+ }
+
+ public void setCookies(Cookie... cookies) {
+ this.cookies = cookies;
+ }
+
+ public Cookie[] getCookies() {
+ return this.cookies;
+ }
+
+ public Map<String, String[]> getPrivateParameterMap() {
+ if (!this.publicParameterNames.isEmpty()) {
+ Map<String, String[]> filtered = new LinkedHashMap<String, String[]>();
+ for (String key : this.parameters.keySet()) {
+ if (!this.publicParameterNames.contains(key)) {
+ filtered.put(key, this.parameters.get(key));
+ }
+ }
+ return filtered;
+ }
+ else {
+ return Collections.unmodifiableMap(this.parameters);
+ }
+ }
+
+ public Map<String, String[]> getPublicParameterMap() {
+ if (!this.publicParameterNames.isEmpty()) {
+ Map<String, String[]> filtered = new LinkedHashMap<String, String[]>();
+ for (String key : this.parameters.keySet()) {
+ if (this.publicParameterNames.contains(key)) {
+ filtered.put(key, this.parameters.get(key));
+ }
+ }
+ return filtered;
+ }
+ else {
+ return Collections.emptyMap();
+ }
+ }
+
+ public void registerPublicParameter(String name) {
+ this.publicParameterNames.add(name);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletRequestDispatcher.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletRequestDispatcher.java
new file mode 100644
index 00000000..ca8695f9
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletRequestDispatcher.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.io.IOException;
+import javax.portlet.PortletException;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletRequestDispatcher;
+import javax.portlet.PortletResponse;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletRequestDispatcher} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletRequestDispatcher implements PortletRequestDispatcher {
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final String url;
+
+
+ /**
+ * Create a new MockPortletRequestDispatcher for the given URL.
+ * @param url the URL to dispatch to.
+ */
+ public MockPortletRequestDispatcher(String url) {
+ Assert.notNull(url, "URL must not be null");
+ this.url = url;
+ }
+
+
+ public void include(RenderRequest request, RenderResponse response) throws PortletException, IOException {
+ include((PortletRequest) request, (PortletResponse) response);
+ }
+
+ public void include(PortletRequest request, PortletResponse response) throws PortletException, IOException {
+ Assert.notNull(request, "Request must not be null");
+ Assert.notNull(response, "Response must not be null");
+ if (!(response instanceof MockMimeResponse)) {
+ throw new IllegalArgumentException("MockPortletRequestDispatcher requires MockMimeResponse");
+ }
+ ((MockMimeResponse) response).setIncludedUrl(this.url);
+ if (logger.isDebugEnabled()) {
+ logger.debug("MockPortletRequestDispatcher: including URL [" + this.url + "]");
+ }
+ }
+
+ public void forward(PortletRequest request, PortletResponse response) throws PortletException, IOException {
+ Assert.notNull(request, "Request must not be null");
+ Assert.notNull(response, "Response must not be null");
+ if (!(response instanceof MockMimeResponse)) {
+ throw new IllegalArgumentException("MockPortletRequestDispatcher requires MockMimeResponse");
+ }
+ ((MockMimeResponse) response).setForwardedUrl(this.url);
+ if (logger.isDebugEnabled()) {
+ logger.debug("MockPortletRequestDispatcher: forwarding to URL [" + this.url + "]");
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletResponse.java
new file mode 100644
index 00000000..b556209f
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletResponse.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletResponse;
+import javax.servlet.http.Cookie;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletResponse} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletResponse implements PortletResponse {
+
+ private final PortalContext portalContext;
+
+ private final Map<String, String[]> properties = new LinkedHashMap<String, String[]>();
+
+ private String namespace = "";
+
+ private final Set<Cookie> cookies = new LinkedHashSet<Cookie>();
+
+ private final Map<String, Element[]> xmlProperties = new LinkedHashMap<String, Element[]>();
+
+ private Document xmlDocument;
+
+
+ /**
+ * Create a new MockPortletResponse with a default {@link MockPortalContext}.
+ * @see MockPortalContext
+ */
+ public MockPortletResponse() {
+ this(null);
+ }
+
+ /**
+ * Create a new MockPortletResponse.
+ * @param portalContext the PortalContext defining the supported
+ * PortletModes and WindowStates
+ */
+ public MockPortletResponse(PortalContext portalContext) {
+ this.portalContext = (portalContext != null ? portalContext : new MockPortalContext());
+ }
+
+ /**
+ * Return the PortalContext that this MockPortletResponse runs in,
+ * defining the supported PortletModes and WindowStates.
+ */
+ public PortalContext getPortalContext() {
+ return this.portalContext;
+ }
+
+
+ //---------------------------------------------------------------------
+ // PortletResponse methods
+ //---------------------------------------------------------------------
+
+ public void addProperty(String key, String value) {
+ Assert.notNull(key, "Property key must not be null");
+ String[] oldArr = this.properties.get(key);
+ if (oldArr != null) {
+ String[] newArr = new String[oldArr.length + 1];
+ System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
+ newArr[oldArr.length] = value;
+ this.properties.put(key, newArr);
+ }
+ else {
+ this.properties.put(key, new String[] {value});
+ }
+ }
+
+ public void setProperty(String key, String value) {
+ Assert.notNull(key, "Property key must not be null");
+ this.properties.put(key, new String[] {value});
+ }
+
+ public Set<String> getPropertyNames() {
+ return Collections.unmodifiableSet(this.properties.keySet());
+ }
+
+ public String getProperty(String key) {
+ Assert.notNull(key, "Property key must not be null");
+ String[] arr = this.properties.get(key);
+ return (arr != null && arr.length > 0 ? arr[0] : null);
+ }
+
+ public String[] getProperties(String key) {
+ Assert.notNull(key, "Property key must not be null");
+ return this.properties.get(key);
+ }
+
+ public String encodeURL(String path) {
+ return path;
+ }
+
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ public String getNamespace() {
+ return this.namespace;
+ }
+
+ public void addProperty(Cookie cookie) {
+ Assert.notNull(cookie, "Cookie must not be null");
+ this.cookies.add(cookie);
+ }
+
+ public Cookie[] getCookies() {
+ return this.cookies.toArray(new Cookie[this.cookies.size()]);
+ }
+
+ public Cookie getCookie(String name) {
+ Assert.notNull(name, "Cookie name must not be null");
+ for (Cookie cookie : this.cookies) {
+ if (name.equals(cookie.getName())) {
+ return cookie;
+ }
+ }
+ return null;
+ }
+
+ public void addProperty(String key, Element value) {
+ Assert.notNull(key, "Property key must not be null");
+ Element[] oldArr = this.xmlProperties.get(key);
+ if (oldArr != null) {
+ Element[] newArr = new Element[oldArr.length + 1];
+ System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
+ newArr[oldArr.length] = value;
+ this.xmlProperties.put(key, newArr);
+ }
+ else {
+ this.xmlProperties.put(key, new Element[] {value});
+ }
+ }
+
+
+ public Set<String> getXmlPropertyNames() {
+ return Collections.unmodifiableSet(this.xmlProperties.keySet());
+ }
+
+ public Element getXmlProperty(String key) {
+ Assert.notNull(key, "Property key must not be null");
+ Element[] arr = this.xmlProperties.get(key);
+ return (arr != null && arr.length > 0 ? arr[0] : null);
+ }
+
+ public Element[] getXmlProperties(String key) {
+ Assert.notNull(key, "Property key must not be null");
+ return this.xmlProperties.get(key);
+ }
+
+ public Element createElement(String tagName) throws DOMException {
+ if (this.xmlDocument == null) {
+ try {
+ this.xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ }
+ catch (ParserConfigurationException ex) {
+ throw new DOMException(DOMException.INVALID_STATE_ERR, ex.toString());
+ }
+ }
+ return this.xmlDocument.createElement(tagName);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletSession.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletSession.java
new file mode 100644
index 00000000..5eaec799
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletSession.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.mock.web.portlet;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletSession;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+import org.springframework.mock.web.MockHttpSession;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletSession} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletSession implements PortletSession {
+
+ private static int nextId = 1;
+
+
+ private final String id = Integer.toString(nextId++);
+
+ private final long creationTime = System.currentTimeMillis();
+
+ private int maxInactiveInterval;
+
+ private long lastAccessedTime = System.currentTimeMillis();
+
+ private final PortletContext portletContext;
+
+ private final Map<String, Object> portletAttributes = new LinkedHashMap<String, Object>();
+
+ private final Map<String, Object> applicationAttributes = new LinkedHashMap<String, Object>();
+
+ private boolean invalid = false;
+
+ private boolean isNew = true;
+
+
+ /**
+ * Create a new MockPortletSession with a default {@link MockPortletContext}.
+ * @see MockPortletContext
+ */
+ public MockPortletSession() {
+ this(null);
+ }
+
+ /**
+ * Create a new MockPortletSession.
+ * @param portletContext the PortletContext that the session runs in
+ */
+ public MockPortletSession(PortletContext portletContext) {
+ this.portletContext = (portletContext != null ? portletContext : new MockPortletContext());
+ }
+
+
+ public Object getAttribute(String name) {
+ return this.portletAttributes.get(name);
+ }
+
+ public Object getAttribute(String name, int scope) {
+ if (scope == PortletSession.PORTLET_SCOPE) {
+ return this.portletAttributes.get(name);
+ }
+ else if (scope == PortletSession.APPLICATION_SCOPE) {
+ return this.applicationAttributes.get(name);
+ }
+ return null;
+ }
+
+ public Enumeration<String> getAttributeNames() {
+ return Collections.enumeration(new LinkedHashSet<String>(this.portletAttributes.keySet()));
+ }
+
+ public Enumeration<String> getAttributeNames(int scope) {
+ if (scope == PortletSession.PORTLET_SCOPE) {
+ return Collections.enumeration(new LinkedHashSet<String>(this.portletAttributes.keySet()));
+ }
+ else if (scope == PortletSession.APPLICATION_SCOPE) {
+ return Collections.enumeration(new LinkedHashSet<String>(this.applicationAttributes.keySet()));
+ }
+ return null;
+ }
+
+ public long getCreationTime() {
+ return this.creationTime;
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ public void access() {
+ this.lastAccessedTime = System.currentTimeMillis();
+ setNew(false);
+ }
+
+ public long getLastAccessedTime() {
+ return this.lastAccessedTime;
+ }
+
+ public int getMaxInactiveInterval() {
+ return this.maxInactiveInterval;
+ }
+
+ /**
+ * Clear all of this session's attributes.
+ */
+ public void clearAttributes() {
+ doClearAttributes(this.portletAttributes);
+ doClearAttributes(this.applicationAttributes);
+ }
+
+ protected void doClearAttributes(Map<String, Object> attributes) {
+ for (Iterator<Map.Entry<String, Object>> it = attributes.entrySet().iterator(); it.hasNext();) {
+ Map.Entry<String, Object> entry = it.next();
+ String name = entry.getKey();
+ Object value = entry.getValue();
+ it.remove();
+ if (value instanceof HttpSessionBindingListener) {
+ ((HttpSessionBindingListener) value).valueUnbound(
+ new HttpSessionBindingEvent(new MockHttpSession(), name, value));
+ }
+ }
+ }
+
+ public void invalidate() {
+ this.invalid = true;
+ clearAttributes();
+ }
+
+ public boolean isInvalid() {
+ return this.invalid;
+ }
+
+ public void setNew(boolean value) {
+ this.isNew = value;
+ }
+
+ public boolean isNew() {
+ return this.isNew;
+ }
+
+ public void removeAttribute(String name) {
+ this.portletAttributes.remove(name);
+ }
+
+ public void removeAttribute(String name, int scope) {
+ if (scope == PortletSession.PORTLET_SCOPE) {
+ this.portletAttributes.remove(name);
+ }
+ else if (scope == PortletSession.APPLICATION_SCOPE) {
+ this.applicationAttributes.remove(name);
+ }
+ }
+
+ public void setAttribute(String name, Object value) {
+ if (value != null) {
+ this.portletAttributes.put(name, value);
+ }
+ else {
+ this.portletAttributes.remove(name);
+ }
+ }
+
+ public void setAttribute(String name, Object value, int scope) {
+ if (scope == PortletSession.PORTLET_SCOPE) {
+ if (value != null) {
+ this.portletAttributes.put(name, value);
+ }
+ else {
+ this.portletAttributes.remove(name);
+ }
+ }
+ else if (scope == PortletSession.APPLICATION_SCOPE) {
+ if (value != null) {
+ this.applicationAttributes.put(name, value);
+ }
+ else {
+ this.applicationAttributes.remove(name);
+ }
+ }
+ }
+
+ public void setMaxInactiveInterval(int interval) {
+ this.maxInactiveInterval = interval;
+ }
+
+ public PortletContext getPortletContext() {
+ return this.portletContext;
+ }
+
+ public Map<String, Object> getAttributeMap() {
+ return Collections.unmodifiableMap(this.portletAttributes);
+ }
+
+ public Map<String, Object> getAttributeMap(int scope) {
+ if (scope == PortletSession.PORTLET_SCOPE) {
+ return Collections.unmodifiableMap(this.portletAttributes);
+ }
+ else if (scope == PortletSession.APPLICATION_SCOPE) {
+ return Collections.unmodifiableMap(this.applicationAttributes);
+ }
+ else {
+ return Collections.emptyMap();
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletURL.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletURL.java
new file mode 100644
index 00000000..12abdf57
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockPortletURL.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.util.Map;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletModeException;
+import javax.portlet.PortletURL;
+import javax.portlet.WindowState;
+import javax.portlet.WindowStateException;
+
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletURL} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockPortletURL extends MockBaseURL implements PortletURL {
+
+ public static final String URL_TYPE_RENDER = "render";
+
+ public static final String URL_TYPE_ACTION = "action";
+
+
+ private final PortalContext portalContext;
+
+ private final String urlType;
+
+ private WindowState windowState;
+
+ private PortletMode portletMode;
+
+
+ /**
+ * Create a new MockPortletURL for the given URL type.
+ * @param portalContext the PortalContext defining the supported
+ * PortletModes and WindowStates
+ * @param urlType the URL type, for example "render" or "action"
+ * @see #URL_TYPE_RENDER
+ * @see #URL_TYPE_ACTION
+ */
+ public MockPortletURL(PortalContext portalContext, String urlType) {
+ Assert.notNull(portalContext, "PortalContext is required");
+ this.portalContext = portalContext;
+ this.urlType = urlType;
+ }
+
+
+ //---------------------------------------------------------------------
+ // PortletURL methods
+ //---------------------------------------------------------------------
+
+ public void setWindowState(WindowState windowState) throws WindowStateException {
+ if (!CollectionUtils.contains(this.portalContext.getSupportedWindowStates(), windowState)) {
+ throw new WindowStateException("WindowState not supported", windowState);
+ }
+ this.windowState = windowState;
+ }
+
+ public WindowState getWindowState() {
+ return this.windowState;
+ }
+
+ public void setPortletMode(PortletMode portletMode) throws PortletModeException {
+ if (!CollectionUtils.contains(this.portalContext.getSupportedPortletModes(), portletMode)) {
+ throw new PortletModeException("PortletMode not supported", portletMode);
+ }
+ this.portletMode = portletMode;
+ }
+
+ public PortletMode getPortletMode() {
+ return this.portletMode;
+ }
+
+ public void removePublicRenderParameter(String name) {
+ this.parameters.remove(name);
+ }
+
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(encodeParameter("urlType", this.urlType));
+ if (this.windowState != null) {
+ sb.append(";").append(encodeParameter("windowState", this.windowState.toString()));
+ }
+ if (this.portletMode != null) {
+ sb.append(";").append(encodeParameter("portletMode", this.portletMode.toString()));
+ }
+ for (Map.Entry<String, String[]> entry : this.parameters.entrySet()) {
+ sb.append(";").append(encodeParameter("param_" + entry.getKey(), entry.getValue()));
+ }
+ return (isSecure() ? "https:" : "http:") +
+ "//localhost/mockportlet?" + sb.toString();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockRenderRequest.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockRenderRequest.java
new file mode 100644
index 00000000..d86ba7f2
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockRenderRequest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletMode;
+import javax.portlet.RenderRequest;
+import javax.portlet.WindowState;
+
+/**
+ * Mock implementation of the {@link javax.portlet.RenderRequest} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockRenderRequest extends MockPortletRequest implements RenderRequest {
+
+ /**
+ * Create a new MockRenderRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @see MockPortalContext
+ * @see MockPortletContext
+ */
+ public MockRenderRequest() {
+ super();
+ }
+
+ /**
+ * Create a new MockRenderRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @param portletMode the mode that the portlet runs in
+ */
+ public MockRenderRequest(PortletMode portletMode) {
+ super();
+ setPortletMode(portletMode);
+ }
+
+ /**
+ * Create a new MockRenderRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @param portletMode the mode that the portlet runs in
+ * @param windowState the window state to run the portlet in
+ */
+ public MockRenderRequest(PortletMode portletMode, WindowState windowState) {
+ super();
+ setPortletMode(portletMode);
+ setWindowState(windowState);
+ }
+
+ /**
+ * Create a new MockRenderRequest with a default {@link MockPortalContext}.
+ * @param portletContext the PortletContext that the request runs in
+ */
+ public MockRenderRequest(PortletContext portletContext) {
+ super(portletContext);
+ }
+
+ /**
+ * Create a new MockRenderRequest.
+ * @param portalContext the PortletContext that the request runs in
+ * @param portletContext the PortletContext that the request runs in
+ */
+ public MockRenderRequest(PortalContext portalContext, PortletContext portletContext) {
+ super(portalContext, portletContext);
+ }
+
+
+ @Override
+ protected String getLifecyclePhase() {
+ return RENDER_PHASE;
+ }
+
+ public String getETag() {
+ return getProperty(RenderRequest.ETAG);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockRenderResponse.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockRenderResponse.java
new file mode 100644
index 00000000..a2feeb37
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockRenderResponse.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.util.Collection;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+
+/**
+ * Mock implementation of the {@link javax.portlet.RenderResponse} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockRenderResponse extends MockMimeResponse implements RenderResponse {
+
+ private String title;
+
+ private Collection<PortletMode> nextPossiblePortletModes;
+
+
+ /**
+ * Create a new MockRenderResponse with a default {@link MockPortalContext}.
+ * @see MockPortalContext
+ */
+ public MockRenderResponse() {
+ super();
+ }
+
+ /**
+ * Create a new MockRenderResponse.
+ * @param portalContext the PortalContext defining the supported
+ * PortletModes and WindowStates
+ */
+ public MockRenderResponse(PortalContext portalContext) {
+ super(portalContext);
+ }
+
+ /**
+ * Create a new MockRenderResponse.
+ * @param portalContext the PortalContext defining the supported
+ * PortletModes and WindowStates
+ * @param request the corresponding render request that this response
+ * is generated for
+ */
+ public MockRenderResponse(PortalContext portalContext, RenderRequest request) {
+ super(portalContext, request);
+ }
+
+
+ //---------------------------------------------------------------------
+ // RenderResponse methods
+ //---------------------------------------------------------------------
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getTitle() {
+ return this.title;
+ }
+
+ public void setNextPossiblePortletModes(Collection<PortletMode> portletModes) {
+ this.nextPossiblePortletModes = portletModes;
+ }
+
+ public Collection<PortletMode> getNextPossiblePortletModes() {
+ return this.nextPossiblePortletModes;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceRequest.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceRequest.java
new file mode 100644
index 00000000..7cce4ba3
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceRequest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+import javax.portlet.RenderRequest;
+import javax.portlet.ResourceRequest;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ResourceRequest} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockResourceRequest extends MockClientDataRequest implements ResourceRequest {
+
+ private String resourceID;
+
+ private String cacheability;
+
+ private final Map<String, String[]> privateRenderParameterMap = new LinkedHashMap<String, String[]>();
+
+
+ /**
+ * Create a new MockResourceRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @see org.springframework.mock.web.portlet.MockPortalContext
+ * @see org.springframework.mock.web.portlet.MockPortletContext
+ */
+ public MockResourceRequest() {
+ super();
+ }
+
+ /**
+ * Create a new MockResourceRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @param resourceID the resource id for this request
+ */
+ public MockResourceRequest(String resourceID) {
+ super();
+ this.resourceID = resourceID;
+ }
+
+ /**
+ * Create a new MockResourceRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @param url the resource URL for this request
+ */
+ public MockResourceRequest(MockResourceURL url) {
+ super();
+ this.resourceID = url.getResourceID();
+ this.cacheability = url.getCacheability();
+ }
+
+ /**
+ * Create a new MockResourceRequest with a default {@link MockPortalContext}.
+ * @param portletContext the PortletContext that the request runs in
+ */
+ public MockResourceRequest(PortletContext portletContext) {
+ super(portletContext);
+ }
+
+ /**
+ * Create a new MockResourceRequest.
+ * @param portalContext the PortalContext that the request runs in
+ * @param portletContext the PortletContext that the request runs in
+ */
+ public MockResourceRequest(PortalContext portalContext, PortletContext portletContext) {
+ super(portalContext, portletContext);
+ }
+
+
+ @Override
+ protected String getLifecyclePhase() {
+ return RESOURCE_PHASE;
+ }
+
+ public void setResourceID(String resourceID) {
+ this.resourceID = resourceID;
+ }
+
+ public String getResourceID() {
+ return this.resourceID;
+ }
+
+ public void setCacheability(String cacheLevel) {
+ this.cacheability = cacheLevel;
+ }
+
+ public String getCacheability() {
+ return this.cacheability;
+ }
+
+ public String getETag() {
+ return getProperty(RenderRequest.ETAG);
+ }
+
+ public void addPrivateRenderParameter(String key, String value) {
+ this.privateRenderParameterMap.put(key, new String[] {value});
+ }
+
+ public void addPrivateRenderParameter(String key, String[] values) {
+ this.privateRenderParameterMap.put(key, values);
+ }
+
+ public Map<String, String[]> getPrivateRenderParameterMap() {
+ return Collections.unmodifiableMap(this.privateRenderParameterMap);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceResponse.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceResponse.java
new file mode 100644
index 00000000..297a1968
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceResponse.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import javax.portlet.ResourceResponse;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ResourceResponse} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockResourceResponse extends MockMimeResponse implements ResourceResponse {
+
+ private int contentLength = 0;
+
+
+ public void setContentLength(int len) {
+ this.contentLength = len;
+ }
+
+ public int getContentLength() {
+ return this.contentLength;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceURL.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceURL.java
new file mode 100644
index 00000000..cce36b18
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockResourceURL.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.util.Map;
+import javax.portlet.ResourceURL;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ResourceURL} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockResourceURL extends MockBaseURL implements ResourceURL {
+
+ private String resourceID;
+
+ private String cacheability;
+
+
+ //---------------------------------------------------------------------
+ // ResourceURL methods
+ //---------------------------------------------------------------------
+
+ public void setResourceID(String resourceID) {
+ this.resourceID = resourceID;
+ }
+
+ public String getResourceID() {
+ return this.resourceID;
+ }
+
+ public void setCacheability(String cacheLevel) {
+ this.cacheability = cacheLevel;
+ }
+
+ public String getCacheability() {
+ return this.cacheability;
+ }
+
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(encodeParameter("resourceID", this.resourceID));
+ if (this.cacheability != null) {
+ sb.append(";").append(encodeParameter("cacheability", this.cacheability));
+ }
+ for (Map.Entry<String, String[]> entry : this.parameters.entrySet()) {
+ sb.append(";").append(encodeParameter("param_" + entry.getKey(), entry.getValue()));
+ }
+ return (isSecure() ? "https:" : "http:") +
+ "//localhost/mockportlet?" + sb.toString();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/MockStateAwareResponse.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockStateAwareResponse.java
new file mode 100644
index 00000000..95df6ed0
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/MockStateAwareResponse.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletModeException;
+import javax.portlet.StateAwareResponse;
+import javax.portlet.WindowState;
+import javax.portlet.WindowStateException;
+import javax.xml.namespace.QName;
+
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.StateAwareResponse} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class MockStateAwareResponse extends MockPortletResponse implements StateAwareResponse {
+
+ private WindowState windowState;
+
+ private PortletMode portletMode;
+
+ private final Map<String, String[]> renderParameters = new LinkedHashMap<String, String[]>();
+
+ private final Map<QName, Serializable> events = new HashMap<QName, Serializable>();
+
+
+ /**
+ * Create a new MockActionResponse with a default {@link MockPortalContext}.
+ * @see org.springframework.mock.web.portlet.MockPortalContext
+ */
+ public MockStateAwareResponse() {
+ super();
+ }
+
+ /**
+ * Create a new MockActionResponse.
+ * @param portalContext the PortalContext defining the supported
+ * PortletModes and WindowStates
+ */
+ public MockStateAwareResponse(PortalContext portalContext) {
+ super(portalContext);
+ }
+
+
+ public void setWindowState(WindowState windowState) throws WindowStateException {
+ if (!CollectionUtils.contains(getPortalContext().getSupportedWindowStates(), windowState)) {
+ throw new WindowStateException("WindowState not supported", windowState);
+ }
+ this.windowState = windowState;
+ }
+
+ public WindowState getWindowState() {
+ return this.windowState;
+ }
+
+ public void setPortletMode(PortletMode portletMode) throws PortletModeException {
+ if (!CollectionUtils.contains(getPortalContext().getSupportedPortletModes(), portletMode)) {
+ throw new PortletModeException("PortletMode not supported", portletMode);
+ }
+ this.portletMode = portletMode;
+ }
+
+ public PortletMode getPortletMode() {
+ return this.portletMode;
+ }
+
+ public void setRenderParameters(Map<String, String[]> parameters) {
+ Assert.notNull(parameters, "Parameters Map must not be null");
+ this.renderParameters.clear();
+ this.renderParameters.putAll(parameters);
+ }
+
+ public void setRenderParameter(String key, String value) {
+ Assert.notNull(key, "Parameter key must not be null");
+ Assert.notNull(value, "Parameter value must not be null");
+ this.renderParameters.put(key, new String[] {value});
+ }
+
+ public void setRenderParameter(String key, String[] values) {
+ Assert.notNull(key, "Parameter key must not be null");
+ Assert.notNull(values, "Parameter values must not be null");
+ this.renderParameters.put(key, values);
+ }
+
+ public String getRenderParameter(String key) {
+ Assert.notNull(key, "Parameter key must not be null");
+ String[] arr = this.renderParameters.get(key);
+ return (arr != null && arr.length > 0 ? arr[0] : null);
+ }
+
+ public String[] getRenderParameterValues(String key) {
+ Assert.notNull(key, "Parameter key must not be null");
+ return this.renderParameters.get(key);
+ }
+
+ public Iterator<String> getRenderParameterNames() {
+ return this.renderParameters.keySet().iterator();
+ }
+
+ public Map<String, String[]> getRenderParameterMap() {
+ return Collections.unmodifiableMap(this.renderParameters);
+ }
+
+ public void removePublicRenderParameter(String name) {
+ this.renderParameters.remove(name);
+ }
+
+ public void setEvent(QName name, Serializable value) {
+ this.events.put(name, value);
+ }
+
+ public void setEvent(String name, Serializable value) {
+ this.events.put(new QName(name), value);
+ }
+
+ public Iterator<QName> getEventNames() {
+ return this.events.keySet().iterator();
+ }
+
+ public Serializable getEvent(QName name) {
+ return this.events.get(name);
+ }
+
+ public Serializable getEvent(String name) {
+ return this.events.get(new QName(name));
+ }
+
+}
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
new file mode 100644
index 00000000..8a88066c
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2002-2009 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.mock.web.portlet;
+
+import java.io.InputStream;
+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;
+import javax.servlet.ServletContext;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.portlet.PortletContext} interface,
+ * wrapping an underlying {@link javax.servlet.ServletContext}.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see MockPortletContext
+ */
+public class ServletWrappingPortletContext implements PortletContext {
+
+ private final ServletContext servletContext;
+
+
+ /**
+ * Create a new PortletContext wrapping the given ServletContext.
+ * @param servletContext the ServletContext to wrap
+ */
+ public ServletWrappingPortletContext(ServletContext servletContext) {
+ Assert.notNull(servletContext, "ServletContext must not be null");
+ this.servletContext = servletContext;
+ }
+
+ /**
+ * Return the underlying ServletContext that this PortletContext wraps.
+ */
+ public final ServletContext getServletContext() {
+ return this.servletContext;
+ }
+
+
+ public String getServerInfo() {
+ return this.servletContext.getServerInfo();
+ }
+
+ public PortletRequestDispatcher getRequestDispatcher(String path) {
+ return null;
+ }
+
+ public PortletRequestDispatcher getNamedDispatcher(String name) {
+ return null;
+ }
+
+ public InputStream getResourceAsStream(String path) {
+ return this.servletContext.getResourceAsStream(path);
+ }
+
+ public int getMajorVersion() {
+ return 2;
+ }
+
+ public int getMinorVersion() {
+ return 0;
+ }
+
+ public String getMimeType(String file) {
+ return this.servletContext.getMimeType(file);
+ }
+
+ public String getRealPath(String path) {
+ return this.servletContext.getRealPath(path);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set<String> getResourcePaths(String path) {
+ return this.servletContext.getResourcePaths(path);
+ }
+
+ public URL getResource(String path) throws MalformedURLException {
+ return this.servletContext.getResource(path);
+ }
+
+ public Object getAttribute(String name) {
+ return this.servletContext.getAttribute(name);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Enumeration<String> getAttributeNames() {
+ return this.servletContext.getAttributeNames();
+ }
+
+ public String getInitParameter(String name) {
+ return this.servletContext.getInitParameter(name);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Enumeration<String> getInitParameterNames() {
+ return this.servletContext.getInitParameterNames();
+ }
+
+ public void log(String msg) {
+ this.servletContext.log(msg);
+ }
+
+ public void log(String message, Throwable throwable) {
+ this.servletContext.log(message, throwable);
+ }
+
+ public void removeAttribute(String name) {
+ this.servletContext.removeAttribute(name);
+ }
+
+ public void setAttribute(String name, Object object) {
+ this.servletContext.setAttribute(name, object);
+ }
+
+ public String getPortletContextName() {
+ return this.servletContext.getServletContextName();
+ }
+
+ public Enumeration<String> getContainerRuntimeOptions() {
+ return Collections.enumeration(new HashSet<String>());
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/portlet/package-info.java b/spring-test/src/main/java/org/springframework/mock/web/portlet/package-info.java
new file mode 100644
index 00000000..3c28fee4
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/portlet/package-info.java
@@ -0,0 +1,14 @@
+
+/**
+ *
+ * A comprehensive set of Portlet API 2.0 mock objects,
+ * targeted at usage with Spring's web MVC framework.
+ * Useful for testing web contexts and controllers.
+ *
+ * <p>More convenient to use than dynamic mock objects
+ * (<a href="http://www.easymock.org">EasyMock</a>) or
+ * existing Portlet API mock objects.
+ *
+ */
+package org.springframework.mock.web.portlet;
+
diff --git a/spring-test/src/main/java/org/springframework/test/AbstractDependencyInjectionSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/AbstractDependencyInjectionSpringContextTests.java
new file mode 100644
index 00000000..6229a8d3
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/AbstractDependencyInjectionSpringContextTests.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.util.Assert;
+
+/**
+ * <p>
+ * Convenient superclass for JUnit 3.8 based tests depending on a Spring
+ * context. The test instance itself is populated by Dependency Injection.
+ * </p>
+ * <p>
+ * Really for integration testing, not unit testing. You should <i>not</i>
+ * normally use the Spring container for unit tests: simply populate your POJOs
+ * in plain JUnit tests!
+ * </p>
+ * <p>
+ * This supports two modes of populating the test:
+ * </p>
+ * <ul>
+ * <li>Via Setter Dependency Injection. Simply express dependencies on objects
+ * in the test fixture, and they will be satisfied by autowiring by type.
+ * <li>Via Field Injection. Declare protected variables of the required type
+ * which match named beans in the context. This is autowire by name, rather than
+ * type. This approach is based on an approach originated by Ara Abrahmian.
+ * Setter Dependency Injection is the default: set the
+ * {@code populateProtectedVariables} property to {@code true} in
+ * the constructor to switch on Field Injection.
+ * </ul>
+ *
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Rick Evans
+ * @author Sam Brannen
+ * @since 1.1.1
+ * @see #setDirty
+ * @see #contextKey
+ * @see #getContext
+ * @see #getConfigLocations
+ * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
+ * ({@link org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests})
+ */
+@Deprecated
+@SuppressWarnings({ "unchecked", "rawtypes" })
+public abstract class AbstractDependencyInjectionSpringContextTests extends AbstractSingleSpringContextTests {
+
+ /**
+ * Constant that indicates no autowiring at all.
+ *
+ * @see #setAutowireMode
+ */
+ public static final int AUTOWIRE_NO = 0;
+
+ /**
+ * Constant that indicates autowiring bean properties by name.
+ *
+ * @see #setAutowireMode
+ */
+ public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
+
+ /**
+ * Constant that indicates autowiring bean properties by type.
+ *
+ * @see #setAutowireMode
+ */
+ public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
+
+ private boolean populateProtectedVariables = false;
+
+ private int autowireMode = AUTOWIRE_BY_TYPE;
+
+ private boolean dependencyCheck = true;
+
+ private String[] managedVariableNames;
+
+
+ /**
+ * Default constructor for AbstractDependencyInjectionSpringContextTests.
+ */
+ public AbstractDependencyInjectionSpringContextTests() {
+ }
+
+ /**
+ * Constructor for AbstractDependencyInjectionSpringContextTests with a
+ * JUnit name.
+ * @param name the name of this text fixture
+ */
+ public AbstractDependencyInjectionSpringContextTests(String name) {
+ super(name);
+ }
+
+
+ /**
+ * Set whether to populate protected variables of this test case. Default is
+ * {@code false}.
+ */
+ public final void setPopulateProtectedVariables(boolean populateFields) {
+ this.populateProtectedVariables = populateFields;
+ }
+
+ /**
+ * Return whether to populate protected variables of this test case.
+ */
+ public final boolean isPopulateProtectedVariables() {
+ return this.populateProtectedVariables;
+ }
+
+ /**
+ * Set the autowire mode for test properties set by Dependency Injection.
+ * <p>The default is {@link #AUTOWIRE_BY_TYPE}. Can be set to
+ * {@link #AUTOWIRE_BY_NAME} or {@link #AUTOWIRE_NO} instead.
+ * @see #AUTOWIRE_BY_TYPE
+ * @see #AUTOWIRE_BY_NAME
+ * @see #AUTOWIRE_NO
+ */
+ public final void setAutowireMode(final int autowireMode) {
+ this.autowireMode = autowireMode;
+ }
+
+ /**
+ * Return the autowire mode for test properties set by Dependency Injection.
+ */
+ public final int getAutowireMode() {
+ return this.autowireMode;
+ }
+
+ /**
+ * Set whether or not dependency checking should be performed for test
+ * properties set by Dependency Injection.
+ * <p>The default is {@code true}, meaning that tests cannot be run
+ * unless all properties are populated.
+ */
+ public final void setDependencyCheck(final boolean dependencyCheck) {
+ this.dependencyCheck = dependencyCheck;
+ }
+
+ /**
+ * Return whether or not dependency checking should be performed for test
+ * properties set by Dependency Injection.
+ */
+ public final boolean isDependencyCheck() {
+ return this.dependencyCheck;
+ }
+
+ /**
+ * Prepare this test instance, injecting dependencies into its protected
+ * fields and its bean properties.
+ * <p>Note: if the {@link ApplicationContext} for this test instance has not
+ * been configured (e.g., is {@code null}), dependency injection
+ * will naturally <strong>not</strong> be performed, but an informational
+ * message will be written to the log.
+ * @see #injectDependencies()
+ */
+ protected void prepareTestInstance() throws Exception {
+ if (getApplicationContext() == null) {
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info("ApplicationContext has not been configured for test [" + getClass().getName()
+ + "]: dependency injection will NOT be performed.");
+ }
+ }
+ else {
+ injectDependencies();
+ }
+ }
+
+ /**
+ * Inject dependencies into 'this' instance (that is, this test instance).
+ * <p>The default implementation populates protected variables if the
+ * {@link #populateProtectedVariables() appropriate flag is set}, else uses
+ * autowiring if autowiring is switched on (which it is by default).
+ * <p>Override this method if you need full control over how dependencies are
+ * injected into the test instance.
+ * @throws Exception in case of dependency injection failure
+ * @throws IllegalStateException if the {@link ApplicationContext} for this
+ * test instance has not been configured
+ * @see #populateProtectedVariables()
+ */
+ @SuppressWarnings("javadoc")
+ protected void injectDependencies() throws Exception {
+ Assert.state(getApplicationContext() != null,
+ "injectDependencies() called without first configuring an ApplicationContext");
+ if (isPopulateProtectedVariables()) {
+ if (this.managedVariableNames == null) {
+ initManagedVariableNames();
+ }
+ populateProtectedVariables();
+ }
+ getApplicationContext().getBeanFactory().autowireBeanProperties(this, getAutowireMode(), isDependencyCheck());
+ }
+
+ private void initManagedVariableNames() throws IllegalAccessException {
+ List managedVarNames = new LinkedList();
+ Class clazz = getClass();
+ do {
+ Field[] fields = clazz.getDeclaredFields();
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Found " + fields.length + " fields on " + clazz);
+ }
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ field.setAccessible(true);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Candidate field: " + field);
+ }
+ if (isProtectedInstanceField(field)) {
+ Object oldValue = field.get(this);
+ if (oldValue == null) {
+ managedVarNames.add(field.getName());
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Added managed variable '" + field.getName() + "'");
+ }
+ }
+ else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Rejected managed variable '" + field.getName() + "'");
+ }
+ }
+ }
+ }
+ clazz = clazz.getSuperclass();
+ } while (!clazz.equals(AbstractDependencyInjectionSpringContextTests.class));
+
+ this.managedVariableNames = (String[]) managedVarNames.toArray(new String[managedVarNames.size()]);
+ }
+
+ private boolean isProtectedInstanceField(Field field) {
+ int modifiers = field.getModifiers();
+ return !Modifier.isStatic(modifiers) && Modifier.isProtected(modifiers);
+ }
+
+ private void populateProtectedVariables() throws IllegalAccessException {
+ for (int i = 0; i < this.managedVariableNames.length; i++) {
+ String varName = this.managedVariableNames[i];
+ Object bean = null;
+ try {
+ Field field = findField(getClass(), varName);
+ bean = getApplicationContext().getBean(varName, field.getType());
+ field.setAccessible(true);
+ field.set(this, bean);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Populated field: " + field);
+ }
+ }
+ catch (NoSuchFieldException ex) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("No field with name '" + varName + "'");
+ }
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ if (this.logger.isWarnEnabled()) {
+ this.logger.warn("No bean with name '" + varName + "'");
+ }
+ }
+ }
+ }
+
+ private Field findField(Class clazz, String name) throws NoSuchFieldException {
+ try {
+ return clazz.getDeclaredField(name);
+ }
+ catch (NoSuchFieldException ex) {
+ Class superclass = clazz.getSuperclass();
+ if (superclass != AbstractSpringContextTests.class) {
+ return findField(superclass, name);
+ }
+ else {
+ throw ex;
+ }
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/AbstractSingleSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/AbstractSingleSpringContextTests.java
new file mode 100644
index 00000000..0d5d0784
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/AbstractSingleSpringContextTests.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright 2002-2012 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;
+
+import org.springframework.beans.factory.support.BeanDefinitionReader;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ResourceUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * <p>
+ * Abstract JUnit 3.8 test class that holds and exposes a single Spring
+ * {@link org.springframework.context.ApplicationContext ApplicationContext}.
+ * </p>
+ * <p>
+ * This class will cache contexts based on a <i>context key</i>: normally the
+ * config locations String array describing the Spring resource descriptors
+ * making up the context. Unless the {@link #setDirty()} method is called by a
+ * test, the context will not be reloaded, even across different subclasses of
+ * this test. This is particularly beneficial if your context is slow to
+ * construct, for example if you are using Hibernate and the time taken to load
+ * the mappings is an issue.
+ * </p>
+ * <p>
+ * For such standard usage, simply override the {@link #getConfigLocations()}
+ * method and provide the desired config files. For alternative configuration
+ * options, see {@link #getConfigPath()} and {@link #getConfigPaths()}.
+ * </p>
+ * <p>
+ * If you don't want to load a standard context from an array of config
+ * locations, you can override the {@link #contextKey()} method. In conjunction
+ * with this you typically need to override the {@link #loadContext(Object)}
+ * method, which by default loads the locations specified in the
+ * {@link #getConfigLocations()} method.
+ * </p>
+ * <p>
+ * <b>WARNING:</b> When doing integration tests from within Eclipse, only use
+ * classpath resource URLs. Else, you may see misleading failures when changing
+ * context locations.
+ * </p>
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @since 2.0
+ * @see #getConfigLocations()
+ * @see #contextKey()
+ * @see #loadContext(Object)
+ * @see #getApplicationContext()
+ * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
+ * ({@link org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests})
+ */
+@Deprecated
+public abstract class AbstractSingleSpringContextTests extends AbstractSpringContextTests {
+
+ /** Application context this test will run against */
+ protected ConfigurableApplicationContext applicationContext;
+
+ private int loadCount = 0;
+
+
+ /**
+ * Default constructor for AbstractSingleSpringContextTests.
+ */
+ public AbstractSingleSpringContextTests() {
+ }
+
+ /**
+ * Constructor for AbstractSingleSpringContextTests with a JUnit name.
+ * @param name the name of this text fixture
+ */
+ public AbstractSingleSpringContextTests(String name) {
+ super(name);
+ }
+
+ /**
+ * This implementation is final. Override {@code onSetUp} for custom behavior.
+ * @see #onSetUp()
+ */
+ protected final void setUp() throws Exception {
+ // lazy load, in case getApplicationContext() has not yet been called.
+ if (this.applicationContext == null) {
+ this.applicationContext = getContext(contextKey());
+ }
+ prepareTestInstance();
+ onSetUp();
+ }
+
+ /**
+ * Prepare this test instance, for example populating its fields.
+ * The context has already been loaded at the time of this callback.
+ * <p>The default implementation does nothing.
+ * @throws Exception in case of preparation failure
+ */
+ protected void prepareTestInstance() throws Exception {
+ }
+
+ /**
+ * Subclasses can override this method in place of the {@code setUp()}
+ * method, which is final in this class.
+ * <p>The default implementation does nothing.
+ * @throws Exception simply let any exception propagate
+ */
+ protected void onSetUp() throws Exception {
+ }
+
+ /**
+ * Called to say that the "applicationContext" instance variable is dirty
+ * and should be reloaded. We need to do this if a test has modified the
+ * context (for example, by replacing a bean definition).
+ */
+ protected void setDirty() {
+ setDirty(contextKey());
+ }
+
+ /**
+ * This implementation is final. Override {@code onTearDown} for
+ * custom behavior.
+ * @see #onTearDown()
+ */
+ protected final void tearDown() throws Exception {
+ onTearDown();
+ }
+
+ /**
+ * Subclasses can override this to add custom behavior on teardown.
+ * @throws Exception simply let any exception propagate
+ */
+ protected void onTearDown() throws Exception {
+ }
+
+ /**
+ * Return a key for this context. Default is the config location array as
+ * determined by {@link #getConfigLocations()}.
+ * <p>If you override this method, you will typically have to override
+ * {@link #loadContext(Object)} as well, being able to handle the key type
+ * that this method returns.
+ * @return the context key
+ * @see #getConfigLocations()
+ */
+ protected Object contextKey() {
+ return getConfigLocations();
+ }
+
+ /**
+ * This implementation assumes a key of type String array and loads a
+ * context from the given locations.
+ * <p>If you override {@link #contextKey()}, you will typically have to
+ * override this method as well, being able to handle the key type that
+ * {@code contextKey()} returns.
+ * @see #getConfigLocations()
+ */
+ protected ConfigurableApplicationContext loadContext(Object key) throws Exception {
+ return loadContextLocations((String[]) key);
+ }
+
+ /**
+ * Load a Spring ApplicationContext from the given config locations.
+ * <p>The default implementation creates a standard
+ * {@link #createApplicationContext GenericApplicationContext}, allowing
+ * for customizing the internal bean factory through
+ * {@link #customizeBeanFactory}.
+ * @param locations the config locations (as Spring resource locations,
+ * e.g. full classpath locations or any kind of URL)
+ * @return the corresponding ApplicationContext instance (potentially cached)
+ * @throws Exception if context loading failed
+ * @see #createApplicationContext(String[])
+ */
+ protected ConfigurableApplicationContext loadContextLocations(String[] locations) throws Exception {
+ ++this.loadCount;
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info("Loading context for locations: " + StringUtils.arrayToCommaDelimitedString(locations));
+ }
+ return createApplicationContext(locations);
+ }
+
+ /**
+ * Create a Spring {@link ConfigurableApplicationContext} for use by this test.
+ * <p>The default implementation creates a standard {@link GenericApplicationContext}
+ * instance, calls the {@link #prepareApplicationContext} prepareApplicationContext}
+ * method and the {@link #customizeBeanFactory customizeBeanFactory} method to allow
+ * for customizing the context and its DefaultListableBeanFactory, populates the
+ * context from the specified config {@code locations} through the configured
+ * {@link #createBeanDefinitionReader(GenericApplicationContext) BeanDefinitionReader},
+ * and finally {@link ConfigurableApplicationContext#refresh() refreshes} the context.
+ * @param locations the config locations (as Spring resource locations,
+ * e.g. full classpath locations or any kind of URL)
+ * @return the GenericApplicationContext instance
+ * @see #loadContextLocations(String[])
+ * @see #customizeBeanFactory(DefaultListableBeanFactory)
+ * @see #createBeanDefinitionReader(GenericApplicationContext)
+ */
+ protected ConfigurableApplicationContext createApplicationContext(String[] locations) {
+ GenericApplicationContext context = new GenericApplicationContext();
+ prepareApplicationContext(context);
+ customizeBeanFactory(context.getDefaultListableBeanFactory());
+ createBeanDefinitionReader(context).loadBeanDefinitions(locations);
+ context.refresh();
+ return context;
+ }
+
+ /**
+ * Prepare the GenericApplicationContext used by this test.
+ * Called before bean definitions are read.
+ * <p>The default implementation is empty. Can be overridden in subclasses to
+ * customize GenericApplicationContext's standard settings.
+ * @param context the context for which the BeanDefinitionReader should be created
+ * @see #createApplicationContext
+ * @see org.springframework.context.support.GenericApplicationContext#setResourceLoader
+ * @see org.springframework.context.support.GenericApplicationContext#setId
+ */
+ protected void prepareApplicationContext(GenericApplicationContext context) {
+ }
+
+ /**
+ * Customize the internal bean factory of the ApplicationContext used by
+ * this test. Called before bean definitions are read.
+ * <p>The default implementation is empty. Can be overridden in subclasses to
+ * customize DefaultListableBeanFactory's standard settings.
+ * @param beanFactory the newly created bean factory for this context
+ * @see #loadContextLocations
+ * @see #createApplicationContext
+ * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
+ * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowEagerClassLoading
+ * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences
+ * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping
+ */
+ protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
+ }
+
+ /**
+ * Factory method for creating new {@link BeanDefinitionReader}s for
+ * loading bean definitions into the supplied
+ * {@link GenericApplicationContext context}.
+ * <p>The default implementation creates a new {@link XmlBeanDefinitionReader}.
+ * Can be overridden in subclasses to provide a different
+ * BeanDefinitionReader implementation.
+ * @param context the context for which the BeanDefinitionReader should be created
+ * @return a BeanDefinitionReader for the supplied context
+ * @see #createApplicationContext(String[])
+ * @see BeanDefinitionReader
+ * @see XmlBeanDefinitionReader
+ */
+ protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) {
+ return new XmlBeanDefinitionReader(context);
+ }
+
+ /**
+ * Subclasses can override this method to return the locations of their
+ * config files, unless they override {@link #contextKey()} and
+ * {@link #loadContext(Object)} instead.
+ * <p>A plain path will be treated as class path location, e.g.:
+ * "org/springframework/whatever/foo.xml". Note however that you may prefix
+ * path locations with standard Spring resource prefixes. Therefore, a
+ * config location path prefixed with "classpath:" with behave the same as a
+ * plain path, but a config location such as
+ * "file:/some/path/path/location/appContext.xml" will be treated as a
+ * filesystem location.
+ * <p>The default implementation builds config locations for the config paths
+ * specified through {@link #getConfigPaths()}.
+ * @return an array of config locations
+ * @see #getConfigPaths()
+ * @see org.springframework.core.io.ResourceLoader#getResource(String)
+ */
+ protected String[] getConfigLocations() {
+ String[] paths = getConfigPaths();
+ String[] locations = new String[paths.length];
+ for (int i = 0; i < paths.length; i++) {
+ String path = paths[i];
+ if (path.startsWith("/")) {
+ locations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + path;
+ }
+ else {
+ locations[i] = ResourceUtils.CLASSPATH_URL_PREFIX +
+ StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(getClass()) + "/" + path);
+ }
+ }
+ return locations;
+ }
+
+ /**
+ * Subclasses can override this method to return paths to their config
+ * files, relative to the concrete test class.
+ * <p>A plain path, e.g. "context.xml", will be loaded as classpath resource
+ * from the same package that the concrete test class is defined in. A path
+ * starting with a slash is treated as fully qualified class path location,
+ * e.g.: "/org/springframework/whatever/foo.xml".
+ * <p>The default implementation builds an array for the config path specified
+ * through {@link #getConfigPath()}.
+ * @return an array of config locations
+ * @see #getConfigPath()
+ * @see java.lang.Class#getResource(String)
+ */
+ protected String[] getConfigPaths() {
+ String path = getConfigPath();
+ return (path != null ? new String[] { path } : new String[0]);
+ }
+
+ /**
+ * Subclasses can override this method to return a single path to a config
+ * file, relative to the concrete test class.
+ * <p>A plain path, e.g. "context.xml", will be loaded as classpath resource
+ * from the same package that the concrete test class is defined in. A path
+ * starting with a slash is treated as fully qualified class path location,
+ * e.g.: "/org/springframework/whatever/foo.xml".
+ * <p>The default implementation simply returns {@code null}.
+ * @return an array of config locations
+ * @see #getConfigPath()
+ * @see Class#getResource(String)
+ */
+ protected String getConfigPath() {
+ return null;
+ }
+
+ /**
+ * Return the ApplicationContext that this base class manages; may be
+ * {@code null}.
+ */
+ public final ConfigurableApplicationContext getApplicationContext() {
+ // lazy load, in case setUp() has not yet been called.
+ if (this.applicationContext == null) {
+ try {
+ this.applicationContext = getContext(contextKey());
+ }
+ catch (Exception e) {
+ // log and continue...
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Caught exception while retrieving the ApplicationContext for test [" +
+ getClass().getName() + "." + getName() + "].", e);
+ }
+ }
+ }
+
+ return this.applicationContext;
+ }
+
+ /**
+ * Return the current number of context load attempts.
+ */
+ public final int getLoadCount() {
+ return this.loadCount;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/AbstractSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/AbstractSpringContextTests.java
new file mode 100644
index 00000000..d01144ed
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/AbstractSpringContextTests.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * <p>
+ * Superclass for JUnit 3.8 test cases using Spring
+ * {@link org.springframework.context.ApplicationContext ApplicationContexts}.
+ * </p>
+ * <p>
+ * Maintains a static cache of contexts by key. This has significant performance
+ * benefit if initializing the context would take time. While initializing a
+ * Spring context itself is very quick, some beans in a context, such as a
+ * LocalSessionFactoryBean for working with Hibernate, may take some time to
+ * initialize. Hence it often makes sense to do that initializing once.
+ * </p>
+ * <p>
+ * Any ApplicationContext created by this class will be asked to register a JVM
+ * shutdown hook for itself. Unless the context gets closed early, all context
+ * instances will be automatically closed on JVM shutdown. This allows for
+ * freeing external resources held by beans within the context, e.g. temporary
+ * files.
+ * </p>
+ * <p>
+ * Normally you won't extend this class directly but rather one of its
+ * subclasses.
+ * </p>
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @since 1.1.1
+ * @see AbstractSingleSpringContextTests
+ * @see AbstractDependencyInjectionSpringContextTests
+ * @see AbstractTransactionalSpringContextTests
+ * @see AbstractTransactionalDataSourceSpringContextTests
+ * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
+ * ({@link org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests})
+ */
+@Deprecated
+public abstract class AbstractSpringContextTests extends ConditionalTestCase {
+
+ /**
+ * Map of context keys returned by subclasses of this class, to Spring
+ * contexts. This needs to be static, as JUnit tests are destroyed and
+ * recreated between running individual test methods.
+ */
+ private static Map<String, ConfigurableApplicationContext> contextKeyToContextMap =
+ new HashMap<String, ConfigurableApplicationContext>();
+
+
+ /**
+ * Default constructor for AbstractSpringContextTests.
+ */
+ public AbstractSpringContextTests() {
+ }
+
+ /**
+ * Constructor for AbstractSpringContextTests with a JUnit name.
+ */
+ public AbstractSpringContextTests(String name) {
+ super(name);
+ }
+
+
+ /**
+ * Explicitly add an ApplicationContext instance under a given key.
+ * <p>This is not meant to be used by subclasses. It is rather exposed for
+ * special test suite environments.
+ * @param key the context key
+ * @param context the ApplicationContext instance
+ */
+ public final void addContext(Object key, ConfigurableApplicationContext context) {
+ Assert.notNull(context, "ApplicationContext must not be null");
+ contextKeyToContextMap.put(contextKeyString(key), context);
+ }
+
+ /**
+ * Return whether there is a cached context for the given key.
+ * @param key the context key
+ */
+ protected final boolean hasCachedContext(Object key) {
+ return contextKeyToContextMap.containsKey(contextKeyString(key));
+ }
+
+ /**
+ * Determine if the supplied context {@code key} is <em>empty</em>.
+ * <p>By default, {@code null} values, empty strings, and zero-length
+ * arrays are considered <em>empty</em>.
+ * @param key the context key to check
+ * @return {@code true} if the supplied context key is empty
+ */
+ protected boolean isContextKeyEmpty(Object key) {
+ return (key == null) || ((key instanceof String) && !StringUtils.hasText((String) key)) ||
+ ((key instanceof Object[]) && ObjectUtils.isEmpty((Object[]) key));
+ }
+
+ /**
+ * Obtain an ApplicationContext for the given key, potentially cached.
+ * @param key the context key; may be {@code null}.
+ * @return the corresponding ApplicationContext instance (potentially cached),
+ * or {@code null} if the provided {@code key} is <em>empty</em>
+ */
+ protected final ConfigurableApplicationContext getContext(Object key) throws Exception {
+ if (isContextKeyEmpty(key)) {
+ return null;
+ }
+ String keyString = contextKeyString(key);
+ ConfigurableApplicationContext ctx = contextKeyToContextMap.get(keyString);
+ if (ctx == null) {
+ ctx = loadContext(key);
+ ctx.registerShutdownHook();
+ contextKeyToContextMap.put(keyString, ctx);
+ }
+ return ctx;
+ }
+
+ /**
+ * Mark the context with the given key as dirty. This will cause the cached
+ * context to be reloaded before the next test case is executed.
+ * <p>Call this method only if you change the state of a singleton bean,
+ * potentially affecting future tests.
+ */
+ protected final void setDirty(Object contextKey) {
+ String keyString = contextKeyString(contextKey);
+ ConfigurableApplicationContext ctx = contextKeyToContextMap.remove(keyString);
+ if (ctx != null) {
+ ctx.close();
+ }
+ }
+
+ /**
+ * Subclasses can override this to return a String representation of their
+ * context key for use in caching and logging.
+ * @param contextKey the context key
+ */
+ protected String contextKeyString(Object contextKey) {
+ return ObjectUtils.nullSafeToString(contextKey);
+ }
+
+ /**
+ * Load a new ApplicationContext for the given key.
+ * <p>To be implemented by subclasses.
+ * @param key the context key
+ * @return the corresponding ApplicationContext instance (new)
+ */
+ protected abstract ConfigurableApplicationContext loadContext(Object key) throws Exception;
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/AbstractTransactionalDataSourceSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/AbstractTransactionalDataSourceSpringContextTests.java
new file mode 100644
index 00000000..0f697d11
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/AbstractTransactionalDataSourceSpringContextTests.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.sql.DataSource;
+
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.jdbc.JdbcTestUtils;
+
+/**
+ * Subclass of AbstractTransactionalSpringContextTests that adds some convenience
+ * functionality for JDBC access. Expects a {@link javax.sql.DataSource} bean
+ * to be defined in the Spring application context.
+ *
+ * <p>This class exposes a {@link org.springframework.jdbc.core.JdbcTemplate}
+ * and provides an easy way to delete from the database in a new transaction.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Thomas Risberg
+ * @since 1.1.1
+ * @see #setDataSource(javax.sql.DataSource)
+ * @see #getJdbcTemplate()
+ * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
+ * ({@link org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests})
+ */
+@Deprecated
+@SuppressWarnings({ "unchecked", "rawtypes" })
+public abstract class AbstractTransactionalDataSourceSpringContextTests
+ extends AbstractTransactionalSpringContextTests {
+
+ protected JdbcTemplate jdbcTemplate;
+
+ private String sqlScriptEncoding;
+
+ /**
+ * Did this test delete any tables? If so, we forbid transaction completion,
+ * and only allow rollback.
+ */
+ private boolean zappedTables;
+
+
+ /**
+ * Default constructor for AbstractTransactionalDataSourceSpringContextTests.
+ */
+ public AbstractTransactionalDataSourceSpringContextTests() {
+ }
+
+ /**
+ * Constructor for AbstractTransactionalDataSourceSpringContextTests with a JUnit name.
+ */
+ public AbstractTransactionalDataSourceSpringContextTests(String name) {
+ super(name);
+ }
+
+
+ /**
+ * Setter: DataSource is provided by Dependency Injection.
+ */
+ public void setDataSource(DataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ /**
+ * Return the JdbcTemplate that this base class manages.
+ */
+ public final JdbcTemplate getJdbcTemplate() {
+ return this.jdbcTemplate;
+ }
+
+ /**
+ * Specify the encoding for SQL scripts, if different from the platform encoding.
+ * @see #executeSqlScript
+ */
+ public void setSqlScriptEncoding(String sqlScriptEncoding) {
+ this.sqlScriptEncoding = sqlScriptEncoding;
+ }
+
+
+ /**
+ * Convenient method to delete all rows from these tables.
+ * Calling this method will make avoidance of rollback by calling
+ * {@code setComplete()} impossible.
+ * @see #setComplete
+ */
+ protected void deleteFromTables(String[] names) {
+ for (int i = 0; i < names.length; i++) {
+ int rowCount = this.jdbcTemplate.update("DELETE FROM " + names[i]);
+ if (logger.isInfoEnabled()) {
+ logger.info("Deleted " + rowCount + " rows from table " + names[i]);
+ }
+ }
+ this.zappedTables = true;
+ }
+
+ /**
+ * Overridden to prevent the transaction committing if a number of tables have been
+ * cleared, as a defensive measure against accidental <i>permanent</i> wiping of a database.
+ * @see org.springframework.test.AbstractTransactionalSpringContextTests#setComplete()
+ */
+ protected final void setComplete() {
+ if (this.zappedTables) {
+ throw new IllegalStateException("Cannot set complete after deleting tables");
+ }
+ super.setComplete();
+ }
+
+ /**
+ * Count the rows in the given table
+ * @param tableName table name to count rows in
+ * @return the number of rows in the table
+ */
+ protected int countRowsInTable(String tableName) {
+ return this.jdbcTemplate.queryForInt("SELECT COUNT(0) FROM " + tableName);
+ }
+
+
+ /**
+ * Execute the given SQL script. Will be rolled back by default,
+ * according to the fate of the current transaction.
+ * @param sqlResourcePath Spring resource path for the SQL script.
+ * Should normally be loaded by classpath.
+ * <p>Statements should be delimited with a semicolon. If statements are not delimited with
+ * a semicolon then there should be one statement per line. Statements are allowed to span
+ * lines only if they are delimited with a semicolon.
+ * <p><b>Do not use this method to execute DDL if you expect rollback.</b>
+ * @param continueOnError whether or not to continue without throwing
+ * an exception in the event of an error
+ * @throws DataAccessException if there is an error executing a statement
+ * and continueOnError was false
+ */
+ protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
+ if (logger.isInfoEnabled()) {
+ logger.info("Executing SQL script '" + sqlResourcePath + "'");
+ }
+
+ EncodedResource resource =
+ new EncodedResource(getApplicationContext().getResource(sqlResourcePath), this.sqlScriptEncoding);
+ long startTime = System.currentTimeMillis();
+ List statements = new LinkedList();
+ try {
+ LineNumberReader lnr = new LineNumberReader(resource.getReader());
+ String script = JdbcTestUtils.readScript(lnr);
+ char delimiter = ';';
+ if (!JdbcTestUtils.containsSqlScriptDelimiters(script, delimiter)) {
+ delimiter = '\n';
+ }
+ JdbcTestUtils.splitSqlScript(script, delimiter, statements);
+ for (Iterator itr = statements.iterator(); itr.hasNext(); ) {
+ String statement = (String) itr.next();
+ try {
+ int rowsAffected = this.jdbcTemplate.update(statement);
+ if (logger.isDebugEnabled()) {
+ logger.debug(rowsAffected + " rows affected by SQL: " + statement);
+ }
+ }
+ catch (DataAccessException ex) {
+ if (continueOnError) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("SQL: " + statement + " failed", ex);
+ }
+ }
+ else {
+ throw ex;
+ }
+ }
+ }
+ long elapsedTime = System.currentTimeMillis() - startTime;
+ logger.info("Done executing SQL scriptBuilder '" + sqlResourcePath + "' in " + elapsedTime + " ms");
+ }
+ catch (IOException ex) {
+ throw new DataAccessResourceFailureException("Failed to open SQL script '" + sqlResourcePath + "'", ex);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/AbstractTransactionalSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/AbstractTransactionalSpringContextTests.java
new file mode 100644
index 00000000..af3c6866
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/AbstractTransactionalSpringContextTests.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright 2002-2012 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;
+
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+
+/**
+ * Convenient base class for JUnit 3.8 based tests that should occur in a
+ * transaction, but normally will roll the transaction back on the completion of
+ * each test.
+ *
+ * <p>This is useful in a range of circumstances, allowing the following benefits:
+ * <ul>
+ * <li>Ability to delete or insert any data in the database, without affecting
+ * other tests
+ * <li>Providing a transactional context for any code requiring a transaction
+ * <li>Ability to write anything to the database without any need to clean up.
+ * </ul>
+ *
+ * <p>This class is typically very fast, compared to traditional setup/teardown
+ * scripts.
+ *
+ * <p>If data should be left in the database, call the {@link #setComplete()}
+ * method in each test. The {@link #setDefaultRollback "defaultRollback"}
+ * property, which defaults to "true", determines whether transactions will
+ * complete by default.
+ *
+ * <p>It is even possible to end the transaction early; for example, to verify lazy
+ * loading behavior of an O/R mapping tool. (This is a valuable away to avoid
+ * unexpected errors when testing a web UI, for example.) Simply call the
+ * {@link #endTransaction()} method. Execution will then occur without a
+ * transactional context.
+ *
+ * <p>The {@link #startNewTransaction()} method may be called after a call to
+ * {@link #endTransaction()} if you wish to create a new transaction, quite
+ * independent of the old transaction. The new transaction's default fate will
+ * be to roll back, unless {@link #setComplete()} is called again during the
+ * scope of the new transaction. Any number of transactions may be created and
+ * ended in this way. The final transaction will automatically be rolled back
+ * when the test case is torn down.
+ *
+ * <p>Transactional behavior requires a single bean in the context implementing the
+ * {@link PlatformTransactionManager} interface. This will be set by the
+ * superclass's Dependency Injection mechanism. If using the superclass's Field
+ * Injection mechanism, the implementation should be named "transactionManager".
+ * This mechanism allows the use of the
+ * {@link AbstractDependencyInjectionSpringContextTests} superclass even when
+ * there is more than one transaction manager in the context.
+ *
+ * <p><b>This base class can also be used without transaction management, if no
+ * PlatformTransactionManager bean is found in the context provided.</b> Be
+ * careful about using this mode, as it allows the potential to permanently
+ * modify data. This mode is available only if dependency checking is turned off
+ * in the {@link AbstractDependencyInjectionSpringContextTests} superclass. The
+ * non-transactional capability is provided to enable use of the same subclass
+ * in different environments.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @since 1.1.1
+ * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
+ * ({@link org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests})
+ */
+@Deprecated
+public abstract class AbstractTransactionalSpringContextTests extends AbstractDependencyInjectionSpringContextTests {
+
+ /** The transaction manager to use */
+ protected PlatformTransactionManager transactionManager;
+
+ /** Should we roll back by default? */
+ private boolean defaultRollback = true;
+
+ /** Should we commit the current transaction? */
+ private boolean complete = false;
+
+ /** Number of transactions started */
+ private int transactionsStarted = 0;
+
+ /**
+ * Transaction definition used by this test class: by default, a plain
+ * DefaultTransactionDefinition. Subclasses can change this to cause
+ * different behavior.
+ */
+ protected TransactionDefinition transactionDefinition= new DefaultTransactionDefinition();
+
+ /**
+ * TransactionStatus for this test. Typical subclasses won't need to use it.
+ */
+ protected TransactionStatus transactionStatus;
+
+
+ /**
+ * Default constructor for AbstractTransactionalSpringContextTests.
+ */
+ public AbstractTransactionalSpringContextTests() {
+ }
+
+ /**
+ * Constructor for AbstractTransactionalSpringContextTests with a JUnit name.
+ */
+ public AbstractTransactionalSpringContextTests(String name) {
+ super(name);
+ }
+
+
+ /**
+ * Specify the transaction manager to use. No transaction management will be
+ * available if this is not set. Populated through dependency injection by
+ * the superclass.
+ * <p>
+ * This mode works only if dependency checking is turned off in the
+ * {@link AbstractDependencyInjectionSpringContextTests} superclass.
+ */
+ public void setTransactionManager(PlatformTransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ /**
+ * Subclasses can set this value in their constructor to change the default,
+ * which is always to roll the transaction back.
+ */
+ public void setDefaultRollback(final boolean defaultRollback) {
+ this.defaultRollback = defaultRollback;
+ }
+ /**
+ * Get the <em>default rollback</em> flag for this test.
+ * @see #setDefaultRollback(boolean)
+ * @return The <em>default rollback</em> flag.
+ */
+ protected boolean isDefaultRollback() {
+ return this.defaultRollback;
+ }
+
+ /**
+ * Determines whether or not to rollback transactions for the current test.
+ * <p>The default implementation delegates to {@link #isDefaultRollback()}.
+ * Subclasses can override as necessary.
+ */
+ protected boolean isRollback() {
+ return isDefaultRollback();
+ }
+
+ /**
+ * Call this method in an overridden {@link #runBare()} method to prevent
+ * transactional execution.
+ */
+ protected void preventTransaction() {
+ this.transactionDefinition = null;
+ }
+
+ /**
+ * Call this method in an overridden {@link #runBare()} method to override
+ * the transaction attributes that will be used, so that {@link #setUp()}
+ * and {@link #tearDown()} behavior is modified.
+ * @param customDefinition the custom transaction definition
+ */
+ protected void setTransactionDefinition(TransactionDefinition customDefinition) {
+ this.transactionDefinition = customDefinition;
+ }
+
+ /**
+ * This implementation creates a transaction before test execution.
+ * <p>Override {@link #onSetUpBeforeTransaction()} and/or
+ * {@link #onSetUpInTransaction()} to add custom set-up behavior for
+ * transactional execution. Alternatively, override this method for general
+ * set-up behavior, calling {@code super.onSetUp()} as part of your
+ * method implementation.
+ * @throws Exception simply let any exception propagate
+ * @see #onTearDown()
+ */
+ protected void onSetUp() throws Exception {
+ this.complete = !this.isRollback();
+
+ if (this.transactionManager == null) {
+ this.logger.info("No transaction manager set: test will NOT run within a transaction");
+ }
+ else if (this.transactionDefinition == null) {
+ this.logger.info("No transaction definition set: test will NOT run within a transaction");
+ }
+ else {
+ onSetUpBeforeTransaction();
+ startNewTransaction();
+ try {
+ onSetUpInTransaction();
+ }
+ catch (final Exception ex) {
+ endTransaction();
+ throw ex;
+ }
+ }
+ }
+
+ /**
+ * Subclasses can override this method to perform any setup operations, such
+ * as populating a database table, <i>before</i> the transaction created by
+ * this class. Only invoked if there <i>is</i> a transaction: that is, if
+ * {@link #preventTransaction()} has not been invoked in an overridden
+ * {@link #runTest()} method.
+ * @throws Exception simply let any exception propagate
+ */
+ protected void onSetUpBeforeTransaction() throws Exception {
+ }
+
+ /**
+ * Subclasses can override this method to perform any setup operations, such
+ * as populating a database table, <i>within</i> the transaction created by
+ * this class.
+ * <p><b>NB:</b> Not called if there is no transaction management, due to no
+ * transaction manager being provided in the context.
+ * <p>If any {@link Throwable} is thrown, the transaction that has been started
+ * prior to the execution of this method will be
+ * {@link #endTransaction() ended} (or rather an attempt will be made to
+ * {@link #endTransaction() end it gracefully}); The offending
+ * {@link Throwable} will then be rethrown.
+ * @throws Exception simply let any exception propagate
+ */
+ protected void onSetUpInTransaction() throws Exception {
+ }
+
+ /**
+ * This implementation ends the transaction after test execution.
+ * <p>Override {@link #onTearDownInTransaction()} and/or
+ * {@link #onTearDownAfterTransaction()} to add custom tear-down behavior
+ * for transactional execution. Alternatively, override this method for
+ * general tear-down behavior, calling {@code super.onTearDown()} as
+ * part of your method implementation.
+ * <p>Note that {@link #onTearDownInTransaction()} will only be called if a
+ * transaction is still active at the time of the test shutdown. In
+ * particular, it will <i>not</i> be called if the transaction has been
+ * completed with an explicit {@link #endTransaction()} call before.
+ * @throws Exception simply let any exception propagate
+ * @see #onSetUp()
+ */
+ protected void onTearDown() throws Exception {
+ // Call onTearDownInTransaction and end transaction if the transaction
+ // is still active.
+ if (this.transactionStatus != null && !this.transactionStatus.isCompleted()) {
+ try {
+ onTearDownInTransaction();
+ }
+ finally {
+ endTransaction();
+ }
+ }
+
+ // Call onTearDownAfterTransaction if there was at least one
+ // transaction, even if it has been completed early through an
+ // endTransaction() call.
+ if (this.transactionsStarted > 0) {
+ onTearDownAfterTransaction();
+ }
+ }
+
+ /**
+ * Subclasses can override this method to run invariant tests here. The
+ * transaction is <i>still active</i> at this point, so any changes made in
+ * the transaction will still be visible. However, there is no need to clean
+ * up the database, as a rollback will follow automatically.
+ * <p><b>NB:</b> Not called if there is no actual transaction, for example due
+ * to no transaction manager being provided in the application context.
+ * @throws Exception simply let any exception propagate
+ */
+ protected void onTearDownInTransaction() throws Exception {
+ }
+
+ /**
+ * Subclasses can override this method to perform cleanup after a
+ * transaction here. At this point, the transaction is <i>not active anymore</i>.
+ * @throws Exception simply let any exception propagate
+ */
+ protected void onTearDownAfterTransaction() throws Exception {
+ }
+
+ /**
+ * Cause the transaction to commit for this test method, even if the test
+ * method is configured to {@link #isRollback() rollback}.
+ * @throws IllegalStateException if the operation cannot be set to complete
+ * as no transaction manager was provided
+ */
+ protected void setComplete() {
+ if (this.transactionManager == null) {
+ throw new IllegalStateException("No transaction manager set");
+ }
+ this.complete = true;
+ }
+
+ /**
+ * Immediately force a commit or rollback of the transaction, according to
+ * the {@code complete} and {@link #isRollback() rollback} flags.
+ * <p>Can be used to explicitly let the transaction end early, for example to
+ * check whether lazy associations of persistent objects work outside of a
+ * transaction (that is, have been initialized properly).
+ * @see #setComplete()
+ */
+ protected void endTransaction() {
+ final boolean commit = this.complete || !isRollback();
+ if (this.transactionStatus != null) {
+ try {
+ if (commit) {
+ this.transactionManager.commit(this.transactionStatus);
+ this.logger.debug("Committed transaction after execution of test [" + getName() + "].");
+ }
+ else {
+ this.transactionManager.rollback(this.transactionStatus);
+ this.logger.debug("Rolled back transaction after execution of test [" + getName() + "].");
+ }
+ }
+ finally {
+ this.transactionStatus = null;
+ }
+ }
+ }
+
+ /**
+ * Start a new transaction. Only call this method if
+ * {@link #endTransaction()} has been called. {@link #setComplete()} can be
+ * used again in the new transaction. The fate of the new transaction, by
+ * default, will be the usual rollback.
+ * @throws TransactionException if starting the transaction failed
+ */
+ protected void startNewTransaction() throws TransactionException {
+ if (this.transactionStatus != null) {
+ throw new IllegalStateException("Cannot start new transaction without ending existing transaction: "
+ + "Invoke endTransaction() before startNewTransaction()");
+ }
+ if (this.transactionManager == null) {
+ throw new IllegalStateException("No transaction manager set");
+ }
+
+ this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition);
+ ++this.transactionsStarted;
+ this.complete = !this.isRollback();
+
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Began transaction (" + this.transactionsStarted + "): transaction manager ["
+ + this.transactionManager + "]; rollback [" + this.isRollback() + "].");
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/AssertThrows.java b/spring-test/src/main/java/org/springframework/test/AssertThrows.java
new file mode 100644
index 00000000..2958fe02
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/AssertThrows.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * Simple method object encapsulation of the 'test-for-Exception' scenario (for JUnit).
+ *
+ * <p>Used like so:
+ *
+ * <pre class="code">
+ * // the class under test
+ * public class Foo {
+ * public void someBusinessLogic(String name) {
+ * if (name == null) {
+ * throw new IllegalArgumentException("The 'name' argument is required");
+ * }
+ * // rest of business logic here...
+ * }
+ * }</pre>
+ *
+ * The test for the above bad argument path can be expressed using the
+ * {@link AssertThrows} class like so:
+ *
+ * <pre class="code">
+ * public class FooTest {
+ * public void testSomeBusinessLogicBadArgumentPath() {
+ * new AssertThrows(IllegalArgumentException.class) {
+ * public void test() {
+ * new Foo().someBusinessLogic(null);
+ * }
+ * }.runTest();
+ * }
+ * }</pre>
+ *
+ * This will result in the test passing if the {@code Foo.someBusinessLogic(..)}
+ * method threw an {@link IllegalArgumentException}; if it did not, the
+ * test would fail with the following message:
+ *
+ * <pre class="code">
+ * "Must have thrown a [class java.lang.IllegalArgumentException]"</pre>
+ *
+ * If the <b>wrong</b> type of {@link Exception} was thrown, the
+ * test will also fail, this time with a message similar to the following:
+ *
+ * <pre class="code">
+ * "junit.framework.AssertionFailedError: Was expecting a [class java.lang.UnsupportedOperationException] to be thrown, but instead a [class java.lang.IllegalArgumentException] was thrown"</pre>
+ *
+ * The test for the correct {@link Exception} respects polymorphism,
+ * so you can test that any old {@link Exception} is thrown like so:
+ *
+ * <pre class="code">
+ * public class FooTest {
+ * public void testSomeBusinessLogicBadArgumentPath() {
+ * // any Exception will do...
+ * new AssertThrows(Exception.class) {
+ * public void test() {
+ * new Foo().someBusinessLogic(null);
+ * }
+ * }.runTest();
+ * }
+ * }</pre>
+ *
+ * Intended for use with JUnit 4 and TestNG (as of Spring 3.0).
+ * You might want to compare this class with the
+ * {@code junit.extensions.ExceptionTestCase} class.
+ *
+ * @author Rick Evans
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @deprecated favor use of JUnit 4's {@code @Test(expected=...)} support
+ */
+@Deprecated
+@SuppressWarnings({ "unchecked", "rawtypes" })
+public abstract class AssertThrows {
+
+ private final Class expectedException;
+
+ private String failureMessage;
+
+ private Exception actualException;
+
+
+ /**
+ * Create a new instance of the {@link AssertThrows} class.
+ * @param expectedException the {@link Exception} expected to be
+ * thrown during the execution of the surrounding test
+ * @throws IllegalArgumentException if the supplied {@code expectedException} is
+ * {@code null}; or if said argument is not an {@link Exception}-derived class
+ */
+ public AssertThrows(Class expectedException) {
+ this(expectedException, null);
+ }
+
+ /**
+ * Create a new instance of the {@link AssertThrows} class.
+ * @param expectedException the {@link Exception} expected to be
+ * thrown during the execution of the surrounding test
+ * @param failureMessage the extra, contextual failure message that will be
+ * included in the failure text if the text fails (can be {@code null})
+ * @throws IllegalArgumentException if the supplied {@code expectedException} is
+ * {@code null}; or if said argument is not an {@link Exception}-derived class
+ */
+ public AssertThrows(Class expectedException, String failureMessage) {
+ if (expectedException == null) {
+ throw new IllegalArgumentException("The 'expectedException' argument is required");
+ }
+ if (!Exception.class.isAssignableFrom(expectedException)) {
+ throw new IllegalArgumentException(
+ "The 'expectedException' argument is not an Exception type (it obviously must be)");
+ }
+ this.expectedException = expectedException;
+ this.failureMessage = failureMessage;
+ }
+
+
+ /**
+ * Return the {@link java.lang.Exception} expected to be thrown during
+ * the execution of the surrounding test.
+ */
+ protected Class getExpectedException() {
+ return this.expectedException;
+ }
+
+ /**
+ * Set the extra, contextual failure message that will be included
+ * in the failure text if the text fails.
+ */
+ public void setFailureMessage(String failureMessage) {
+ this.failureMessage = failureMessage;
+ }
+
+ /**
+ * Return the extra, contextual failure message that will be included
+ * in the failure text if the text fails.
+ */
+ protected String getFailureMessage() {
+ return this.failureMessage;
+ }
+
+
+ /**
+ * Subclass must override this {@code abstract} method and
+ * provide the test logic.
+ * @throws Exception if an error occurs during the execution of the
+ * aformentioned test logic
+ */
+ public abstract void test() throws Exception;
+
+
+ /**
+ * The main template method that drives the running of the
+ * {@link #test() test logic} and the
+ * {@link #checkExceptionExpectations(Exception) checking} of the
+ * resulting (expected) {@link java.lang.Exception}.
+ * @see #test()
+ * @see #doFail()
+ * @see #checkExceptionExpectations(Exception)
+ */
+ public void runTest() {
+ try {
+ test();
+ doFail();
+ }
+ catch (Exception actualException) {
+ this.actualException = actualException;
+ checkExceptionExpectations(actualException);
+ }
+ }
+
+ /**
+ * Template method called when the test fails; i.e. the expected
+ * {@link java.lang.Exception} is <b>not</b> thrown.
+ * <p>The default implementation simply fails the test via a call to
+ * {@link org.junit.Assert#fail(String)}.
+ * <p>If you want to customise the failure message, consider overriding
+ * {@link #createMessageForNoExceptionThrown()}, and / or supplying an
+ * extra, contextual failure message via the appropriate constructor overload.
+ * @see #getFailureMessage()
+ */
+ protected void doFail() {
+ throw new AssertionError(createMessageForNoExceptionThrown());
+ }
+
+ /**
+ * Creates the failure message used if the test fails
+ * (i.e. the expected exception is not thrown in the body of the test).
+ * @return the failure message used if the test fails
+ * @see #getFailureMessage()
+ */
+ protected String createMessageForNoExceptionThrown() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Should have thrown a [").append(this.getExpectedException()).append("]");
+ if (getFailureMessage() != null) {
+ sb.append(": ").append(getFailureMessage());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Does the donkey work of checking (verifying) that the
+ * {@link Exception} that was thrown in the body of a test is
+ * an instance of the {@link #getExpectedException()} class (or an
+ * instance of a subclass).
+ * <p>If you want to customise the failure message, consider overriding
+ * {@link #createMessageForWrongThrownExceptionType(Exception)}.
+ * @param actualException the {@link Exception} that has been thrown
+ * in the body of a test method (will never be {@code null})
+ */
+ protected void checkExceptionExpectations(Exception actualException) {
+ if (!getExpectedException().isAssignableFrom(actualException.getClass())) {
+ AssertionError error =
+ new AssertionError(createMessageForWrongThrownExceptionType(actualException));
+ error.initCause(actualException);
+ throw error;
+ }
+ }
+
+ /**
+ * Creates the failure message used if the wrong type
+ * of {@link java.lang.Exception} is thrown in the body of the test.
+ * @param actualException the actual exception thrown
+ * @return the message for the given exception
+ */
+ protected String createMessageForWrongThrownExceptionType(Exception actualException) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Was expecting a [").append(getExpectedException().getName());
+ sb.append("] to be thrown, but instead a [").append(actualException.getClass().getName());
+ sb.append("] was thrown.");
+ return sb.toString();
+ }
+
+
+ /**
+ * Expose the actual exception thrown from {@link #test}, if any.
+ * @return the actual exception, or {@code null} if none
+ */
+ public final Exception getActualException() {
+ return this.actualException;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/ConditionalTestCase.java b/spring-test/src/main/java/org/springframework/test/ConditionalTestCase.java
new file mode 100644
index 00000000..a8448f22
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/ConditionalTestCase.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2002-2007 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;
+
+import junit.framework.TestCase;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Superclass for JUnit 3.8 based tests that allows conditional test execution
+ * at the individual test method level. The
+ * {@link #isDisabledInThisEnvironment(String) isDisabledInThisEnvironment()}
+ * method is invoked before the execution of each test method. Subclasses can
+ * override that method to return whether or not the given test should be
+ * executed. Note that the tests will still appear to have executed and passed;
+ * however, log output will show that the test was not executed.
+ *
+ * @author Rod Johnson
+ * @since 2.0
+ * @see #isDisabledInThisEnvironment
+ * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
+ * ({@link org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests})
+ */
+@Deprecated
+public abstract class ConditionalTestCase extends TestCase {
+
+ private static int disabledTestCount;
+
+
+ /**
+ * Return the number of tests disabled in this environment.
+ */
+ public static int getDisabledTestCount() {
+ return disabledTestCount;
+ }
+
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+
+ /**
+ * Default constructor for ConditionalTestCase.
+ */
+ public ConditionalTestCase() {
+ }
+
+ /**
+ * Constructor for ConditionalTestCase with a JUnit name.
+ */
+ public ConditionalTestCase(String name) {
+ super(name);
+ }
+
+ public void runBare() throws Throwable {
+ // getName will return the name of the method being run
+ if (isDisabledInThisEnvironment(getName())) {
+ recordDisabled();
+ this.logger.info("**** " + getClass().getName() + "." + getName() + " is disabled in this environment: "
+ + "Total disabled tests = " + getDisabledTestCount());
+ return;
+ }
+
+ // Let JUnit handle execution
+ super.runBare();
+ }
+
+ /**
+ * Should this test run?
+ * @param testMethodName name of the test method
+ * @return whether the test should execute in the current environment
+ */
+ protected boolean isDisabledInThisEnvironment(String testMethodName) {
+ return false;
+ }
+
+ /**
+ * Record a disabled test.
+ * @return the current disabled test count
+ */
+ protected int recordDisabled() {
+ return ++disabledTestCount;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/AbstractAnnotationAwareTransactionalTests.java b/spring-test/src/main/java/org/springframework/test/annotation/AbstractAnnotationAwareTransactionalTests.java
new file mode 100644
index 00000000..30c08a22
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/AbstractAnnotationAwareTransactionalTests.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import junit.framework.AssertionFailedError;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
+import org.springframework.transaction.interceptor.TransactionAttributeSource;
+import org.springframework.util.Assert;
+
+/**
+ * <p>
+ * Java 5 specific subclass of
+ * {@link AbstractTransactionalDataSourceSpringContextTests}, exposing a
+ * {@link SimpleJdbcTemplate} and obeying annotations for transaction control.
+ * </p>
+ * <p>
+ * For example, test methods can be annotated with the regular Spring
+ * {@link org.springframework.transaction.annotation.Transactional @Transactional}
+ * annotation (e.g., to force execution in a read-only transaction) or with the
+ * {@link NotTransactional @NotTransactional} annotation to prevent any
+ * transaction being created at all. In addition, individual test methods can be
+ * annotated with {@link Rollback @Rollback} to override the
+ * {@link #isDefaultRollback() default rollback} settings.
+ * </p>
+ * <p>
+ * The following list constitutes all annotations currently supported by
+ * AbstractAnnotationAwareTransactionalTests:
+ * </p>
+ * <ul>
+ * <li>{@link DirtiesContext @DirtiesContext}</li>
+ * <li>{@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration}</li>
+ * <li>{@link IfProfileValue @IfProfileValue}</li>
+ * <li>{@link ExpectedException @ExpectedException}</li>
+ * <li>{@link Timed @Timed}</li>
+ * <li>{@link Repeat @Repeat}</li>
+ * <li>{@link org.springframework.transaction.annotation.Transactional @Transactional}</li>
+ * <li>{@link NotTransactional @NotTransactional}</li>
+ * <li>{@link Rollback @Rollback}</li>
+ * </ul>
+ *
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
+ * ({@link org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests})
+ */
+@Deprecated
+public abstract class AbstractAnnotationAwareTransactionalTests extends
+ AbstractTransactionalDataSourceSpringContextTests {
+
+ protected SimpleJdbcTemplate simpleJdbcTemplate;
+
+ private final TransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource();
+
+ /**
+ * {@link ProfileValueSource} available to subclasses but primarily intended
+ * for use in {@link #isDisabledInThisEnvironment(Method)}.
+ * <p>Set to {@link SystemProfileValueSource} by default for backwards
+ * compatibility; however, the value may be changed in the
+ * {@link #AbstractAnnotationAwareTransactionalTests(String)} constructor.
+ */
+ protected ProfileValueSource profileValueSource = SystemProfileValueSource.getInstance();
+
+
+ /**
+ * Default constructor for AbstractAnnotationAwareTransactionalTests, which
+ * delegates to {@link #AbstractAnnotationAwareTransactionalTests(String)}.
+ */
+ public AbstractAnnotationAwareTransactionalTests() {
+ this(null);
+ }
+
+ /**
+ * Constructs a new AbstractAnnotationAwareTransactionalTests instance with
+ * the specified JUnit {@code name} and retrieves the configured (or
+ * default) {@link ProfileValueSource}.
+ * @param name the name of the current test
+ * @see ProfileValueUtils#retrieveProfileValueSource(Class)
+ */
+ public AbstractAnnotationAwareTransactionalTests(String name) {
+ super(name);
+ this.profileValueSource = ProfileValueUtils.retrieveProfileValueSource(getClass());
+ }
+
+
+ @Override
+ public void setDataSource(DataSource dataSource) {
+ super.setDataSource(dataSource);
+ // JdbcTemplate will be identically configured
+ this.simpleJdbcTemplate = new SimpleJdbcTemplate(this.jdbcTemplate);
+ }
+
+ /**
+ * Search for a unique {@link ProfileValueSource} in the supplied
+ * {@link ApplicationContext}. If found, the
+ * {@code profileValueSource} for this test will be set to the unique
+ * {@link ProfileValueSource}.
+ * @param applicationContext the ApplicationContext in which to search for
+ * the ProfileValueSource; may not be {@code null}
+ * @deprecated Use {@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration} instead.
+ */
+ @Deprecated
+ protected void findUniqueProfileValueSourceFromContext(ApplicationContext applicationContext) {
+ Assert.notNull(applicationContext, "Can not search for a ProfileValueSource in a null ApplicationContext.");
+ ProfileValueSource uniqueProfileValueSource = null;
+ Map<?, ?> beans = applicationContext.getBeansOfType(ProfileValueSource.class);
+ if (beans.size() == 1) {
+ uniqueProfileValueSource = (ProfileValueSource) beans.values().iterator().next();
+ }
+ if (uniqueProfileValueSource != null) {
+ this.profileValueSource = uniqueProfileValueSource;
+ }
+ }
+
+ /**
+ * Overridden to populate transaction definition from annotations.
+ */
+ @Override
+ public void runBare() throws Throwable {
+
+ // getName will return the name of the method being run.
+ if (isDisabledInThisEnvironment(getName())) {
+ // Let superclass log that we didn't run the test.
+ super.runBare();
+ return;
+ }
+
+ final Method testMethod = getTestMethod();
+
+ if (isDisabledInThisEnvironment(testMethod)) {
+ recordDisabled();
+ this.logger.info("**** " + getClass().getName() + "." + getName() + " is disabled in this environment: "
+ + "Total disabled tests=" + getDisabledTestCount());
+ return;
+ }
+
+ TransactionDefinition explicitTransactionDefinition =
+ this.transactionAttributeSource.getTransactionAttribute(testMethod, getClass());
+ if (explicitTransactionDefinition != null) {
+ this.logger.info("Custom transaction definition [" + explicitTransactionDefinition + "] for test method ["
+ + getName() + "].");
+ setTransactionDefinition(explicitTransactionDefinition);
+ }
+ else if (testMethod.isAnnotationPresent(NotTransactional.class)) {
+ // Don't have any transaction...
+ preventTransaction();
+ }
+
+ // Let JUnit handle execution. We're just changing the state of the test class first.
+ runTestTimed(new TestExecutionCallback() {
+ public void run() throws Throwable {
+ try {
+ AbstractAnnotationAwareTransactionalTests.super.runBare();
+ }
+ finally {
+ // Mark the context to be blown away if the test was
+ // annotated to result in setDirty being invoked
+ // automatically.
+ if (testMethod.isAnnotationPresent(DirtiesContext.class)) {
+ AbstractAnnotationAwareTransactionalTests.this.setDirty();
+ }
+ }
+ }
+ }, testMethod);
+ }
+
+ /**
+ * Determine if the test for the supplied {@code testMethod} should
+ * run in the current environment.
+ * <p>The default implementation is based on
+ * {@link IfProfileValue @IfProfileValue} semantics.
+ * @param testMethod the test method
+ * @return {@code true} if the test is <em>disabled</em> in the current environment
+ * @see ProfileValueUtils#isTestEnabledInThisEnvironment
+ */
+ protected boolean isDisabledInThisEnvironment(Method testMethod) {
+ return !ProfileValueUtils.isTestEnabledInThisEnvironment(this.profileValueSource, testMethod, getClass());
+ }
+
+ /**
+ * Get the current test method.
+ */
+ protected Method getTestMethod() {
+ assertNotNull("TestCase.getName() cannot be null", getName());
+ Method testMethod = null;
+ try {
+ // Use same algorithm as JUnit itself to retrieve the test method
+ // about to be executed (the method name is returned by getName). It
+ // has to be public so we can retrieve it.
+ testMethod = getClass().getMethod(getName(), (Class[]) null);
+ }
+ catch (NoSuchMethodException ex) {
+ fail("Method '" + getName() + "' not found");
+ }
+ if (!Modifier.isPublic(testMethod.getModifiers())) {
+ fail("Method '" + getName() + "' should be public");
+ }
+ return testMethod;
+ }
+
+ /**
+ * Determine whether or not to rollback transactions for the current test
+ * by taking into consideration the
+ * {@link #isDefaultRollback() default rollback} flag and a possible
+ * method-level override via the {@link Rollback @Rollback} annotation.
+ * @return the <em>rollback</em> flag for the current test
+ */
+ @Override
+ protected boolean isRollback() {
+ boolean rollback = isDefaultRollback();
+ Rollback rollbackAnnotation = getTestMethod().getAnnotation(Rollback.class);
+ if (rollbackAnnotation != null) {
+ boolean rollbackOverride = rollbackAnnotation.value();
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Method-level @Rollback(" + rollbackOverride + ") overrides default rollback ["
+ + rollback + "] for test [" + getName() + "].");
+ }
+ rollback = rollbackOverride;
+ }
+ else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("No method-level @Rollback override: using default rollback [" + rollback
+ + "] for test [" + getName() + "].");
+ }
+ }
+ return rollback;
+ }
+
+ private void runTestTimed(TestExecutionCallback tec, Method testMethod) throws Throwable {
+ Timed timed = testMethod.getAnnotation(Timed.class);
+ if (timed == null) {
+ runTest(tec, testMethod);
+ }
+ else {
+ long startTime = System.currentTimeMillis();
+ try {
+ runTest(tec, testMethod);
+ }
+ finally {
+ long elapsed = System.currentTimeMillis() - startTime;
+ if (elapsed > timed.millis()) {
+ fail("Took " + elapsed + " ms; limit was " + timed.millis());
+ }
+ }
+ }
+ }
+
+ private void runTest(TestExecutionCallback tec, Method testMethod) throws Throwable {
+ ExpectedException expectedExceptionAnnotation = testMethod.getAnnotation(ExpectedException.class);
+ boolean exceptionIsExpected = (expectedExceptionAnnotation != null && expectedExceptionAnnotation.value() != null);
+ Class<? extends Throwable> expectedException = (exceptionIsExpected ? expectedExceptionAnnotation.value() : null);
+
+ Repeat repeat = testMethod.getAnnotation(Repeat.class);
+ int runs = ((repeat != null) && (repeat.value() > 1)) ? repeat.value() : 1;
+
+ for (int i = 0; i < runs; i++) {
+ try {
+ if (runs > 1 && this.logger != null && this.logger.isInfoEnabled()) {
+ this.logger.info("Repetition " + (i + 1) + " of test " + testMethod.getName());
+ }
+ tec.run();
+ if (exceptionIsExpected) {
+ fail("Expected exception: " + expectedException.getName());
+ }
+ }
+ catch (Throwable t) {
+ if (!exceptionIsExpected) {
+ throw t;
+ }
+ if (!expectedException.isAssignableFrom(t.getClass())) {
+ // Wrap the unexpected throwable with an explicit message.
+ AssertionFailedError assertionError = new AssertionFailedError("Unexpected exception, expected<" +
+ expectedException.getName() + "> but was<" + t.getClass().getName() + ">");
+ assertionError.initCause(t);
+ throw assertionError;
+ }
+ }
+ }
+ }
+
+
+ private static interface TestExecutionCallback {
+
+ void run() throws Throwable;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java b/spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java
new file mode 100644
index 00000000..aa6ed211
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/DirtiesContext.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.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;
+
+/**
+ * Test annotation which indicates that the
+ * {@link org.springframework.context.ApplicationContext ApplicationContext}
+ * associated with a test is <em>dirty</em> and should be closed:
+ * <ul>
+ * <li>after the current test, when declared at the method level</li>
+ * <li>after each test method in the current test class, when declared at the
+ * class level with class mode set to {@link ClassMode#AFTER_EACH_TEST_METHOD
+ * AFTER_EACH_TEST_METHOD}</li>
+ * <li>after the current test class, when declared at the class level with class
+ * mode set to {@link ClassMode#AFTER_CLASS AFTER_CLASS}</li>
+ * </ul>
+ * <p>
+ * Use this annotation if a test has modified the context &mdash; for example, by
+ * replacing a bean definition or changing the state of a singleton bean.
+ * Subsequent tests will be supplied a new context.
+ * </p>
+ * <p>
+ * {@code @DirtiesContext} may be used as a class-level and method-level
+ * annotation within the same class. In such scenarios, the
+ * {@code ApplicationContext} will be marked as <em>dirty</em> after any
+ * such annotated method as well as after the entire class. If the
+ * {@link ClassMode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
+ * AFTER_EACH_TEST_METHOD}, the context will be marked dirty after each test
+ * method in the class.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @author Rod Johnson
+ * @since 2.0
+ * @see org.springframework.test.context.ContextConfiguration
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+public @interface DirtiesContext {
+
+ /**
+ * Defines <i>modes</i> which determine how {@code @DirtiesContext} is
+ * interpreted when used to annotate a test class.
+ *
+ * @since 3.0
+ */
+ static enum ClassMode {
+
+ /**
+ * The associated {@code ApplicationContext} will be marked as
+ * <em>dirty</em> after the test class.
+ */
+ AFTER_CLASS,
+
+ /**
+ * The associated {@code ApplicationContext} will be marked as
+ * <em>dirty</em> after each test method in the class.
+ */
+ AFTER_EACH_TEST_METHOD;
+ }
+
+ /**
+ * Defines <i>modes</i> which determine how the context cache is cleared
+ * when {@code @DirtiesContext} is used in a test whose context is
+ * configured as part of a hierarchy via
+ * {@link org.springframework.test.context.ContextHierarchy @ContextHierarchy}.
+ *
+ * @since 3.2.2
+ */
+ static enum HierarchyMode {
+
+ /**
+ * The context cache will be cleared using an <em>exhaustive</em> algorithm
+ * that includes not only the {@linkplain HierarchyMode#CURRENT_LEVEL current level}
+ * but also all other context hierarchies that share an ancestor context
+ * common to the current test.
+ *
+ * <p>All {@code ApplicationContexts} that reside in a subhierarchy of
+ * the common ancestor context will be removed from the context cache and
+ * closed.
+ */
+ EXHAUSTIVE,
+
+ /**
+ * The {@code ApplicationContext} for the <em>current level</em> in the
+ * context hierarchy and all contexts in subhierarchies of the current
+ * level will be removed from the context cache and closed.
+ *
+ * <p>The <em>current level</em> refers to the {@code ApplicationContext}
+ * at the lowest level in the context hierarchy that is visible from the
+ * current test.
+ */
+ CURRENT_LEVEL;
+ }
+
+
+ /**
+ * The <i>mode</i> to use when a test class is annotated with
+ * {@code @DirtiesContext}.
+ * <p>Defaults to {@link ClassMode#AFTER_CLASS AFTER_CLASS}.
+ * <p>Note: Setting the class mode on an annotated test method has no meaning,
+ * since the mere presence of the {@code @DirtiesContext} annotation on a
+ * test method is sufficient.
+ *
+ * @since 3.0
+ */
+ ClassMode classMode() default ClassMode.AFTER_CLASS;
+
+ /**
+ * The context cache clearing <em>mode</em> to use when a context is
+ * configured as part of a hierarchy via
+ * {@link org.springframework.test.context.ContextHierarchy @ContextHierarchy}.
+ * <p>Defaults to {@link HierarchyMode#EXHAUSTIVE EXHAUSTIVE}.
+ *
+ * @since 3.2.2
+ */
+ HierarchyMode hierarchyMode() default HierarchyMode.EXHAUSTIVE;
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/ExpectedException.java b/spring-test/src/main/java/org/springframework/test/annotation/ExpectedException.java
new file mode 100644
index 00000000..944f3aea
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/ExpectedException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Test annotation to indicate that a test method is required to throw the
+ * specified exception.
+ *
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @since 2.0
+ * @deprecated as of Spring 3.1 in favor of using built-in support for declaring
+ * expected exceptions in the underlying testing framework (e.g., JUnit, TestNG, etc.)
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Deprecated
+public @interface ExpectedException {
+
+ Class<? extends Throwable> value();
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/IfProfileValue.java b/spring-test/src/main/java/org/springframework/test/annotation/IfProfileValue.java
new file mode 100644
index 00000000..c5c71846
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/IfProfileValue.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2002-2012 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.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;
+
+/**
+ * <p>
+ * Test annotation to indicate that a test is enabled for a specific testing
+ * profile or environment. If the configured {@link ProfileValueSource} returns
+ * a matching {@link #value() value} for the provided {@link #name() name}, the
+ * test will be enabled.
+ * </p>
+ * <p>
+ * Note: {@code &#064;IfProfileValue} can be applied at the class level,
+ * the method level, or both. {@code &#064;IfProfileValue} at the class
+ * level overrides method-level usage of {@code &#064;IfProfileValue} for
+ * any methods within that class.
+ * </p>
+ * <p>
+ * Examples: when using {@link SystemProfileValueSource} as the
+ * {@link ProfileValueSource} implementation, you can configure a test method to
+ * run only on Java VMs from Sun Microsystems as follows:
+ * </p>
+ *
+ * <pre class="code">
+ * &#064;IfProfileValue(name = &quot;java.vendor&quot;, value = &quot;Sun Microsystems Inc.&quot;)
+ * public void testSomething() {
+ * // ...
+ * }
+ * </pre>
+ * <p>
+ * You can alternatively configure {@code &#064;IfProfileValue} with
+ * <em>OR</em> semantics for multiple {@link #values() values} as follows
+ * (assuming a {@link ProfileValueSource} has been appropriately configured for
+ * the &quot;test-groups&quot; name):
+ * </p>
+ *
+ * <pre class="code">
+ * &#064;IfProfileValue(name = &quot;test-groups&quot;, values = { &quot;unit-tests&quot;, &quot;integration-tests&quot; })
+ * public void testWhichRunsForUnitOrIntegrationTestGroups() {
+ * // ...
+ * }
+ * </pre>
+ *
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @since 2.0
+ * @see ProfileValueSource
+ * @see ProfileValueSourceConfiguration
+ * @see ProfileValueUtils
+ * @see org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests
+ * @see org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests
+ * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target( { ElementType.TYPE, ElementType.METHOD })
+public @interface IfProfileValue {
+
+ /**
+ * The {@code name} of the <em>profile value</em> against which to
+ * test.
+ */
+ String name();
+
+ /**
+ * A single, permissible {@code value} of the <em>profile value</em>
+ * for the given {@link #name() name}.
+ * <p>
+ * Note: Assigning values to both {@link #value()} and {@link #values()}
+ * will lead to a configuration conflict.
+ */
+ String value() default "";
+
+ /**
+ * A list of all permissible {@code values} of the
+ * <em>profile value</em> for the given {@link #name() name}.
+ * <p>
+ * Note: Assigning values to both {@link #value()} and {@link #values()}
+ * will lead to a configuration conflict.
+ */
+ String[] values() default {};
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/NotTransactional.java b/spring-test/src/main/java/org/springframework/test/annotation/NotTransactional.java
new file mode 100644
index 00000000..c8539186
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/NotTransactional.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Test annotation to indicate that a method is <i>not transactional</i>.
+ *
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @since 2.0
+ * @deprecated as of Spring 3.0, in favor of moving the non-transactional test
+ * method to a separate (non-transactional) test class or to a
+ * {@link org.springframework.test.context.transaction.BeforeTransaction
+ * &#64;BeforeTransaction} or
+ * {@link org.springframework.test.context.transaction.AfterTransaction
+ * &#64;AfterTransaction} method.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Deprecated
+public @interface NotTransactional {
+}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSource.java b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSource.java
new file mode 100644
index 00000000..05ee407f
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSource.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+/**
+ * <p>
+ * Strategy interface for retrieving <em>profile values</em> for a given
+ * testing environment.
+ * </p>
+ * <p>
+ * Concrete implementations must provide a {@code public} no-args
+ * constructor.
+ * </p>
+ * <p>
+ * Spring provides the following out-of-the-box implementations:
+ * </p>
+ * <ul>
+ * <li>{@link SystemProfileValueSource}</li>
+ * </ul>
+ *
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @since 2.0
+ * @see ProfileValueSourceConfiguration
+ * @see IfProfileValue
+ * @see ProfileValueUtils
+ */
+public interface ProfileValueSource {
+
+ /**
+ * Get the <em>profile value</em> indicated by the specified key.
+ * @param key the name of the <em>profile value</em>
+ * @return the String value of the <em>profile value</em>, or {@code null}
+ * if there is no <em>profile value</em> with that key
+ */
+ String get(String key);
+
+}
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
new file mode 100644
index 00000000..4e240aa6
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2012 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.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;
+
+/**
+ * <p>
+ * 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
+ * &#064;IfProfileValue} annotation.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see ProfileValueSource
+ * @see IfProfileValue
+ * @see ProfileValueUtils
+ */
+@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
+ */
+ Class<? extends ProfileValueSource> value() default SystemProfileValueSource.class;
+
+}
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
new file mode 100644
index 00000000..922e34c6
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/ProfileValueUtils.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import java.lang.reflect.Method;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * General utility methods for working with <em>profile values</em>.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see ProfileValueSource
+ * @see ProfileValueSourceConfiguration
+ * @see IfProfileValue
+ */
+public abstract class ProfileValueUtils {
+
+ private static final Log logger = LogFactory.getLog(ProfileValueUtils.class);
+
+
+ /**
+ * Retrieves the {@link ProfileValueSource} type for the specified
+ * {@link Class test class} as configured via the
+ * {@link ProfileValueSourceConfiguration
+ * &#064;ProfileValueSourceConfiguration} annotation and instantiates a new
+ * instance of that type.
+ * <p>
+ * If {@link ProfileValueSourceConfiguration
+ * &#064;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
+ * class
+ * @see SystemProfileValueSource
+ */
+ @SuppressWarnings("unchecked")
+ public static ProfileValueSource retrieveProfileValueSource(Class<?> testClass) {
+ Assert.notNull(testClass, "testClass must not be null");
+
+ Class<ProfileValueSourceConfiguration> annotationType = ProfileValueSourceConfiguration.class;
+ ProfileValueSourceConfiguration config = testClass.getAnnotation(annotationType);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Retrieved @ProfileValueSourceConfiguration [" + config + "] for test class ["
+ + testClass.getName() + "]");
+ }
+
+ Class<? extends ProfileValueSource> profileValueSourceType;
+ if (config != null) {
+ profileValueSourceType = config.value();
+ }
+ else {
+ profileValueSourceType = (Class<? extends ProfileValueSource>) AnnotationUtils.getDefaultValue(annotationType);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Retrieved ProfileValueSource type [" + profileValueSourceType + "] for class ["
+ + testClass.getName() + "]");
+ }
+
+ ProfileValueSource profileValueSource;
+ if (SystemProfileValueSource.class.equals(profileValueSourceType)) {
+ profileValueSource = SystemProfileValueSource.getInstance();
+ }
+ else {
+ try {
+ profileValueSource = profileValueSourceType.newInstance();
+ }
+ catch (Exception e) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Could not instantiate a ProfileValueSource of type [" + profileValueSourceType
+ + "] for class [" + testClass.getName() + "]: using default.", e);
+ }
+ profileValueSource = SystemProfileValueSource.getInstance();
+ }
+ }
+
+ return profileValueSource;
+ }
+
+ /**
+ * Determine if the supplied {@code testClass} is <em>enabled</em> in
+ * the current environment, as specified by the {@link IfProfileValue
+ * &#064;IfProfileValue} annotation at the class level.
+ * <p>
+ * Defaults to {@code true} if no {@link IfProfileValue
+ * &#064;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 = testClass.getAnnotation(IfProfileValue.class);
+ return isTestEnabledInThisEnvironment(retrieveProfileValueSource(testClass), ifProfileValue);
+ }
+
+ /**
+ * Determine if the supplied {@code testMethod} is <em>enabled</em> in
+ * the current environment, as specified by the {@link IfProfileValue
+ * &#064;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
+ * &#064;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
+ * environment
+ */
+ public static boolean isTestEnabledInThisEnvironment(Method testMethod, Class<?> testClass) {
+ return isTestEnabledInThisEnvironment(retrieveProfileValueSource(testClass), testMethod, testClass);
+ }
+
+ /**
+ * Determine if the supplied {@code testMethod} is <em>enabled</em> in
+ * the current environment, as specified by the {@link IfProfileValue
+ * &#064;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
+ * &#064;IfProfileValue} annotation is declared.
+ *
+ * @param profileValueSource the ProfileValueSource to use to determine if
+ * the test is enabled
+ * @param testMethod the test method
+ * @param testClass the test class
+ * @return {@code true} if the test is <em>enabled</em> in the current
+ * environment
+ */
+ public static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource, Method testMethod,
+ Class<?> testClass) {
+
+ IfProfileValue ifProfileValue = testClass.getAnnotation(IfProfileValue.class);
+ boolean classLevelEnabled = isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
+
+ if (classLevelEnabled) {
+ ifProfileValue = testMethod.getAnnotation(IfProfileValue.class);
+ return isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine if the {@code value} (or one of the {@code values})
+ * in the supplied {@link IfProfileValue &#064;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
+ * {@code null}
+ * @return {@code true} if the test is <em>enabled</em> in the current
+ * environment or if the supplied {@code ifProfileValue} is
+ * {@code null}
+ */
+ private static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource,
+ IfProfileValue ifProfileValue) {
+
+ if (ifProfileValue == null) {
+ return true;
+ }
+
+ String environmentValue = profileValueSource.get(ifProfileValue.name());
+ 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.");
+ }
+ annotatedValues = new String[] { ifProfileValue.value() };
+ }
+
+ for (String value : annotatedValues) {
+ if (ObjectUtils.nullSafeEquals(value, environmentValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/Repeat.java b/spring-test/src/main/java/org/springframework/test/annotation/Repeat.java
new file mode 100644
index 00000000..4c3dcd3a
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/Repeat.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Test annotation to indicate that a test method should be invoked repeatedly.
+ * <p />
+ * Note that the scope of execution to be repeated includes execution of the
+ * test method itself as well as any <em>set up</em> or <em>tear down</em> of
+ * the test fixture.
+ *
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @since 2.0
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Repeat {
+
+ int value() default 1;
+
+}
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
new file mode 100644
index 00000000..bb0bc88c
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/Rollback.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Test annotation to indicate whether or not the transaction for the annotated
+ * test method should be <em>rolled back</em> after the test method has
+ * completed. If {@code true}, the transaction will be rolled back;
+ * otherwise, the transaction will be committed.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Rollback {
+
+ /**
+ * Whether or not the transaction for the annotated method should be rolled
+ * back after the method has completed.
+ */
+ boolean value() default true;
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/SystemProfileValueSource.java b/spring-test/src/main/java/org/springframework/test/annotation/SystemProfileValueSource.java
new file mode 100644
index 00000000..f806acc1
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/SystemProfileValueSource.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2007 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.annotation;
+
+import org.springframework.util.Assert;
+
+/**
+ * Implementation of {@link ProfileValueSource} which uses system properties as
+ * the underlying source.
+ *
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @since 2.0
+ */
+public class SystemProfileValueSource implements ProfileValueSource {
+
+ private static final SystemProfileValueSource INSTANCE = new SystemProfileValueSource();
+
+
+ /**
+ * Obtain the canonical instance of this ProfileValueSource.
+ */
+ public static final SystemProfileValueSource getInstance() {
+ return INSTANCE;
+ }
+
+
+ /**
+ * Private constructor, enforcing the singleton pattern.
+ */
+ private SystemProfileValueSource() {
+ }
+
+ /**
+ * Get the <em>profile value</em> indicated by the specified key from the
+ * system properties.
+ * @see System#getProperty(String)
+ */
+ public String get(String key) {
+ Assert.hasText(key, "'key' must not be empty");
+ return System.getProperty(key);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/Timed.java b/spring-test/src/main/java/org/springframework/test/annotation/Timed.java
new file mode 100644
index 00000000..a78960ce
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/Timed.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Test-specific annotation to indicate that a test method has to finish
+ * execution in a {@link #millis() specified time period}.
+ * </p>
+ * <p>
+ * If the text execution takes longer than the specified time period, then the
+ * test is to be considered failed.
+ * </p>
+ * <p>
+ * Note that the time period includes execution of the test method itself, any
+ * {@link Repeat repetitions} of the test, and any <em>set up</em> or
+ * <em>tear down</em> of the test fixture.
+ * </p>
+ *
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @since 2.0
+ * @see Repeat
+ * @see AbstractAnnotationAwareTransactionalTests
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Timed {
+
+ /**
+ * The maximum amount of time (in milliseconds) that a test execution can
+ * take without being marked as failed due to taking too long.
+ */
+ long millis();
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/annotation/package-info.java b/spring-test/src/main/java/org/springframework/test/annotation/package-info.java
new file mode 100644
index 00000000..1695b36c
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/annotation/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Support classes for annotation-driven tests.
+ */
+
+package org.springframework.test.annotation;
+
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
new file mode 100644
index 00000000..39bfb5d3
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2002-2012 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.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;
+
+/**
+ * {@code ActiveProfiles} is a class-level annotation that is used to declare
+ * which <em>active bean definition profiles</em> should be used when loading
+ * an {@link org.springframework.context.ApplicationContext ApplicationContext}
+ * for test classes.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see SmartContextLoader
+ * @see MergedContextConfiguration
+ * @see ContextConfiguration
+ * @see org.springframework.context.ApplicationContext
+ * @see org.springframework.context.annotation.Profile
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ActiveProfiles {
+
+ /**
+ * Alias for {@link #profiles}.
+ *
+ * <p>This attribute may <strong>not</strong> be used in conjunction
+ * with {@link #profiles}, but it may be used <em>instead</em> of
+ * {@link #profiles}.
+ */
+ String[] value() default {};
+
+ /**
+ * The bean definition profiles to activate.
+ *
+ * <p>This attribute may <strong>not</strong> be used in conjunction
+ * with {@link #value}, but it may be used <em>instead</em> of
+ * {@link #value}.
+ */
+ String[] profiles() default {};
+
+ /**
+ * Whether or not bean definition profiles from superclasses should be
+ * <em>inherited</em>.
+ *
+ * <p>The default value is {@code true}, which means that a test
+ * class will <em>inherit</em> bean definition profiles defined by a
+ * test superclass. Specifically, the bean definition profiles for a test
+ * class will be appended to the list of bean definition profiles
+ * defined by a test superclass. Thus, subclasses have the option of
+ * <em>extending</em> the list of bean definition profiles.
+ *
+ * <p>If {@code inheritProfiles} is set to {@code false}, the bean
+ * definition profiles for the test class will <em>shadow</em> and
+ * effectively replace any bean definition profiles defined by a superclass.
+ *
+ * <p>In the following example, the {@code ApplicationContext} for
+ * {@code BaseTest} will be loaded using only the &quot;base&quot;
+ * bean definition profile; beans defined in the &quot;extended&quot; profile
+ * will therefore not be loaded. In contrast, the {@code ApplicationContext}
+ * for {@code ExtendedTest} will be loaded using the &quot;base&quot;
+ * <strong>and</strong> &quot;extended&quot; bean definition profiles.
+ * <pre class="code">
+ * &#064;ActiveProfiles(&quot;base&quot;)
+ * &#064;ContextConfiguration
+ * public class BaseTest {
+ * // ...
+ * }
+ *
+ * &#064;ActiveProfiles(&quot;extended&quot;)
+ * &#064;ContextConfiguration
+ * public class ExtendedTest extends BaseTest {
+ * // ...
+ * }
+ * </pre>
+ *
+ * <p>Note: {@code @ActiveProfiles} can be used when loading an
+ * {@code ApplicationContext} from path-based resource locations or
+ * annotated classes.
+ *
+ * @see ContextConfiguration#locations
+ * @see ContextConfiguration#classes
+ * @see ContextConfiguration#inheritLocations
+ */
+ boolean inheritProfiles() default true;
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java
new file mode 100644
index 00000000..507d2555
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java
@@ -0,0 +1,121 @@
+/*
+ * 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.context;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.util.Assert;
+
+/**
+ * {@code CacheAwareContextLoaderDelegate} loads application contexts from
+ * {@link MergedContextConfiguration} by delegating to the
+ * {@link ContextLoader} configured in the {@code MergedContextConfiguration}
+ * and interacting transparently with the {@link ContextCache} behind the scenes.
+ *
+ * <p>Note: {@code CacheAwareContextLoaderDelegate} does not implement the
+ * {@link ContextLoader} or {@link SmartContextLoader} interface.
+ *
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+public class CacheAwareContextLoaderDelegate {
+
+ private static final Log logger = LogFactory.getLog(CacheAwareContextLoaderDelegate.class);
+
+ private static final Log statsLogger = LogFactory.getLog("org.springframework.test.context.cache");
+
+ private final ContextCache contextCache;
+
+
+ CacheAwareContextLoaderDelegate(ContextCache contextCache) {
+ Assert.notNull(contextCache, "ContextCache must not be null");
+ this.contextCache = contextCache;
+ }
+
+
+ /**
+ * Load the {@code ApplicationContext} for the supplied merged context
+ * configuration. Supports both the {@link SmartContextLoader} and
+ * {@link ContextLoader} SPIs.
+ * @throws Exception if an error occurs while loading the application context
+ */
+ private ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
+ throws Exception {
+
+ ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
+ Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. " +
+ "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
+
+ ApplicationContext applicationContext;
+
+ if (contextLoader instanceof SmartContextLoader) {
+ SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
+ applicationContext = smartContextLoader.loadContext(mergedContextConfiguration);
+ }
+ else {
+ String[] locations = mergedContextConfiguration.getLocations();
+ Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. " +
+ "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
+ applicationContext = contextLoader.loadContext(locations);
+ }
+
+ return applicationContext;
+ }
+
+ /**
+ * Load the {@link ApplicationContext application context} for the supplied
+ * merged context configuration.
+ * <p>If the context is present in the cache it will simply be returned;
+ * otherwise, it will be loaded, stored in the cache, and returned.
+ * @return the application context
+ * @throws IllegalStateException if an error occurs while retrieving or
+ * loading the application context
+ */
+ public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) {
+ synchronized (this.contextCache) {
+ ApplicationContext context = this.contextCache.get(mergedContextConfiguration);
+ if (context == null) {
+ try {
+ context = loadContextInternal(mergedContextConfiguration);
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Storing ApplicationContext in cache under key [%s]",
+ mergedContextConfiguration));
+ }
+ this.contextCache.put(mergedContextConfiguration, context);
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException("Failed to load ApplicationContext", ex);
+ }
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Retrieved ApplicationContext from cache with key [%s]",
+ mergedContextConfiguration));
+ }
+ }
+
+ if (statsLogger.isDebugEnabled()) {
+ statsLogger.debug("Spring test ApplicationContext cache statistics: " + this.contextCache);
+ }
+
+ return context;
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextCache.java b/spring-test/src/main/java/org/springframework/test/context/ContextCache.java
new file mode 100644
index 00000000..576021bd
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextCache.java
@@ -0,0 +1,256 @@
+/*
+ * 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.context;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.style.ToStringCreator;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
+import org.springframework.util.Assert;
+
+/**
+ * Cache for Spring {@link ApplicationContext ApplicationContexts} in a test environment.
+ *
+ * <p>Maintains a cache of {@code ApplicationContexts} keyed by
+ * {@link MergedContextConfiguration} instances.
+ *
+ * <p>This has significant performance benefits if initializing the context would take time.
+ * While initializing a Spring context itself is very quick, some beans in a context, such
+ * as a {@code LocalSessionFactoryBean} for working with Hibernate, may take some time to
+ * initialize. Hence it often makes sense to perform that initialization only once per
+ * test suite.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+class ContextCache {
+
+ /**
+ * Map of context keys to Spring {@code ApplicationContext} instances.
+ */
+ private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
+ new ConcurrentHashMap<MergedContextConfiguration, ApplicationContext>(64);
+
+ /**
+ * Map of parent keys to sets of children keys, representing a top-down <em>tree</em>
+ * of context hierarchies. This information is used for determining which subtrees
+ * need to be recursively removed and closed when removing a context that is a parent
+ * of other contexts.
+ */
+ private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap =
+ new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(64);
+
+ private final AtomicInteger hitCount = new AtomicInteger();
+
+ private final AtomicInteger missCount = new AtomicInteger();
+
+
+ /**
+ * Clear all contexts from the cache and clears context hierarchy information as well.
+ */
+ public void clear() {
+ this.contextMap.clear();
+ this.hierarchyMap.clear();
+ }
+
+ /**
+ * Clear hit and miss count statistics for the cache (i.e., resets counters to zero).
+ */
+ public void clearStatistics() {
+ this.hitCount.set(0);
+ this.missCount.set(0);
+ }
+
+ /**
+ * Return whether there is a cached context for the given key.
+ * @param key the context key (never {@code null})
+ */
+ public boolean contains(MergedContextConfiguration key) {
+ Assert.notNull(key, "Key must not be null");
+ return this.contextMap.containsKey(key);
+ }
+
+ /**
+ * Obtain a cached {@code ApplicationContext} for the given key.
+ * <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts will
+ * be updated accordingly.
+ * @param key the context key (never {@code null})
+ * @return the corresponding {@code ApplicationContext} instance, or {@code null}
+ * if not found in the cache
+ * @see #remove
+ */
+ public ApplicationContext get(MergedContextConfiguration key) {
+ Assert.notNull(key, "Key must not be null");
+ ApplicationContext context = this.contextMap.get(key);
+ if (context == null) {
+ this.missCount.incrementAndGet();
+ }
+ else {
+ this.hitCount.incrementAndGet();
+ }
+ return context;
+ }
+
+ /**
+ * Get the overall hit count for this cache.
+ * <p>A <em>hit</em> is an access to the cache, which returned a non-null context
+ * for a queried key.
+ */
+ public int getHitCount() {
+ return this.hitCount.get();
+ }
+
+ /**
+ * Get the overall miss count for this cache.
+ * <p>A <em>miss</em> is an access to the cache, which returned a {@code null} context
+ * for a queried key.
+ */
+ public int getMissCount() {
+ return this.missCount.get();
+ }
+
+ /**
+ * Explicitly add an {@code ApplicationContext} instance to the cache under the given key.
+ * @param key the context key (never {@code null})
+ * @param context the {@code ApplicationContext} instance (never {@code null})
+ */
+ public void put(MergedContextConfiguration key, ApplicationContext context) {
+ Assert.notNull(key, "Key must not be null");
+ Assert.notNull(context, "ApplicationContext must not be null");
+
+ this.contextMap.put(key, context);
+ MergedContextConfiguration child = key;
+ MergedContextConfiguration parent = child.getParent();
+ while (parent != null) {
+ Set<MergedContextConfiguration> list = this.hierarchyMap.get(parent);
+ if (list == null) {
+ list = new HashSet<MergedContextConfiguration>();
+ this.hierarchyMap.put(parent, list);
+ }
+ list.add(child);
+ child = parent;
+ parent = child.getParent();
+ }
+ }
+
+ /**
+ * Remove the context with the given key from the cache and explicitly
+ * {@linkplain ConfigurableApplicationContext#close() close} it if it is an
+ * instance of {@link ConfigurableApplicationContext}.
+ * <p>Generally speaking, you would only call this method if you change the
+ * state of a singleton bean, potentially affecting future interaction with
+ * the context.
+ * <p>In addition, the semantics of the supplied {@code HierarchyMode} will
+ * be honored. See the Javadoc for {@link HierarchyMode} for details.
+ * @param key the context key; never {@code null}
+ * @param hierarchyMode the hierarchy mode; may be {@code null} if the context
+ * is not part of a hierarchy
+ */
+ public void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
+ Assert.notNull(key, "Key must not be null");
+
+ // startKey is the level at which to begin clearing the cache, depending
+ // on the configured hierarchy mode.
+ MergedContextConfiguration startKey = key;
+ if (hierarchyMode == HierarchyMode.EXHAUSTIVE) {
+ while (startKey.getParent() != null) {
+ startKey = startKey.getParent();
+ }
+ }
+
+ List<MergedContextConfiguration> removedContexts = new ArrayList<MergedContextConfiguration>();
+ remove(removedContexts, startKey);
+
+ // Remove all remaining references to any removed contexts from the
+ // hierarchy map.
+ for (MergedContextConfiguration currentKey : removedContexts) {
+ for (Set<MergedContextConfiguration> children : this.hierarchyMap.values()) {
+ children.remove(currentKey);
+ }
+ }
+
+ // Remove empty entries from the hierarchy map.
+ for (MergedContextConfiguration currentKey : this.hierarchyMap.keySet()) {
+ if (this.hierarchyMap.get(currentKey).isEmpty()) {
+ this.hierarchyMap.remove(currentKey);
+ }
+ }
+ }
+
+ private void remove(List<MergedContextConfiguration> removedContexts, MergedContextConfiguration key) {
+ Assert.notNull(key, "Key must not be null");
+
+ Set<MergedContextConfiguration> children = this.hierarchyMap.get(key);
+ if (children != null) {
+ for (MergedContextConfiguration child : children) {
+ // Recurse through lower levels
+ remove(removedContexts, child);
+ }
+ // Remove the set of children for the current context from the hierarchy map.
+ this.hierarchyMap.remove(key);
+ }
+
+ // Physically remove and close leaf nodes first (i.e., on the way back up the
+ // stack as opposed to prior to the recursive call).
+ ApplicationContext context = this.contextMap.remove(key);
+ if (context instanceof ConfigurableApplicationContext) {
+ ((ConfigurableApplicationContext) context).close();
+ }
+ removedContexts.add(key);
+ }
+
+ /**
+ * Determine the number of contexts currently stored in the cache. If the cache
+ * contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
+ * <tt>Integer.MAX_VALUE</tt>.
+ */
+ public int size() {
+ return this.contextMap.size();
+ }
+
+ /**
+ * Determine the number of parent contexts currently tracked within the cache.
+ */
+ public int getParentContextCount() {
+ return this.hierarchyMap.size();
+ }
+
+ /**
+ * Generates a text string, which contains the {@linkplain #size() size} as well
+ * as the {@linkplain #getHitCount() hit}, {@linkplain #getMissCount() miss},
+ * and {@linkplain #getParentContextCount() parent context} counts.
+ */
+ @Override
+ public String toString() {
+ return new ToStringCreator(this)
+ .append("size", size())
+ .append("hitCount", getHitCount())
+ .append("missCount", getMissCount())
+ .append("parentContextCount", getParentContextCount())
+ .toString();
+ }
+
+}
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
new file mode 100644
index 00000000..7033765c
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+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;
+
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * {@code @ContextConfiguration} defines class-level metadata that is
+ * used to determine how to load and configure an
+ * {@link org.springframework.context.ApplicationContext ApplicationContext}
+ * for integration tests.
+ *
+ * <h3>Supported Resource Types</h3>
+ *
+ * <p>Prior to Spring 3.1, only path-based resource locations were supported.
+ * As of Spring 3.1, {@linkplain #loader context loaders} may choose to support
+ * either path-based or class-based resources (but not both). Consequently
+ * {@code @ContextConfiguration} can be used to declare either path-based
+ * resource locations (via the {@link #locations} or {@link #value}
+ * attribute) <i>or</i> annotated classes (via the {@link #classes}
+ * attribute).
+ *
+ * <h3>Annotated Classes</h3>
+ *
+ * <p>The term <em>annotated class</em> can refer to any of the following.
+ *
+ * <ul>
+ * <li>A class annotated with
+ * {@link org.springframework.context.annotation.Configuration @Configuration}</li>
+ * <li>A component (i.e., a class annotated with
+ * {@link org.springframework.stereotype.Component @Component},
+ * {@link org.springframework.stereotype.Service @Service},
+ * {@link org.springframework.stereotype.Repository @Repository}, etc.)</li>
+ * <li>A JSR-330 compliant class that is annotated with {@code javax.inject} annotations</li>
+ * <li>Any other class that contains
+ * {@link org.springframework.context.annotation.Bean @Bean}-methods</li>
+ * </ul>
+ *
+ * <p>Consult the Javadoc for
+ * {@link org.springframework.context.annotation.Configuration @Configuration} and
+ * {@link org.springframework.context.annotation.Bean @Bean}
+ * for further information regarding the configuration and semantics of
+ * <em>annotated classes</em>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see ContextHierarchy
+ * @see ActiveProfiles
+ * @see ContextLoader
+ * @see SmartContextLoader
+ * @see ContextConfigurationAttributes
+ * @see MergedContextConfiguration
+ * @see org.springframework.context.ApplicationContext
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ContextConfiguration {
+
+ /**
+ * Alias for {@link #locations}.
+ *
+ * <p>This attribute may <strong>not</strong> be used in conjunction
+ * with {@link #locations} or {@link #classes}, but it may be used
+ * instead of {@link #locations}.
+ * @since 3.0
+ * @see #inheritLocations
+ */
+ String[] value() default {};
+
+ /**
+ * The resource locations to use for loading an
+ * {@link org.springframework.context.ApplicationContext ApplicationContext}.
+ *
+ * <p>Check out the Javadoc for
+ * {@link org.springframework.test.context.support.AbstractContextLoader#modifyLocations
+ * AbstractContextLoader.modifyLocations()} for details on how a location
+ * String will be interpreted at runtime, in particular in case of a relative
+ * path. Also, check out the documentation on
+ * {@link org.springframework.test.context.support.AbstractContextLoader#generateDefaultLocations
+ * AbstractContextLoader.generateDefaultLocations()} for details on the default
+ * locations that are going to be used if none are specified.
+ *
+ * <p>Note that the above-mentioned default rules only apply for a standard
+ * {@link org.springframework.test.context.support.AbstractContextLoader
+ * AbstractContextLoader} subclass such as
+ * {@link org.springframework.test.context.support.GenericXmlContextLoader
+ * GenericXmlContextLoader} which is the effective default implementation
+ * used at runtime if {@code locations} are configured. See the
+ * documentation for {@link #loader} for further details regarding default
+ * loaders.
+ *
+ * <p>This attribute may <strong>not</strong> be used in conjunction with
+ * {@link #value} or {@link #classes}, but it may be used instead of
+ * {@link #value}.
+ * @since 2.5
+ * @see #inheritLocations
+ */
+ String[] locations() default {};
+
+ /**
+ * The <em>annotated classes</em> to use for loading an
+ * {@link org.springframework.context.ApplicationContext ApplicationContext}.
+ *
+ * <p>Check out the Javadoc for
+ * {@link org.springframework.test.context.support.AnnotationConfigContextLoader#detectDefaultConfigurationClasses
+ * AnnotationConfigContextLoader.detectDefaultConfigurationClasses()} for details
+ * on how default configuration classes will be detected if no
+ * <em>annotated classes</em> are specified. See the documentation for
+ * {@link #loader} for further details regarding default loaders.
+ *
+ * <p>This attribute may <strong>not</strong> be used in conjunction with
+ * {@link #locations} or {@link #value}.
+ *
+ * @since 3.1
+ * @see org.springframework.context.annotation.Configuration
+ * @see org.springframework.test.context.support.AnnotationConfigContextLoader
+ * @see #inheritLocations
+ */
+ Class<?>[] classes() default {};
+
+ /**
+ * The application context <em>initializer classes</em> to use for initializing
+ * a {@link ConfigurableApplicationContext}.
+ *
+ * <p>The concrete {@code ConfigurableApplicationContext} type supported by each
+ * declared initializer must be compatible with the type of {@code ApplicationContext}
+ * created by the {@link SmartContextLoader} in use.
+ *
+ * <p>{@code SmartContextLoader} implementations typically detect whether
+ * Spring's {@link org.springframework.core.Ordered Ordered} interface has been
+ * implemented or if the @{@link org.springframework.core.annotation.Order Order}
+ * annotation is present and sort instances accordingly prior to invoking them.
+ *
+ * @since 3.2
+ * @see org.springframework.context.ApplicationContextInitializer
+ * @see org.springframework.context.ConfigurableApplicationContext
+ * @see #inheritInitializers
+ * @see #loader
+ */
+ Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] initializers() default {};
+
+ /**
+ * Whether or not {@link #locations resource locations} or <em>annotated
+ * classes</em> from test superclasses should be <em>inherited</em>.
+ *
+ * <p>The default value is {@code true}. This means that an annotated
+ * class will <em>inherit</em> the resource locations or annotated classes
+ * defined by test superclasses. Specifically, the resource locations or
+ * annotated classes for a given test class will be appended to the list of
+ * resource locations or annotated classes defined by test superclasses.
+ * Thus, subclasses have the option of <em>extending</em> the list of resource
+ * locations or annotated classes.
+ *
+ * <p>If {@code inheritLocations} is set to {@code false}, the
+ * resource locations or annotated classes for the annotated class
+ * will <em>shadow</em> and effectively replace any resource locations
+ * or annotated classes defined by superclasses.
+ *
+ * <p>In the following example that uses path-based resource locations, the
+ * {@link org.springframework.context.ApplicationContext ApplicationContext}
+ * for {@code ExtendedTest} will be loaded from
+ * &quot;base-context.xml&quot; <strong>and</strong>
+ * &quot;extended-context.xml&quot;, in that order. Beans defined in
+ * &quot;extended-context.xml&quot; may therefore override those defined in
+ * &quot;base-context.xml&quot;.
+ * <pre class="code">
+ * &#064;ContextConfiguration(&quot;base-context.xml&quot;)
+ * public class BaseTest {
+ * // ...
+ * }
+ *
+ * &#064;ContextConfiguration(&quot;extended-context.xml&quot;)
+ * public class ExtendedTest extends BaseTest {
+ * // ...
+ * }
+ * </pre>
+ *
+ * <p>Similarly, in the following example that uses annotated
+ * classes, the
+ * {@link org.springframework.context.ApplicationContext ApplicationContext}
+ * for {@code ExtendedTest} will be loaded from the
+ * {@code BaseConfig} <strong>and</strong> {@code ExtendedConfig}
+ * configuration classes, in that order. Beans defined in
+ * {@code ExtendedConfig} may therefore override those defined in
+ * {@code BaseConfig}.
+ * <pre class="code">
+ * &#064;ContextConfiguration(classes=BaseConfig.class)
+ * public class BaseTest {
+ * // ...
+ * }
+ *
+ * &#064;ContextConfiguration(classes=ExtendedConfig.class)
+ * public class ExtendedTest extends BaseTest {
+ * // ...
+ * }
+ * </pre>
+ * @since 2.5
+ */
+ boolean inheritLocations() default true;
+
+ /**
+ * Whether or not {@linkplain #initializers context initializers} from test
+ * superclasses should be <em>inherited</em>.
+ *
+ * <p>The default value is {@code true}. This means that an annotated
+ * class will <em>inherit</em> the application context initializers defined
+ * by test superclasses. Specifically, the initializers for a given test
+ * class will be added to the set of initializers defined by test
+ * superclasses. Thus, subclasses have the option of <em>extending</em> the
+ * set of initializers.
+ *
+ * <p>If {@code inheritInitializers} is set to {@code false}, the
+ * initializers for the annotated class will <em>shadow</em> and effectively
+ * replace any initializers defined by superclasses.
+ *
+ * <p>In the following example, the
+ * {@link org.springframework.context.ApplicationContext ApplicationContext}
+ * for {@code ExtendedTest} will be initialized using
+ * {@code BaseInitializer} <strong>and</strong> {@code ExtendedInitializer}.
+ * Note, however, that the order in which the initializers are invoked
+ * depends on whether they implement {@link org.springframework.core.Ordered
+ * Ordered} or are annotated with {@link org.springframework.core.annotation.Order
+ * &#064;Order}.
+ * <pre class="code">
+ * &#064;ContextConfiguration(initializers = BaseInitializer.class)
+ * public class BaseTest {
+ * // ...
+ * }
+ *
+ * &#064;ContextConfiguration(initializers = ExtendedInitializer.class)
+ * public class ExtendedTest extends BaseTest {
+ * // ...
+ * }
+ * </pre>
+ * @since 3.2
+ */
+ boolean inheritInitializers() default true;
+
+ /**
+ * The type of {@link SmartContextLoader} (or {@link ContextLoader}) to use
+ * for loading an {@link org.springframework.context.ApplicationContext
+ * ApplicationContext}.
+ *
+ * <p>If not specified, the loader will be inherited from the first superclass
+ * that is annotated with {@code @ContextConfiguration} and specifies an
+ * explicit loader. If no class in the hierarchy specifies an explicit
+ * loader, a default loader will be used instead.
+ *
+ * <p>The default concrete implementation chosen at runtime will be either
+ * {@link org.springframework.test.context.support.DelegatingSmartContextLoader
+ * DelegatingSmartContextLoader} or
+ * {@link org.springframework.test.context.web.WebDelegatingSmartContextLoader
+ * WebDelegatingSmartContextLoader} depending on the absence or presence of
+ * {@link org.springframework.test.context.web.WebAppConfiguration
+ * &#064;WebAppConfiguration}. For further details on the default behavior
+ * of various concrete {@code SmartContextLoaders}, check out the Javadoc for
+ * {@link org.springframework.test.context.support.AbstractContextLoader
+ * AbstractContextLoader},
+ * {@link org.springframework.test.context.support.GenericXmlContextLoader
+ * GenericXmlContextLoader},
+ * {@link org.springframework.test.context.support.AnnotationConfigContextLoader
+ * AnnotationConfigContextLoader},
+ * {@link org.springframework.test.context.web.GenericXmlWebContextLoader
+ * GenericXmlWebContextLoader}, and
+ * {@link org.springframework.test.context.web.AnnotationConfigWebContextLoader
+ * AnnotationConfigWebContextLoader}.
+ *
+ * @since 2.5
+ */
+ Class<? extends ContextLoader> loader() default ContextLoader.class;
+
+ /**
+ * The name of the context hierarchy level represented by this configuration.
+ *
+ * <p>If not specified the name will be inferred based on the numerical level
+ * within all declared contexts within the hierarchy.
+ *
+ * <p>This attribute is only applicable when used within a test class hierarchy
+ * that is configured using {@code @ContextHierarchy}, in which case the name
+ * can be used for <em>merging</em> or <em>overriding</em> this configuration
+ * with configuration of the same name in hierarchy levels defined in superclasses.
+ * See the Javadoc for {@link ContextHierarchy @ContextHierarchy} for details.
+ *
+ * @since 3.2.2
+ */
+ String name() default "";
+
+}
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
new file mode 100644
index 00000000..16c724a8
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfigurationAttributes.java
@@ -0,0 +1,420 @@
+/*
+ * 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.context;
+
+import java.util.Arrays;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.style.ToStringCreator;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@code ContextConfigurationAttributes} encapsulates the context configuration
+ * attributes declared via {@link ContextConfiguration @ContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see ContextConfiguration
+ * @see SmartContextLoader#processContextConfiguration(ContextConfigurationAttributes)
+ * @see MergedContextConfiguration
+ */
+public class ContextConfigurationAttributes {
+
+ private static final Log logger = LogFactory.getLog(ContextConfigurationAttributes.class);
+
+ private final Class<?> declaringClass;
+
+ private Class<?>[] classes;
+
+ private String[] locations;
+
+ private final boolean inheritLocations;
+
+ private final Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] initializers;
+
+ private final boolean inheritInitializers;
+
+ private final String name;
+
+ private final Class<? extends ContextLoader> contextLoaderClass;
+
+
+ /**
+ * Construct a new {@link ContextConfigurationAttributes} instance for the
+ * supplied {@link ContextConfiguration @ContextConfiguration} annotation and
+ * the {@linkplain Class test class} that declared it.
+ * @param declaringClass the test class that declared {@code @ContextConfiguration}
+ * @param contextConfiguration the annotation from which to retrieve the attributes
+ */
+ public ContextConfigurationAttributes(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
+ this(declaringClass, resolveLocations(declaringClass, contextConfiguration), contextConfiguration.classes(),
+ contextConfiguration.inheritLocations(), contextConfiguration.initializers(),
+ contextConfiguration.inheritInitializers(), contextConfiguration.name(), contextConfiguration.loader());
+ }
+
+ /**
+ * Construct a new {@link ContextConfigurationAttributes} instance for the
+ * supplied {@link AnnotationAttributes} (parsed from a
+ * {@link ContextConfiguration @ContextConfiguration} annotation) and
+ * the {@linkplain Class test class} that declared them.
+ * @param declaringClass the test class that declared {@code @ContextConfiguration}
+ * @param annAttrs the annotation attributes from which to retrieve the attributes
+ */
+ @SuppressWarnings("unchecked")
+ public ContextConfigurationAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) {
+ this(declaringClass,
+ resolveLocations(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getStringArray("value")),
+ 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"));
+ }
+
+ /**
+ * Construct a new {@link ContextConfigurationAttributes} instance for the
+ * {@linkplain Class test class} that declared the
+ * {@link ContextConfiguration @ContextConfiguration} annotation and its
+ * corresponding attributes.
+ * @param declaringClass the test class that declared {@code @ContextConfiguration}
+ * @param locations the resource locations declared via {@code @ContextConfiguration}
+ * @param classes the annotated classes declared via {@code @ContextConfiguration}
+ * @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration}
+ * @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
+ * @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
+ * {@code null}
+ * @deprecated as of Spring 3.2, use
+ * {@link #ContextConfigurationAttributes(Class, String[], Class[], boolean, Class[], boolean, String, Class)}
+ * instead
+ */
+ @Deprecated
+ public ContextConfigurationAttributes(Class<?> declaringClass, String[] locations, Class<?>[] classes,
+ boolean inheritLocations, Class<? extends ContextLoader> contextLoaderClass) {
+
+ this(declaringClass, locations, classes, inheritLocations, null, true, null, contextLoaderClass);
+ }
+
+ /**
+ * Construct a new {@link ContextConfigurationAttributes} instance for the
+ * {@linkplain Class test class} that declared the
+ * {@link ContextConfiguration @ContextConfiguration} annotation and its
+ * corresponding attributes.
+ * @param declaringClass the test class that declared {@code @ContextConfiguration}
+ * @param locations the resource locations declared via {@code @ContextConfiguration}
+ * @param classes the annotated classes declared via {@code @ContextConfiguration}
+ * @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration}
+ * @param initializers the context initializers declared via {@code @ContextConfiguration}
+ * @param inheritInitializers the {@code inheritInitializers} flag declared via {@code @ContextConfiguration}
+ * @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
+ * @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
+ * {@code null}
+ */
+ public ContextConfigurationAttributes(
+ Class<?> declaringClass, String[] locations, Class<?>[] classes, boolean inheritLocations,
+ Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] initializers,
+ boolean inheritInitializers, Class<? extends ContextLoader> contextLoaderClass) {
+
+ this(declaringClass, locations, classes, inheritLocations, initializers, inheritInitializers, null,
+ contextLoaderClass);
+ }
+
+ /**
+ * Construct a new {@link ContextConfigurationAttributes} instance for the
+ * {@linkplain Class test class} that declared the
+ * {@link ContextConfiguration @ContextConfiguration} annotation and its
+ * corresponding attributes.
+ * @param declaringClass the test class that declared {@code @ContextConfiguration}
+ * @param locations the resource locations declared via {@code @ContextConfiguration}
+ * @param classes the annotated classes declared via {@code @ContextConfiguration}
+ * @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration}
+ * @param initializers the context initializers declared via {@code @ContextConfiguration}
+ * @param inheritInitializers the {@code inheritInitializers} flag declared via {@code @ContextConfiguration}
+ * @param name the name of level in the context hierarchy, or {@code null} if not applicable
+ * @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
+ * @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
+ * {@code null}
+ */
+ public ContextConfigurationAttributes(
+ Class<?> declaringClass, String[] locations, Class<?>[] classes, boolean inheritLocations,
+ Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] initializers,
+ boolean inheritInitializers, String name, Class<? extends ContextLoader> contextLoaderClass) {
+
+ Assert.notNull(declaringClass, "declaringClass must not be null");
+ Assert.notNull(contextLoaderClass, "contextLoaderClass must not be null");
+
+ 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.",
+ declaringClass.getName(), ObjectUtils.nullSafeToString(locations),
+ ObjectUtils.nullSafeToString(classes)));
+ }
+
+ this.declaringClass = declaringClass;
+ this.locations = locations;
+ this.classes = classes;
+ this.inheritLocations = inheritLocations;
+ this.initializers = initializers;
+ this.inheritInitializers = inheritInitializers;
+ this.name = (StringUtils.hasText(name) ? name : null);
+ this.contextLoaderClass = contextLoaderClass;
+ }
+
+
+ /**
+ * Resolve resource locations from the {@link ContextConfiguration#locations() locations}
+ * and {@link ContextConfiguration#value() value} attributes of the supplied
+ * {@link ContextConfiguration} annotation.
+ * @throws IllegalStateException if both the locations and value attributes have been declared
+ */
+ private static String[] resolveLocations(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
+ return resolveLocations(declaringClass, contextConfiguration.locations(), contextConfiguration.value());
+ }
+
+ /**
+ * Resolve resource locations from the supplied {@code locations} and
+ * {@code value} arrays, which correspond to attributes of the same names in
+ * the {@link ContextConfiguration} annotation.
+ * @throws IllegalStateException if both the locations and value attributes have been declared
+ */
+ private static String[] resolveLocations(Class<?> declaringClass, String[] locations, String[] value) {
+ Assert.notNull(declaringClass, "declaringClass must not be null");
+ if (!ObjectUtils.isEmpty(value) && !ObjectUtils.isEmpty(locations)) {
+ throw new IllegalStateException(String.format("Test class [%s] has been configured with " +
+ "@ContextConfiguration's 'value' %s and 'locations' %s attributes. Only one declaration " +
+ "of resource locations is permitted per @ContextConfiguration annotation.",
+ declaringClass.getName(), ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(locations)));
+ }
+ else if (!ObjectUtils.isEmpty(value)) {
+ locations = value;
+ }
+ return locations;
+ }
+
+
+ /**
+ * Get the {@linkplain Class class} that declared the
+ * {@link ContextConfiguration @ContextConfiguration} annotation.
+ * @return the declaring class (never {@code null})
+ */
+ public Class<?> getDeclaringClass() {
+ return this.declaringClass;
+ }
+
+ /**
+ * Set the <em>processed</em> annotated classes, effectively overriding the
+ * original value declared via {@link ContextConfiguration @ContextConfiguration}.
+ * @see #getClasses()
+ */
+ public void setClasses(Class<?>... classes) {
+ this.classes = classes;
+ }
+
+ /**
+ * Get the annotated classes that were declared via
+ * {@link ContextConfiguration @ContextConfiguration}.
+ * <p>Note: this is a mutable property. The returned value may therefore
+ * represent a <em>processed</em> value that does not match the original value
+ * declared via {@link ContextConfiguration @ContextConfiguration}.
+ * @return the annotated classes; potentially {@code null} or <em>empty</em>
+ * @see ContextConfiguration#classes
+ * @see #setClasses(Class[])
+ */
+ public Class<?>[] getClasses() {
+ return this.classes;
+ }
+
+ /**
+ * Determine if this {@code ContextConfigurationAttributes} instance has
+ * class-based resources.
+ * @return {@code true} if the {@link #getClasses() classes} array is not empty
+ * @see #hasResources()
+ * @see #hasLocations()
+ */
+ public boolean hasClasses() {
+ return !ObjectUtils.isEmpty(getClasses());
+ }
+
+ /**
+ * Set the <em>processed</em> resource locations, effectively overriding the
+ * original value declared via {@link ContextConfiguration @ContextConfiguration}.
+ * @see #getLocations()
+ */
+ public void setLocations(String... locations) {
+ this.locations = locations;
+ }
+
+ /**
+ * Get the resource locations that were declared via
+ * {@link ContextConfiguration @ContextConfiguration}.
+ * <p>Note: this is a mutable property. The returned value may therefore
+ * represent a <em>processed</em> value that does not match the original value
+ * declared via {@link ContextConfiguration @ContextConfiguration}.
+ * @return the resource locations; potentially {@code null} or <em>empty</em>
+ * @see ContextConfiguration#value
+ * @see ContextConfiguration#locations
+ * @see #setLocations(String[])
+ */
+ public String[] getLocations() {
+ return this.locations;
+ }
+
+ /**
+ * Determine if this {@code ContextConfigurationAttributes} instance has
+ * path-based resource locations.
+ * @return {@code true} if the {@link #getLocations() locations} array is not empty
+ * @see #hasResources()
+ * @see #hasClasses()
+ */
+ public boolean hasLocations() {
+ return !ObjectUtils.isEmpty(getLocations());
+ }
+
+ /**
+ * Determine if this {@code ContextConfigurationAttributes} instance has
+ * either path-based resource locations or class-based resources.
+ * @return {@code true} if either the {@link #getLocations() locations}
+ * or the {@link #getClasses() classes} array is not empty
+ * @see #hasLocations()
+ * @see #hasClasses()
+ */
+ public boolean hasResources() {
+ return (hasLocations() || hasClasses());
+ }
+
+ /**
+ * Get the {@code inheritLocations} flag that was declared via
+ * {@link ContextConfiguration @ContextConfiguration}.
+ * @return the {@code inheritLocations} flag
+ * @see ContextConfiguration#inheritLocations
+ */
+ public boolean isInheritLocations() {
+ return this.inheritLocations;
+ }
+
+ /**
+ * Get the {@code ApplicationContextInitializer} classes that were declared via
+ * {@link ContextConfiguration @ContextConfiguration}.
+ * @return the {@code ApplicationContextInitializer} classes
+ * @since 3.2
+ */
+ public Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] getInitializers() {
+ return this.initializers;
+ }
+
+ /**
+ * Get the {@code inheritInitializers} flag that was declared via
+ * {@link ContextConfiguration @ContextConfiguration}.
+ * @return the {@code inheritInitializers} flag
+ * @since 3.2
+ */
+ public boolean isInheritInitializers() {
+ return this.inheritInitializers;
+ }
+
+ /**
+ * Get the name of the context hierarchy level that was declared via
+ * {@link ContextConfiguration @ContextConfiguration}.
+ * @return the name of the context hierarchy level or {@code null} if not applicable
+ * @see ContextConfiguration#name()
+ * @since 3.2.2
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Get the {@code ContextLoader} class that was declared via
+ * {@link ContextConfiguration @ContextConfiguration}.
+ * @return the {@code ContextLoader} class
+ * @see ContextConfiguration#loader
+ */
+ public Class<? extends ContextLoader> getContextLoaderClass() {
+ return this.contextLoaderClass;
+ }
+
+
+ /**
+ * Determine if the supplied object is equal to this
+ * {@code ContextConfigurationAttributes} instance by comparing both object's
+ * {@linkplain #getDeclaringClass() declaring class},
+ * {@linkplain #getLocations() locations},
+ * {@linkplain #getClasses() annotated classes},
+ * {@linkplain #isInheritLocations() inheritLocations flag},
+ * {@linkplain #getInitializers() context initializer classes},
+ * {@linkplain #isInheritInitializers() inheritInitializers flag}, and the
+ * {@link #getContextLoaderClass() ContextLoader class}.
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof ContextConfigurationAttributes)) {
+ return false;
+ }
+ ContextConfigurationAttributes otherAttr = (ContextConfigurationAttributes) other;
+ return (ObjectUtils.nullSafeEquals(this.declaringClass, otherAttr.declaringClass) &&
+ Arrays.equals(this.classes, otherAttr.classes)) &&
+ Arrays.equals(this.locations, otherAttr.locations) &&
+ this.inheritLocations == otherAttr.inheritLocations &&
+ Arrays.equals(this.initializers, otherAttr.initializers) &&
+ this.inheritInitializers == otherAttr.inheritInitializers &&
+ ObjectUtils.nullSafeEquals(this.name, otherAttr.name) &&
+ ObjectUtils.nullSafeEquals(this.contextLoaderClass, otherAttr.contextLoaderClass);
+ }
+
+ /**
+ * Generate a unique hash code for all properties of this
+ * {@code ContextConfigurationAttributes} instance excluding the
+ * {@linkplain #getName() name}.
+ */
+ @Override
+ public int hashCode() {
+ int result = this.declaringClass.hashCode();
+ result = 31 * result + Arrays.hashCode(this.classes);
+ result = 31 * result + Arrays.hashCode(this.locations);
+ result = 31 * result + Arrays.hashCode(this.initializers);
+ return result;
+ }
+
+ /**
+ * Provide a String representation of the context configuration attributes
+ * and declaring class.
+ */
+ @Override
+ public String toString() {
+ return new ToStringCreator(this)
+ .append("declaringClass", this.declaringClass.getName())
+ .append("classes", ObjectUtils.nullSafeToString(this.classes))
+ .append("locations", ObjectUtils.nullSafeToString(this.locations))
+ .append("inheritLocations", this.inheritLocations)
+ .append("initializers", ObjectUtils.nullSafeToString(this.initializers))
+ .append("inheritInitializers", this.inheritInitializers)
+ .append("name", this.name)
+ .append("contextLoaderClass", this.contextLoaderClass.getName())
+ .toString();
+ }
+
+}
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
new file mode 100644
index 00000000..7eac62d6
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextHierarchy.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+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;
+
+/**
+ * {@code @ContextHierarchy} is a class-level annotation that is used to define
+ * a hierarchy of {@link org.springframework.context.ApplicationContext
+ * ApplicationContexts} for integration tests.
+ *
+ * <h3>Examples</h3>
+ * <p>The following JUnit-based examples demonstrate common configuration
+ * scenarios for integration tests that require the use of context hierarchies.
+ *
+ * <h4>Single Test Class with Context Hierarchy</h4>
+ * <p>{@code ControllerIntegrationTests} represents a typical integration testing
+ * scenario for a Spring MVC web application by declaring a context hierarchy
+ * consisting of two levels, one for the <em>root</em> {@code WebApplicationContext}
+ * (with {@code TestAppConfig}) and one for the <em>dispatcher servlet</em>
+ * {@code WebApplicationContext} (with {@code WebConfig}). The {@code
+ * WebApplicationContext} that is <em>autowired</em> into the test instance is
+ * the one for the child context (i.e., the lowest context in the hierarchy).
+ *
+ * <pre class="code">
+ * &#064;RunWith(SpringJUnit4ClassRunner.class)
+ * &#064;WebAppConfiguration
+ * &#064;ContextHierarchy({
+ * &#064;ContextConfiguration(classes = TestAppConfig.class),
+ * &#064;ContextConfiguration(classes = WebConfig.class)
+ * })
+ * public class ControllerIntegrationTests {
+ *
+ * &#064;Autowired
+ * private WebApplicationContext wac;
+ *
+ * // ...
+ * }</pre>
+ *
+ * <h4>Class Hierarchy with Implicit Parent Context</h4>
+ * <p>The following test classes define a context hierarchy within a test class
+ * hierarchy. {@code AbstractWebTests} declares the configuration for a root
+ * {@code WebApplicationContext} in a Spring-powered web application. Note,
+ * however, that {@code AbstractWebTests} does not declare {@code @ContextHierarchy};
+ * consequently, subclasses of {@code AbstractWebTests} can optionally participate
+ * in a context hierarchy or follow the standard semantics for {@code @ContextConfiguration}.
+ * {@code SoapWebServiceTests} and {@code RestWebServiceTests} both extend
+ * {@code AbstractWebTests} and define a context hierarchy via {@code @ContextHierarchy}.
+ * The result is that three application contexts will be loaded (one for each
+ * declaration of {@code @ContextConfiguration}, and the application context
+ * loaded based on the configuration in {@code AbstractWebTests} will be set as
+ * the parent context for each of the contexts loaded for the concrete subclasses.
+ *
+ * <pre class="code">
+ * &#064;RunWith(SpringJUnit4ClassRunner.class)
+ * &#064;WebAppConfiguration
+ * &#064;ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
+ * public abstract class AbstractWebTests {}
+ *
+ * &#064;ContextHierarchy(&#064;ContextConfiguration("/spring/soap-ws-config.xml")
+ * public class SoapWebServiceTests extends AbstractWebTests {}
+ *
+ * &#064;ContextHierarchy(&#064;ContextConfiguration("/spring/rest-ws-config.xml")
+ * public class RestWebServiceTests extends AbstractWebTests {}</pre>
+ *
+ * <h4>Class Hierarchy with Merged Context Hierarchy Configuration</h4>
+ * <p>The following classes demonstrate the use of <em>named</em> hierarchy levels
+ * in order to <em>merge</em> the configuration for specific levels in a context
+ * hierarchy. {@code BaseTests} defines two levels in the hierarchy, {@code parent}
+ * and {@code child}. {@code ExtendedTests} extends {@code BaseTests} and instructs
+ * the Spring TestContext Framework to merge the context configuration for the
+ * {@code child} hierarchy level, simply by ensuring that the names declared via
+ * {@link ContextConfiguration#name} are both {@code "child"}. The result is that
+ * three application contexts will be loaded: one for {@code "/app-config.xml"},
+ * one for {@code "/user-config.xml"}, and one for <code>{"/user-config.xml",
+ * "/order-config.xml"}</code>. As with the previous example, the application
+ * context loaded from {@code "/app-config.xml"} will be set as the parent context
+ * for the contexts loaded from {@code "/user-config.xml"} and <code>{"/user-config.xml",
+ * "/order-config.xml"}</code>.
+ *
+ * <pre class="code">
+ * &#064;RunWith(SpringJUnit4ClassRunner.class)
+ * &#064;ContextHierarchy({
+ * &#064;ContextConfiguration(name = "parent", locations = "/app-config.xml"),
+ * &#064;ContextConfiguration(name = "child", locations = "/user-config.xml")
+ * })
+ * public class BaseTests {}
+ *
+ * &#064;ContextHierarchy(
+ * &#064;ContextConfiguration(name = "child", locations = "/order-config.xml")
+ * )
+ * public class ExtendedTests extends BaseTests {}</pre>
+ *
+ * <h4>Class Hierarchy with Overridden Context Hierarchy Configuration</h4>
+ * <p>In contrast to the previous example, this example demonstrates how to
+ * <em>override</em> the configuration for a given named level in a context hierarchy
+ * by setting the {@link ContextConfiguration#inheritLocations} flag to {@code false}.
+ * Consequently, the application context for {@code ExtendedTests} will be loaded
+ * only from {@code "/test-user-config.xml"} and will have its parent set to the
+ * context loaded from {@code "/app-config.xml"}.
+ *
+ * <pre class="code">
+ * &#064;RunWith(SpringJUnit4ClassRunner.class)
+ * &#064;ContextHierarchy({
+ * &#064;ContextConfiguration(name = "parent", locations = "/app-config.xml"),
+ * &#064;ContextConfiguration(name = "child", locations = "/user-config.xml")
+ * })
+ * public class BaseTests {}
+ *
+ * &#064;ContextHierarchy(
+ * &#064;ContextConfiguration(name = "child", locations = "/test-user-config.xml", inheritLocations=false)
+ * )
+ * public class ExtendedTests extends BaseTests {}</pre>
+ *
+ * @author Sam Brannen
+ * @since 3.2.2
+ * @see ContextConfiguration
+ * @see org.springframework.context.ApplicationContext
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface ContextHierarchy {
+
+ /**
+ * A list of {@link ContextConfiguration @ContextConfiguration} instances,
+ * each of which defines a level in the context hierarchy.
+ *
+ * <p>If you need to merge or override the configuration for a given level
+ * of the context hierarchy within a test class hierarchy, you must explicitly
+ * name that level by supplying the same value to the {@link ContextConfiguration#name
+ * name} attribute in {@code @ContextConfiguration} at each level in the
+ * class hierarchy. See the class-level Javadoc for examples.
+ */
+ ContextConfiguration[] value();
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoader.java
new file mode 100644
index 00000000..71faf5e5
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoader.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2002-2012 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.ApplicationContext;
+
+/**
+ * Strategy interface for loading an {@link ApplicationContext application context}
+ * for an integration test managed by the Spring TestContext Framework.
+ *
+ * <p><b>Note</b>: as of Spring 3.1, implement {@link SmartContextLoader} instead
+ * of this interface in order to provide support for annotated classes, active
+ * bean definition profiles, and application context initializers.
+ *
+ * <p>Clients of a ContextLoader should call
+ * {@link #processLocations(Class, String...) processLocations()} prior to
+ * calling {@link #loadContext(String...) loadContext()} in case the
+ * ContextLoader provides custom support for modifying or generating locations.
+ * The results of {@link #processLocations(Class, String...) processLocations()}
+ * should then be supplied to {@link #loadContext(String...) loadContext()}.
+ *
+ * <p>Concrete implementations must provide a {@code public} no-args
+ * constructor.
+ *
+ * <p>Spring provides the following out-of-the-box implementations:
+ * <ul>
+ * <li>{@link org.springframework.test.context.support.GenericXmlContextLoader GenericXmlContextLoader}</li>
+ * <li>{@link org.springframework.test.context.support.GenericPropertiesContextLoader GenericPropertiesContextLoader}</li>
+ * </ul>
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see SmartContextLoader
+ * @see org.springframework.test.context.support.AnnotationConfigContextLoader AnnotationConfigContextLoader
+ */
+public interface ContextLoader {
+
+ /**
+ * Processes application context resource locations for a specified class.
+ *
+ * <p>Concrete implementations may choose to modify the supplied locations,
+ * generate new locations, or simply return the supplied locations unchanged.
+ *
+ * @param clazz the class with which the locations are associated: used to
+ * determine how to process the supplied locations
+ * @param locations the unmodified locations to use for loading the
+ * application context (can be {@code null} or empty)
+ * @return an array of application context resource locations
+ */
+ String[] processLocations(Class<?> clazz, String... locations);
+
+ /**
+ * Loads a new {@link ApplicationContext context} based on the supplied
+ * {@code locations}, configures the context, and finally returns
+ * the context in fully <em>refreshed</em> state.
+ *
+ * <p>Configuration locations are generally considered to be classpath
+ * resources by default.
+ *
+ * <p>Concrete implementations should register annotation configuration
+ * processors with bean factories of {@link ApplicationContext application
+ * contexts} loaded by this ContextLoader. Beans will therefore automatically
+ * be candidates for annotation-based dependency injection using
+ * {@link org.springframework.beans.factory.annotation.Autowired @Autowired},
+ * {@link javax.annotation.Resource @Resource}, and
+ * {@link javax.inject.Inject @Inject}.
+ *
+ * <p>Any ApplicationContext loaded by a ContextLoader <strong>must</strong>
+ * register a JVM shutdown hook for itself. Unless the context gets closed
+ * early, all context instances will be automatically closed on JVM
+ * shutdown. This allows for freeing external resources held by beans within
+ * the context, e.g. temporary files.
+ *
+ * @param locations the resource locations to use to load the application context
+ * @return a new application context
+ * @throws Exception if context loading failed
+ */
+ ApplicationContext loadContext(String... locations) throws Exception;
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java
new file mode 100644
index 00000000..f71e447b
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java
@@ -0,0 +1,707 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import static org.springframework.beans.BeanUtils.instantiateClass;
+import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass;
+import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClassForTypes;
+import static org.springframework.core.annotation.AnnotationUtils.isAnnotationDeclaredLocally;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Utility methods for working with {@link ContextLoader ContextLoaders} and
+ * {@link SmartContextLoader SmartContextLoaders} and resolving resource locations,
+ * annotated classes, active bean definition profiles, and application context
+ * initializers.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see ContextLoader
+ * @see SmartContextLoader
+ * @see ContextConfiguration
+ * @see ContextConfigurationAttributes
+ * @see ActiveProfiles
+ * @see ApplicationContextInitializer
+ * @see ContextHierarchy
+ * @see MergedContextConfiguration
+ */
+abstract class ContextLoaderUtils {
+
+ static final String GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX = "ContextHierarchyLevel#";
+
+ private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class);
+
+ private static final String DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.DelegatingSmartContextLoader";
+ private static final String DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.web.WebDelegatingSmartContextLoader";
+
+ private static final String WEB_APP_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebAppConfiguration";
+ private static final String WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebMergedContextConfiguration";
+
+
+ private ContextLoaderUtils() {
+ /* no-op */
+ }
+
+ /**
+ * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied
+ * list of {@link ContextConfigurationAttributes} and then instantiate and return that
+ * {@code ContextLoader}.
+ *
+ * <p>If the supplied {@code defaultContextLoaderClassName} is {@code null} or
+ * <em>empty</em>, depending on the absence or presence of
+ * {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration} either
+ * {@code "org.springframework.test.context.support.DelegatingSmartContextLoader"} or
+ * {@code "org.springframework.test.context.web.WebDelegatingSmartContextLoader"} will
+ * be used as the default context loader class name. For details on the class
+ * resolution process, see {@link #resolveContextLoaderClass}.
+ *
+ * @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>
+ * (i.e., as if we were traversing up the class hierarchy)
+ * @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
+ * class to use; may be {@code null} or <em>empty</em>
+ * @return the resolved {@code ContextLoader} for the supplied {@code testClass}
+ * (never {@code null})
+ * @see #resolveContextLoaderClass
+ */
+ static ContextLoader resolveContextLoader(Class<?> testClass,
+ List<ContextConfigurationAttributes> configAttributesList, String defaultContextLoaderClassName) {
+ Assert.notNull(testClass, "Class must not be null");
+ Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
+
+ if (!StringUtils.hasText(defaultContextLoaderClassName)) {
+ Class<? extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
+ defaultContextLoaderClassName = webAppConfigClass != null
+ && testClass.isAnnotationPresent(webAppConfigClass) ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME
+ : DEFAULT_CONTEXT_LOADER_CLASS_NAME;
+ }
+
+ Class<? extends ContextLoader> contextLoaderClass = resolveContextLoaderClass(testClass, configAttributesList,
+ defaultContextLoaderClassName);
+
+ return instantiateClass(contextLoaderClass, ContextLoader.class);
+ }
+
+ /**
+ * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied
+ * list of {@link ContextConfigurationAttributes}.
+ *
+ * <p>Beginning with the first level in the context configuration attributes hierarchy:
+ *
+ * <ol>
+ * <li>If the {@link ContextConfigurationAttributes#getContextLoaderClass()
+ * contextLoaderClass} property of {@link ContextConfigurationAttributes} is
+ * configured with an explicit class, that class will be returned.</li>
+ * <li>If an explicit {@code ContextLoader} class is not specified at the current
+ * level in the hierarchy, traverse to the next level in the hierarchy and return to
+ * step #1.</li>
+ * <li>If no explicit {@code ContextLoader} class is found after traversing the
+ * hierarchy, an attempt will be made to load and return the class with the supplied
+ * {@code defaultContextLoaderClassName}.</li>
+ * </ol>
+ *
+ * @param testClass the class for which to resolve the {@code ContextLoader} class;
+ * must not be {@code null}; only used for logging purposes
+ * @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>
+ * (i.e., as if we were traversing up the class hierarchy)
+ * @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
+ * class to use; must not be {@code null} or empty
+ * @return the {@code ContextLoader} class to use for the supplied test class
+ * @throws IllegalArgumentException if {@code @ContextConfiguration} is not
+ * <em>present</em> on the supplied test class
+ * @throws IllegalStateException if the default {@code ContextLoader} class could not
+ * be loaded
+ */
+ @SuppressWarnings("unchecked")
+ static Class<? extends ContextLoader> resolveContextLoaderClass(Class<?> testClass,
+ List<ContextConfigurationAttributes> configAttributesList, String defaultContextLoaderClassName) {
+ Assert.notNull(testClass, "Class must not be null");
+ Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
+ Assert.hasText(defaultContextLoaderClassName, "Default ContextLoader class name must not be null or empty");
+
+ for (ContextConfigurationAttributes configAttributes : configAttributesList) {
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Processing ContextLoader for context configuration attributes %s",
+ configAttributes));
+ }
+
+ Class<? extends ContextLoader> contextLoaderClass = configAttributes.getContextLoaderClass();
+ if (!ContextLoader.class.equals(contextLoaderClass)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format(
+ "Found explicit ContextLoader class [%s] for context configuration attributes %s",
+ contextLoaderClass.getName(), configAttributes));
+ }
+ return contextLoaderClass;
+ }
+ }
+
+ try {
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Using default ContextLoader class [%s] for test class [%s]",
+ defaultContextLoaderClassName, testClass.getName()));
+ }
+ return (Class<? extends ContextLoader>) ClassUtils.forName(defaultContextLoaderClassName,
+ ContextLoaderUtils.class.getClassLoader());
+ }
+ catch (Throwable t) {
+ throw new IllegalStateException("Could not load default ContextLoader class ["
+ + defaultContextLoaderClassName + "]. Specify @ContextConfiguration's 'loader' "
+ + "attribute or make the default loader class available.", t);
+ }
+ }
+
+ /**
+ * Convenience method for creating a {@link ContextConfigurationAttributes} instance
+ * from the supplied {@link ContextConfiguration} and declaring class and then adding
+ * the attributes to the supplied list.
+ */
+ private static void convertContextConfigToConfigAttributesAndAddToList(ContextConfiguration contextConfiguration,
+ Class<?> declaringClass, final List<ContextConfigurationAttributes> attributesList) {
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].",
+ contextConfiguration, declaringClass.getName()));
+ }
+
+ ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass,
+ contextConfiguration);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Resolved context configuration attributes: " + attributes);
+ }
+ attributesList.add(attributes);
+ }
+
+ /**
+ * Resolve the list of lists of {@linkplain ContextConfigurationAttributes context
+ * configuration attributes} for the supplied {@linkplain Class test class} and its
+ * superclasses, taking into account context hierarchies declared via
+ * {@link ContextHierarchy @ContextHierarchy} and
+ * {@link ContextConfiguration @ContextConfiguration}.
+ *
+ * <p>The outer list represents a top-down ordering of context configuration
+ * attributes, where each element in the list represents the context configuration
+ * declared on a given test class in the class hierarchy. Each nested list
+ * contains the context configuration attributes declared either via a single
+ * instance of {@code @ContextConfiguration} on the particular class or via
+ * multiple instances of {@code @ContextConfiguration} declared within a
+ * single {@code @ContextHierarchy} instance on the particular class.
+ * Furthermore, each nested list maintains the order in which
+ * {@code @ContextConfiguration} instances are declared.
+ *
+ * <p>Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
+ * {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
+ * {@link ContextConfiguration @ContextConfiguration} will <strong>not</strong>
+ * be taken into consideration. If these flags need to be honored, that must be
+ * handled manually when traversing the nested lists returned by this method.
+ *
+ * @param testClass the class for which to resolve the context hierarchy attributes
+ * (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
+ * neither {@code @ContextConfiguration} nor {@code @ContextHierarchy} is
+ * <em>present</em> on the supplied class; or if a given class in the class hierarchy
+ * declares both {@code @ContextConfiguration} and {@code @ContextHierarchy} as
+ * top-level annotations.
+ *
+ * @since 3.2.2
+ * @see #buildContextHierarchyMap(Class)
+ * @see #resolveContextConfigurationAttributes(Class)
+ */
+ static List<List<ContextConfigurationAttributes>> resolveContextHierarchyAttributes(Class<?> testClass) {
+ Assert.notNull(testClass, "Class must not be null");
+
+ final Class<ContextConfiguration> contextConfigType = ContextConfiguration.class;
+ final Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class;
+ final List<Class<? extends Annotation>> annotationTypes = Arrays.asList(contextConfigType, contextHierarchyType);
+
+ final List<List<ContextConfigurationAttributes>> hierarchyAttributes = new ArrayList<List<ContextConfigurationAttributes>>();
+
+ Class<?> declaringClass = findAnnotationDeclaringClassForTypes(annotationTypes, testClass);
+ Assert.notNull(declaringClass, String.format(
+ "Could not find an 'annotation declaring class' for annotation type [%s] or [%s] and test class [%s]",
+ contextConfigType.getName(), contextHierarchyType.getName(), testClass.getName()));
+
+ while (declaringClass != null) {
+
+ boolean contextConfigDeclaredLocally = isAnnotationDeclaredLocally(contextConfigType, declaringClass);
+ boolean contextHierarchyDeclaredLocally = isAnnotationDeclaredLocally(contextHierarchyType, declaringClass);
+
+ if (contextConfigDeclaredLocally && contextHierarchyDeclaredLocally) {
+ String msg = String.format("Test class [%s] has been configured with both @ContextConfiguration "
+ + "and @ContextHierarchy as class-level annotations. Only one of these annotations may "
+ + "be declared as a top-level annotation per test class.", declaringClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>();
+
+ if (contextConfigDeclaredLocally) {
+ ContextConfiguration contextConfiguration = declaringClass.getAnnotation(contextConfigType);
+ convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass,
+ configAttributesList);
+ }
+ else if (contextHierarchyDeclaredLocally) {
+ ContextHierarchy contextHierarchy = declaringClass.getAnnotation(contextHierarchyType);
+ for (ContextConfiguration contextConfiguration : contextHierarchy.value()) {
+ convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass,
+ configAttributesList);
+ }
+ }
+ else {
+ // This should theoretically actually never happen...
+ String msg = String.format("Test class [%s] has been configured with neither @ContextConfiguration "
+ + "nor @ContextHierarchy as a class-level annotation.", declaringClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ hierarchyAttributes.add(0, configAttributesList);
+
+ declaringClass = findAnnotationDeclaringClassForTypes(annotationTypes, declaringClass.getSuperclass());
+ }
+
+ return hierarchyAttributes;
+ }
+
+ /**
+ * Build a <em>context hierarchy map</em> for the supplied {@linkplain Class
+ * test class} and its superclasses, taking into account context hierarchies
+ * declared via {@link ContextHierarchy @ContextHierarchy} and
+ * {@link ContextConfiguration @ContextConfiguration}.
+ *
+ * <p>Each value in the map represents the consolidated list of {@linkplain
+ * ContextConfigurationAttributes context configuration attributes} for a
+ * given level in the context hierarchy (potentially across the test class
+ * hierarchy), keyed by the {@link ContextConfiguration#name() name} of the
+ * context hierarchy level.
+ *
+ * <p>If a given level in the context hierarchy does not have an explicit
+ * name (i.e., configured via {@link ContextConfiguration#name}), a name will
+ * be generated for that hierarchy level by appending the numerical level to
+ * the {@link #GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX}.
+ *
+ * @param testClass the class for which to resolve the context hierarchy map
+ * (must not be {@code null})
+ * @return a map of context configuration attributes for the context hierarchy,
+ * keyed by context hierarchy level name; never {@code null}
+ * @throws IllegalArgumentException if the lists of context configuration
+ * attributes for each level in the {@code @ContextHierarchy} do not define
+ * unique context configuration within the overall hierarchy.
+ *
+ * @since 3.2.2
+ * @see #resolveContextHierarchyAttributes(Class)
+ */
+ static Map<String, List<ContextConfigurationAttributes>> buildContextHierarchyMap(Class<?> testClass) {
+ final Map<String, List<ContextConfigurationAttributes>> map = new LinkedHashMap<String, List<ContextConfigurationAttributes>>();
+ int hierarchyLevel = 1;
+
+ for (List<ContextConfigurationAttributes> configAttributesList : resolveContextHierarchyAttributes(testClass)) {
+ for (ContextConfigurationAttributes configAttributes : configAttributesList) {
+ String name = configAttributes.getName();
+
+ // Assign a generated name?
+ if (!StringUtils.hasText(name)) {
+ name = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + hierarchyLevel;
+ }
+
+ // Encountered a new context hierarchy level?
+ if (!map.containsKey(name)) {
+ hierarchyLevel++;
+ map.put(name, new ArrayList<ContextConfigurationAttributes>());
+ }
+
+ map.get(name).add(configAttributes);
+ }
+ }
+
+ // Check for uniqueness
+ Set<List<ContextConfigurationAttributes>> set = new HashSet<List<ContextConfigurationAttributes>>(map.values());
+ if (set.size() != map.size()) {
+ String msg = String.format("The @ContextConfiguration elements configured via "
+ + "@ContextHierarchy in test class [%s] and its superclasses must "
+ + "define unique contexts per hierarchy level.", testClass.getName());
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ return map;
+ }
+
+ /**
+ * Resolve the list of {@linkplain ContextConfigurationAttributes context
+ * configuration attributes} for the supplied {@linkplain Class test class} and its
+ * superclasses.
+ *
+ * <p>Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
+ * {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
+ * {@link ContextConfiguration @ContextConfiguration} will <strong>not</strong>
+ * be taken into consideration. If these flags need to be honored, that must be
+ * handled manually when traversing the list returned by this method.
+ *
+ * @param testClass the class for which to resolve the configuration attributes (must
+ * not be {@code null})
+ * @return the list of configuration attributes for the specified class, ordered
+ * <em>bottom-up</em> (i.e., as if we were traversing up the class hierarchy);
+ * never {@code null}
+ * @throws IllegalArgumentException if the supplied class is {@code null} or if
+ * {@code @ContextConfiguration} is not <em>present</em> on the supplied class
+ */
+ static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> testClass) {
+ Assert.notNull(testClass, "Class must not be null");
+
+ final List<ContextConfigurationAttributes> attributesList = new ArrayList<ContextConfigurationAttributes>();
+
+ Class<ContextConfiguration> annotationType = ContextConfiguration.class;
+ Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, testClass);
+ Assert.notNull(declaringClass, String.format(
+ "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
+ annotationType.getName(), testClass.getName()));
+
+ while (declaringClass != null) {
+ ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType);
+ convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass, attributesList);
+ declaringClass = findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass());
+ }
+
+ return attributesList;
+ }
+
+ /**
+ * Resolve the list of merged {@code ApplicationContextInitializer} classes for the
+ * supplied list of {@code ContextConfigurationAttributes}.
+ *
+ * <p>Note that the {@link ContextConfiguration#inheritInitializers inheritInitializers}
+ * flag of {@link ContextConfiguration @ContextConfiguration} will be taken into
+ * consideration. Specifically, if the {@code inheritInitializers} flag is set to
+ * {@code true} for a given level in the class hierarchy represented by the provided
+ * configuration attributes, context initializer classes defined at the given level
+ * will be merged with those defined in higher levels of the class hierarchy.
+ *
+ * @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>
+ * (i.e., as if we were traversing up the class hierarchy)
+ * @return the set of merged context initializer classes, including those from
+ * superclasses if appropriate (never {@code null})
+ * @since 3.2
+ */
+ static Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> resolveInitializerClasses(
+ List<ContextConfigurationAttributes> configAttributesList) {
+ Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
+
+ final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = //
+ new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
+
+ for (ContextConfigurationAttributes configAttributes : configAttributesList) {
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Processing context initializers for context configuration attributes %s",
+ configAttributes));
+ }
+
+ initializerClasses.addAll(Arrays.asList(configAttributes.getInitializers()));
+
+ if (!configAttributes.isInheritInitializers()) {
+ break;
+ }
+ }
+
+ return initializerClasses;
+ }
+
+ /**
+ * Resolve <em>active bean definition profiles</em> for the supplied {@link Class}.
+ *
+ * <p>Note that the {@link ActiveProfiles#inheritProfiles inheritProfiles} flag of
+ * {@link ActiveProfiles @ActiveProfiles} will be taken into consideration.
+ * Specifically, if the {@code inheritProfiles} flag is set to {@code true}, profiles
+ * defined in the test class will be merged with those defined in superclasses.
+ *
+ * @param testClass the class for which to resolve the active profiles (must not be
+ * {@code null})
+ * @return the set of active profiles for the specified class, including active
+ * profiles from superclasses if appropriate (never {@code null})
+ * @see ActiveProfiles
+ * @see org.springframework.context.annotation.Profile
+ */
+ static String[] resolveActiveProfiles(Class<?> testClass) {
+ Assert.notNull(testClass, "Class must not be null");
+
+ Class<ActiveProfiles> annotationType = ActiveProfiles.class;
+ Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, testClass);
+
+ if (declaringClass == null && logger.isDebugEnabled()) {
+ logger.debug(String.format(
+ "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
+ annotationType.getName(), testClass.getName()));
+ }
+
+ final Set<String> activeProfiles = new HashSet<String>();
+
+ while (declaringClass != null) {
+ ActiveProfiles annotation = declaringClass.getAnnotation(annotationType);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
+ declaringClass.getName()));
+ }
+
+ String[] profiles = annotation.profiles();
+ String[] valueProfiles = annotation.value();
+
+ if (!ObjectUtils.isEmpty(valueProfiles) && !ObjectUtils.isEmpty(profiles)) {
+ String msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
+ + "and 'profiles' [%s] attributes. Only one declaration of active bean "
+ + "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(),
+ ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles));
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+ else if (!ObjectUtils.isEmpty(valueProfiles)) {
+ profiles = valueProfiles;
+ }
+
+ for (String profile : profiles) {
+ if (StringUtils.hasText(profile)) {
+ activeProfiles.add(profile.trim());
+ }
+ }
+
+ declaringClass = annotation.inheritProfiles() ? findAnnotationDeclaringClass(annotationType,
+ declaringClass.getSuperclass()) : null;
+ }
+
+ return StringUtils.toStringArray(activeProfiles);
+ }
+
+ /**
+ * Build the {@link MergedContextConfiguration merged context configuration} for
+ * the supplied {@link Class testClass} and {@code defaultContextLoaderClassName},
+ * taking into account context hierarchies declared via
+ * {@link ContextHierarchy @ContextHierarchy} and
+ * {@link ContextConfiguration @ContextConfiguration}.
+ *
+ * @param testClass the test class for which the {@code MergedContextConfiguration}
+ * should be built (must not be {@code null})
+ * @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
+ * class to use (may be {@code null})
+ * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to
+ * be passed to the {@code MergedContextConfiguration} constructor
+ * @return the merged context configuration
+ * @see #buildContextHierarchyMap(Class)
+ * @see #buildMergedContextConfiguration(Class, List, String, MergedContextConfiguration, CacheAwareContextLoaderDelegate)
+ */
+ @SuppressWarnings("javadoc")
+ static MergedContextConfiguration buildMergedContextConfiguration(Class<?> testClass,
+ String defaultContextLoaderClassName, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
+
+ if (testClass.isAnnotationPresent(ContextHierarchy.class)) {
+ Map<String, List<ContextConfigurationAttributes>> hierarchyMap = buildContextHierarchyMap(testClass);
+
+ MergedContextConfiguration parentConfig = null;
+ MergedContextConfiguration mergedConfig = null;
+
+ for (List<ContextConfigurationAttributes> list : hierarchyMap.values()) {
+ List<ContextConfigurationAttributes> reversedList = new ArrayList<ContextConfigurationAttributes>(list);
+ Collections.reverse(reversedList);
+
+ // Don't use the supplied testClass; instead ensure that we are
+ // building the MCC for the actual test class that declared the
+ // configuration for the current level in the context hierarchy.
+ Assert.notEmpty(reversedList, "ContextConfigurationAttributes list must not be empty");
+ Class<?> declaringClass = reversedList.get(0).getDeclaringClass();
+
+ mergedConfig = buildMergedContextConfiguration(declaringClass, reversedList,
+ defaultContextLoaderClassName, parentConfig, cacheAwareContextLoaderDelegate);
+ parentConfig = mergedConfig;
+ }
+
+ // Return the last level in the context hierarchy
+ return mergedConfig;
+ }
+ else {
+ return buildMergedContextConfiguration(testClass, resolveContextConfigurationAttributes(testClass),
+ defaultContextLoaderClassName, null, cacheAwareContextLoaderDelegate);
+ }
+ }
+
+ /**
+ * Build the {@link MergedContextConfiguration merged context configuration} for the
+ * supplied {@link Class testClass}, context configuration attributes,
+ * {@code defaultContextLoaderClassName}, and parent context configuration.
+ *
+ * @param testClass the test class for which the {@code MergedContextConfiguration}
+ * should be built (must not be {@code null})
+ * @param configAttributesList the list of context configuration attributes for the
+ * specified test class, ordered <em>bottom-up</em> (i.e., as if we were
+ * traversing up the class hierarchy); never {@code null} or empty
+ * @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
+ * class to use (may be {@code null})
+ * @param parentConfig the merged context configuration for the parent application
+ * 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
+ * @return the merged context configuration
+ * @see #resolveContextLoader
+ * @see #resolveContextConfigurationAttributes
+ * @see SmartContextLoader#processContextConfiguration
+ * @see ContextLoader#processLocations
+ * @see #resolveActiveProfiles
+ * @see MergedContextConfiguration
+ */
+ private static MergedContextConfiguration buildMergedContextConfiguration(final Class<?> testClass,
+ final List<ContextConfigurationAttributes> configAttributesList,
+ final String defaultContextLoaderClassName, MergedContextConfiguration parentConfig,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
+
+ final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList,
+ defaultContextLoaderClassName);
+ final List<String> locationsList = new ArrayList<String>();
+ final List<Class<?>> classesList = new ArrayList<Class<?>>();
+
+ for (ContextConfigurationAttributes configAttributes : configAttributesList) {
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Processing locations and classes for context configuration attributes %s",
+ 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()));
+ }
+ else {
+ String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(),
+ configAttributes.getLocations());
+ locationsList.addAll(0, Arrays.asList(processedLocations));
+ // Legacy ContextLoaders don't know how to process classes
+ }
+
+ if (!configAttributes.isInheritLocations()) {
+ break;
+ }
+ }
+
+ String[] locations = StringUtils.toStringArray(locationsList);
+ Class<?>[] classes = ClassUtils.toClassArray(classesList);
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = resolveInitializerClasses(configAttributesList);
+ String[] activeProfiles = resolveActiveProfiles(testClass);
+
+ MergedContextConfiguration mergedConfig = buildWebMergedContextConfiguration(testClass, locations, classes,
+ initializerClasses, activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
+
+ if (mergedConfig == null) {
+ mergedConfig = new MergedContextConfiguration(testClass, locations, classes, initializerClasses,
+ activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
+ }
+
+ return mergedConfig;
+ }
+
+ /**
+ * Load the {@link org.springframework.test.context.web.WebAppConfiguration}
+ * class, using reflection in order to avoid package cycles.
+ *
+ * @return the {@code @WebAppConfiguration} class or {@code null} if it cannot be loaded
+ * @since 3.2
+ */
+ @SuppressWarnings("unchecked")
+ private static Class<? extends Annotation> loadWebAppConfigurationClass() {
+ Class<? extends Annotation> webAppConfigClass = null;
+ try {
+ webAppConfigClass = (Class<? extends Annotation>) ClassUtils.forName(WEB_APP_CONFIGURATION_CLASS_NAME,
+ ContextLoaderUtils.class.getClassLoader());
+ }
+ catch (Throwable t) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Could not load @WebAppConfiguration class [" + WEB_APP_CONFIGURATION_CLASS_NAME + "].", t);
+ }
+ }
+ return webAppConfigClass;
+ }
+
+ /**
+ * Attempt to build a {@link org.springframework.test.context.web.WebMergedContextConfiguration}
+ * from the supplied arguments, using reflection in order to avoid package cycles.
+ *
+ * @return the {@code WebMergedContextConfiguration} or {@code null} if it could not be built
+ * @since 3.2
+ */
+ @SuppressWarnings("unchecked")
+ private static MergedContextConfiguration buildWebMergedContextConfiguration(
+ Class<?> testClass,
+ String[] locations,
+ Class<?>[] classes,
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses,
+ String[] activeProfiles, ContextLoader contextLoader,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) {
+
+ Class<? extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
+
+ if (webAppConfigClass != null && testClass.isAnnotationPresent(webAppConfigClass)) {
+ Annotation annotation = testClass.getAnnotation(webAppConfigClass);
+ String resourceBasePath = (String) AnnotationUtils.getValue(annotation);
+
+ try {
+ Class<? extends MergedContextConfiguration> webMergedConfigClass = (Class<? extends MergedContextConfiguration>) ClassUtils.forName(
+ WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME, ContextLoaderUtils.class.getClassLoader());
+
+ Constructor<? extends MergedContextConfiguration> constructor = ClassUtils.getConstructorIfAvailable(
+ webMergedConfigClass, Class.class, String[].class, Class[].class, Set.class, String[].class,
+ String.class, ContextLoader.class, CacheAwareContextLoaderDelegate.class,
+ MergedContextConfiguration.class);
+
+ if (constructor != null) {
+ return instantiateClass(constructor, testClass, locations, classes, initializerClasses,
+ activeProfiles, resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
+ }
+ }
+ catch (Throwable t) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Could not instantiate [" + WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME + "].", t);
+ }
+ }
+ }
+
+ return null;
+ }
+
+}
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
new file mode 100644
index 00000000..befe85c7
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.style.ToStringCreator;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@code MergedContextConfiguration} encapsulates the <em>merged</em>
+ * context configuration declared on a test class and all of its superclasses
+ * via {@link ContextConfiguration @ContextConfiguration} and
+ * {@link ActiveProfiles @ActiveProfiles}.
+ *
+ * <p>Merged resource locations, annotated classes, and active profiles
+ * represent all declared values in the test class hierarchy taking into
+ * consideration the semantics of the
+ * {@link ContextConfiguration#inheritLocations inheritLocations} and
+ * {@link ActiveProfiles#inheritProfiles inheritProfiles} flags in
+ * {@code @ContextConfiguration} and {@code @ActiveProfiles}, respectively.
+ *
+ * <p>A {@link SmartContextLoader} uses {@code MergedContextConfiguration}
+ * to load an {@link org.springframework.context.ApplicationContext ApplicationContext}.
+ *
+ * <p>{@code MergedContextConfiguration} is also used by the {@link TestContext}
+ * as the context cache key for caching an
+ * {@link org.springframework.context.ApplicationContext ApplicationContext}
+ * that was loaded using properties of this {@code MergedContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see ContextConfiguration
+ * @see ActiveProfiles
+ * @see ContextConfigurationAttributes
+ * @see SmartContextLoader#loadContext(MergedContextConfiguration)
+ */
+public class MergedContextConfiguration implements Serializable {
+
+ private static final long serialVersionUID = -3290560718464957422L;
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
+ private static final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> EMPTY_INITIALIZER_CLASSES = //
+ Collections.<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> emptySet();
+
+ private final Class<?> testClass;
+ private final String[] locations;
+ private final Class<?>[] classes;
+ private final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses;
+ private final String[] activeProfiles;
+ private final ContextLoader contextLoader;
+ private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
+ private final MergedContextConfiguration parent;
+
+
+ private static String[] processLocations(String[] locations) {
+ return locations == null ? EMPTY_STRING_ARRAY : locations;
+ }
+
+ private static Class<?>[] processClasses(Class<?>[] classes) {
+ return classes == null ? EMPTY_CLASS_ARRAY : classes;
+ }
+
+ private static Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> processContextInitializerClasses(
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses) {
+ return contextInitializerClasses == null ? EMPTY_INITIALIZER_CLASSES
+ : Collections.unmodifiableSet(contextInitializerClasses);
+ }
+
+ private static String[] processActiveProfiles(String[] activeProfiles) {
+ if (activeProfiles == null) {
+ return EMPTY_STRING_ARRAY;
+ }
+
+ // Active profiles must be unique and sorted in order to support proper
+ // cache key generation. Specifically, profile sets {foo,bar} and
+ // {bar,foo} must both result in the same array (e.g., [bar,foo]).
+ SortedSet<String> sortedProfilesSet = new TreeSet<String>(Arrays.asList(activeProfiles));
+ return StringUtils.toStringArray(sortedProfilesSet);
+ }
+
+ /**
+ * Generate a null-safe {@link String} representation of the supplied
+ * {@link ContextLoader} based solely on the fully qualified name of the
+ * loader or &quot;null&quot; if the supplied loaded is {@code null}.
+ */
+ protected static String nullSafeToString(ContextLoader contextLoader) {
+ return contextLoader == null ? "null" : contextLoader.getClass().getName();
+ }
+
+ /**
+ * Create a new {@code MergedContextConfiguration} instance for the
+ * supplied test class, resource locations, annotated classes, active
+ * profiles, and {@code ContextLoader}.
+ *
+ * <p>If a {@code null} value is supplied for {@code locations},
+ * {@code classes}, or {@code activeProfiles} an empty array 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 resource locations
+ * @param classes the merged annotated classes
+ * @param activeProfiles the merged active bean definition profiles
+ * @param contextLoader the resolved {@code ContextLoader}
+ * @see #MergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader)
+ */
+ public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
+ String[] activeProfiles, ContextLoader contextLoader) {
+ this(testClass, locations, classes, null, activeProfiles, contextLoader);
+ }
+
+ /**
+ * Create a new {@code MergedContextConfiguration} instance for the
+ * supplied test class, resource locations, annotated classes, context
+ * initializers, active profiles, and {@code ContextLoader}.
+ *
+ * <p>If a {@code null} value is supplied for {@code locations},
+ * {@code classes}, or {@code activeProfiles} an empty array will
+ * be stored instead. If a {@code null} value is supplied for the
+ * {@code contextInitializerClasses} 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 resource locations
+ * @param classes the merged annotated classes
+ * @param contextInitializerClasses the merged context initializer classes
+ * @param activeProfiles the merged active bean definition profiles
+ * @param contextLoader the resolved {@code ContextLoader}
+ * @see #MergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)
+ */
+ public MergedContextConfiguration(
+ Class<?> testClass,
+ String[] locations,
+ Class<?>[] classes,
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
+ String[] activeProfiles, ContextLoader contextLoader) {
+ this(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader, null, null);
+ }
+
+ /**
+ * Create a new {@code MergedContextConfiguration} instance for the
+ * supplied test class, resource locations, annotated classes, context
+ * initializers, active profiles, {@code ContextLoader}, and parent
+ * configuration.
+ *
+ * <p>If a {@code null} value is supplied for {@code locations},
+ * {@code classes}, or {@code activeProfiles} an empty array will
+ * be stored instead. If a {@code null} value is supplied for the
+ * {@code contextInitializerClasses} 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 resource locations
+ * @param classes the merged annotated classes
+ * @param contextInitializerClasses the merged context initializer classes
+ * @param activeProfiles the merged active bean definition profiles
+ * @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 3.2.2
+ */
+ public MergedContextConfiguration(
+ Class<?> testClass,
+ String[] locations,
+ Class<?>[] classes,
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
+ String[] activeProfiles, ContextLoader contextLoader,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
+ this.testClass = testClass;
+ this.locations = processLocations(locations);
+ this.classes = processClasses(classes);
+ this.contextInitializerClasses = processContextInitializerClasses(contextInitializerClasses);
+ this.activeProfiles = processActiveProfiles(activeProfiles);
+ this.contextLoader = contextLoader;
+ this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate;
+ this.parent = parent;
+ }
+
+ /**
+ * Get the {@linkplain Class test class} associated with this {@code MergedContextConfiguration}.
+ */
+ public Class<?> getTestClass() {
+ return testClass;
+ }
+
+ /**
+ * Get the merged resource locations for the {@linkplain #getTestClass() test class}.
+ */
+ public String[] getLocations() {
+ return locations;
+ }
+
+ /**
+ * Get the merged annotated classes for the {@linkplain #getTestClass() test class}.
+ */
+ public Class<?>[] getClasses() {
+ return classes;
+ }
+
+ /**
+ * Get the merged {@code ApplicationContextInitializer} classes for the
+ * {@linkplain #getTestClass() test class}.
+ */
+ public Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> getContextInitializerClasses() {
+ return contextInitializerClasses;
+ }
+
+ /**
+ * Get the merged active bean definition profiles for the {@linkplain #getTestClass() test class}.
+ */
+ public String[] getActiveProfiles() {
+ return activeProfiles;
+ }
+
+ /**
+ * Get the resolved {@link ContextLoader} for the {@linkplain #getTestClass() test class}.
+ */
+ public ContextLoader getContextLoader() {
+ return contextLoader;
+ }
+
+ /**
+ * Get the {@link MergedContextConfiguration} for the parent application context in a
+ * context hierarchy.
+ *
+ * @return the parent configuration or {@code null} if there is no parent
+ * @see #getParentApplicationContext()
+ * @since 3.2.2
+ */
+ public MergedContextConfiguration getParent() {
+ return this.parent;
+ }
+
+ /**
+ * Get the parent {@link ApplicationContext} for the context defined by this
+ * {@code MergedContextConfiguration} from the context cache.
+ * <p>
+ * If the parent context has not yet been loaded, it will be loaded, stored in the
+ * cache, and then returned.
+ *
+ * @return the parent {@code ApplicationContext} or {@code null} if there is no parent
+ * @see #getParent()
+ * @since 3.2.2
+ */
+ public ApplicationContext getParentApplicationContext() {
+ if (parent == null) {
+ return null;
+ }
+
+ Assert.state(cacheAwareContextLoaderDelegate != null,
+ "Cannot retrieve a parent application context without access to the CacheAwareContextLoaderDelegate.");
+ return cacheAwareContextLoaderDelegate.loadContext(parent);
+ }
+
+ /**
+ * Generate a unique hash code for all properties of this
+ * {@code MergedContextConfiguration} excluding the
+ * {@linkplain #getTestClass() test class}.
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(locations);
+ result = prime * result + Arrays.hashCode(classes);
+ result = prime * result + contextInitializerClasses.hashCode();
+ result = prime * result + Arrays.hashCode(activeProfiles);
+ result = prime * result + (parent == null ? 0 : parent.hashCode());
+ result = prime * result + nullSafeToString(contextLoader).hashCode();
+ return result;
+ }
+
+ /**
+ * Determine if the supplied object is equal to this {@code MergedContextConfiguration}
+ * instance by comparing both object's {@linkplain #getLocations() locations},
+ * {@linkplain #getClasses() annotated classes},
+ * {@linkplain #getContextInitializerClasses() context initializer classes},
+ * {@linkplain #getActiveProfiles() active profiles},
+ * {@linkplain #getParent() parents}, and the fully qualified names of their
+ * {@link #getContextLoader() ContextLoaders}.
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof MergedContextConfiguration)) {
+ return false;
+ }
+
+ final MergedContextConfiguration that = (MergedContextConfiguration) obj;
+
+ if (!Arrays.equals(this.locations, that.locations)) {
+ return false;
+ }
+
+ if (!Arrays.equals(this.classes, that.classes)) {
+ return false;
+ }
+
+ if (!this.contextInitializerClasses.equals(that.contextInitializerClasses)) {
+ return false;
+ }
+
+ if (!Arrays.equals(this.activeProfiles, that.activeProfiles)) {
+ return false;
+ }
+
+ if (this.parent == null) {
+ if (that.parent != null) {
+ return false;
+ }
+ }
+ else if (!this.parent.equals(that.parent)) {
+ return false;
+ }
+
+ if (!nullSafeToString(this.contextLoader).equals(nullSafeToString(that.contextLoader))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Provide a String representation of the {@linkplain #getTestClass() test class},
+ * {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes},
+ * {@linkplain #getContextInitializerClasses() context initializer classes},
+ * {@linkplain #getActiveProfiles() active profiles}, the name of the
+ * {@link #getContextLoader() ContextLoader}, and the
+ * {@linkplain #getParent() parent configuration}.
+ */
+ @Override
+ public String toString() {
+ return new ToStringCreator(this)//
+ .append("testClass", testClass)//
+ .append("locations", ObjectUtils.nullSafeToString(locations))//
+ .append("classes", ObjectUtils.nullSafeToString(classes))//
+ .append("contextInitializerClasses", ObjectUtils.nullSafeToString(contextInitializerClasses))//
+ .append("activeProfiles", ObjectUtils.nullSafeToString(activeProfiles))//
+ .append("contextLoader", nullSafeToString(contextLoader))//
+ .append("parent", parent)//
+ .toString();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java
new file mode 100644
index 00000000..d0495ca0
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/SmartContextLoader.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Strategy interface for loading an {@link ApplicationContext application context}
+ * for an integration test managed by the Spring TestContext Framework.
+ *
+ * <p>The {@code SmartContextLoader} SPI supersedes the {@link ContextLoader} SPI
+ * introduced in Spring 2.5: a {@code SmartContextLoader} can choose to process
+ * either resource locations or annotated classes. Furthermore, a
+ * {@code SmartContextLoader} can set active bean definition profiles in the
+ * context that it loads (see {@link MergedContextConfiguration#getActiveProfiles()}
+ * and {@link #loadContext(MergedContextConfiguration)}).
+ *
+ * <p>See the Javadoc for {@link ContextConfiguration @ContextConfiguration}
+ * for a definition of <em>annotated class</em>.
+ *
+ * <p>Clients of a {@code SmartContextLoader} should call
+ * {@link #processContextConfiguration(ContextConfigurationAttributes)
+ * processContextConfiguration()} prior to calling
+ * {@link #loadContext(MergedContextConfiguration) loadContext()}. This gives a
+ * {@code SmartContextLoader} the opportunity to provide custom support for
+ * modifying resource locations or detecting default resource locations or
+ * default configuration classes. The results of
+ * {@link #processContextConfiguration(ContextConfigurationAttributes)
+ * processContextConfiguration()} should be merged for all classes in the
+ * hierarchy of the root test class and then supplied to
+ * {@link #loadContext(MergedContextConfiguration) loadContext()}.
+ *
+ * <p>Even though {@code SmartContextLoader} extends {@code ContextLoader},
+ * clients should favor {@code SmartContextLoader}-specific methods over those
+ * defined in {@code ContextLoader}, particularly because a
+ * {@code SmartContextLoader} may choose not to support methods defined in the
+ * {@code ContextLoader} SPI.
+ *
+ * <p>Concrete implementations must provide a {@code public} no-args constructor.
+ *
+ * <p>Spring provides the following out-of-the-box implementations:
+ * <ul>
+ * <li>{@link org.springframework.test.context.support.DelegatingSmartContextLoader DelegatingSmartContextLoader}</li>
+ * <li>{@link org.springframework.test.context.support.AnnotationConfigContextLoader AnnotationConfigContextLoader}</li>
+ * <li>{@link org.springframework.test.context.support.GenericXmlContextLoader GenericXmlContextLoader}</li>
+ * <li>{@link org.springframework.test.context.support.GenericPropertiesContextLoader GenericPropertiesContextLoader}</li>
+ * <li>{@link org.springframework.test.context.web.WebDelegatingSmartContextLoader WebDelegatingSmartContextLoader}</li>
+ * <li>{@link org.springframework.test.context.web.AnnotationConfigWebContextLoader AnnotationConfigWebContextLoader}</li>
+ * <li>{@link org.springframework.test.context.web.GenericXmlWebContextLoader GenericXmlWebContextLoader}</li>
+ * </ul>
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see ContextConfiguration
+ * @see ActiveProfiles
+ * @see ContextConfigurationAttributes
+ * @see MergedContextConfiguration
+ */
+public interface SmartContextLoader extends ContextLoader {
+
+ /**
+ * Processes the {@link ContextConfigurationAttributes} for a given test class.
+ * <p>Concrete implementations may choose to <em>modify</em> the {@code locations}
+ * or {@code classes} in the supplied {@link ContextConfigurationAttributes},
+ * <em>generate</em> default configuration locations, or <em>detect</em>
+ * default configuration classes if the supplied values are {@code null}
+ * or empty.
+ * <p><b>Note</b>: in contrast to a standard {@code ContextLoader}, a
+ * {@code SmartContextLoader} <b>must</b> <em>preemptively</em> verify that
+ * a generated or detected default actually exists before setting the corresponding
+ * {@code locations} or {@code classes} property in the supplied
+ * {@link ContextConfigurationAttributes}. Consequently, leaving the
+ * {@code locations} or {@code classes} property empty signals that
+ * this {@code SmartContextLoader} was not able to generate or detect defaults.
+ * @param configAttributes the context configuration attributes to process
+ */
+ void processContextConfiguration(ContextConfigurationAttributes configAttributes);
+
+ /**
+ * Loads a new {@link ApplicationContext context} based on the supplied
+ * {@link MergedContextConfiguration merged context configuration},
+ * configures the context, and finally returns the context in a fully
+ * <em>refreshed</em> state.
+ * <p>Concrete implementations should register annotation configuration
+ * processors with bean factories of
+ * {@link ApplicationContext application contexts} loaded by this
+ * {@code SmartContextLoader}. Beans will therefore automatically be
+ * candidates for annotation-based dependency injection using
+ * {@link org.springframework.beans.factory.annotation.Autowired @Autowired},
+ * {@link javax.annotation.Resource @Resource}, and
+ * {@link javax.inject.Inject @Inject}. In addition, concrete implementations
+ * should set the active bean definition profiles in the context's
+ * {@link org.springframework.core.env.Environment Environment}.
+ * <p>Any {@code ApplicationContext} loaded by a
+ * {@code SmartContextLoader} <strong>must</strong> register a JVM
+ * shutdown hook for itself. Unless the context gets closed early, all context
+ * instances will be automatically closed on JVM shutdown. This allows for
+ * freeing of external resources held by beans within the context (e.g.,
+ * temporary files).
+ * @param mergedConfig the merged context configuration to use to load the
+ * application context
+ * @return a new application context
+ * @throws Exception if context loading failed
+ * @see #processContextConfiguration(ContextConfigurationAttributes)
+ * @see org.springframework.context.annotation.AnnotationConfigUtils
+ * #registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
+ * @see MergedContextConfiguration#getActiveProfiles()
+ * @see org.springframework.context.ConfigurableApplicationContext#getEnvironment()
+ */
+ ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception;
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContext.java b/spring-test/src/main/java/org/springframework/test/context/TestContext.java
new file mode 100644
index 00000000..9324340f
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/TestContext.java
@@ -0,0 +1,220 @@
+/*
+ * 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.context;
+
+import java.lang.reflect.Method;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.AttributeAccessorSupport;
+import org.springframework.core.style.ToStringCreator;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
+import org.springframework.util.Assert;
+
+/**
+ * {@code TestContext} encapsulates the context in which a test is executed,
+ * agnostic of the actual testing framework in use.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class TestContext extends AttributeAccessorSupport {
+
+ private static final long serialVersionUID = -5827157174866681233L;
+
+ private static final Log logger = LogFactory.getLog(TestContext.class);
+
+ private final ContextCache contextCache;
+
+ private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
+
+ private final MergedContextConfiguration mergedContextConfiguration;
+
+ private final Class<?> testClass;
+
+ private Object testInstance;
+
+ private Method testMethod;
+
+ private Throwable testException;
+
+
+ /**
+ * Delegates to {@link #TestContext(Class, ContextCache, String)} with a
+ * value of {@code null} for the default {@code ContextLoader} class name.
+ */
+ TestContext(Class<?> testClass, ContextCache contextCache) {
+ this(testClass, contextCache, null);
+ }
+
+ /**
+ * Construct a new test context for the supplied {@linkplain Class test class}
+ * and {@linkplain ContextCache context cache} and parse the corresponding
+ * {@link ContextConfiguration &#064;ContextConfiguration} or
+ * {@link ContextHierarchy &#064;ContextHierarchy} annotation, if present.
+ * <p>If the supplied class name for the default {@code ContextLoader}
+ * is {@code null} or <em>empty</em> and no concrete {@code ContextLoader}
+ * class is explicitly supplied via {@code @ContextConfiguration}, a
+ * {@link org.springframework.test.context.support.DelegatingSmartContextLoader
+ * DelegatingSmartContextLoader} or
+ * {@link org.springframework.test.context.web.WebDelegatingSmartContextLoader
+ * WebDelegatingSmartContextLoader} will be used instead.
+ * @param testClass the test class for which the test context should be
+ * constructed (must not be {@code null})
+ * @param contextCache the context cache from which the constructed test
+ * context should retrieve application contexts (must not be {@code null})
+ * @param defaultContextLoaderClassName the name of the default
+ * {@code ContextLoader} class to use (may be {@code null})
+ */
+ TestContext(Class<?> testClass, ContextCache contextCache, String defaultContextLoaderClassName) {
+ Assert.notNull(testClass, "Test class must not be null");
+ Assert.notNull(contextCache, "ContextCache must not be null");
+
+ this.testClass = testClass;
+ this.contextCache = contextCache;
+ this.cacheAwareContextLoaderDelegate = new CacheAwareContextLoaderDelegate(contextCache);
+
+ MergedContextConfiguration mergedContextConfiguration;
+
+ if (testClass.isAnnotationPresent(ContextConfiguration.class) ||
+ testClass.isAnnotationPresent(ContextHierarchy.class)) {
+ mergedContextConfiguration = ContextLoaderUtils.buildMergedContextConfiguration(testClass,
+ defaultContextLoaderClassName, cacheAwareContextLoaderDelegate);
+ }
+ else {
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format(
+ "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]",
+ testClass.getName()));
+ }
+ mergedContextConfiguration = new MergedContextConfiguration(testClass, null, null, null, null);
+ }
+
+ this.mergedContextConfiguration = mergedContextConfiguration;
+ }
+
+
+ /**
+ * Get the {@link ApplicationContext application context} for this test
+ * context, possibly cached.
+ * @return the application context
+ * @throws IllegalStateException if an error occurs while retrieving the
+ * application context
+ */
+ public ApplicationContext getApplicationContext() {
+ return this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration);
+ }
+
+ /**
+ * Get the {@link Class test class} for this test context.
+ * @return the test class (never {@code null})
+ */
+ public Class<?> getTestClass() {
+ return this.testClass;
+ }
+
+ /**
+ * Get the current {@link Object test instance} for this test context.
+ * <p>Note: this is a mutable property.
+ * @return the current test instance (may be {@code null})
+ * @see #updateState(Object, Method, Throwable)
+ */
+ public Object getTestInstance() {
+ return this.testInstance;
+ }
+
+ /**
+ * Get the current {@link Method test method} for this test context.
+ * <p>Note: this is a mutable property.
+ * @return the current test method (may be {@code null})
+ * @see #updateState(Object, Method, Throwable)
+ */
+ public Method getTestMethod() {
+ return this.testMethod;
+ }
+
+ /**
+ * Get the {@link Throwable exception} that was thrown during execution of
+ * the {@link #getTestMethod() test method}.
+ * <p>Note: this is a mutable property.
+ * @return the exception that was thrown, or {@code null} if no
+ * exception was thrown
+ * @see #updateState(Object, Method, Throwable)
+ */
+ public Throwable getTestException() {
+ return this.testException;
+ }
+
+ /**
+ * Call this method to signal that the {@linkplain ApplicationContext application
+ * context} associated with this test context is <em>dirty</em> and should be
+ * discarded. Do this if a test has modified the context &mdash; for example,
+ * by replacing a bean definition or modifying the state of a singleton bean.
+ * @deprecated as of Spring 3.2.2; use
+ * {@link #markApplicationContextDirty(DirtiesContext.HierarchyMode)} instead.
+ */
+ @Deprecated
+ public void markApplicationContextDirty() {
+ markApplicationContextDirty((HierarchyMode) null);
+ }
+
+ /**
+ * Call this method to signal that the {@linkplain ApplicationContext application
+ * context} associated with this test context is <em>dirty</em> and should be
+ * discarded. Do this if a test has modified the context &mdash; for example,
+ * by replacing a bean definition or modifying the state of a singleton bean.
+ * @param hierarchyMode the context cache clearing mode to be applied if the
+ * context is part of a hierarchy (may be {@code null})
+ */
+ public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
+ synchronized (this.contextCache) {
+ this.contextCache.remove(this.mergedContextConfiguration, hierarchyMode);
+ }
+ }
+
+ /**
+ * Update this test context to reflect the state of the currently executing test.
+ * @param testInstance the current test instance (may be {@code null})
+ * @param testMethod the current test method (may be {@code null})
+ * @param testException the exception that was thrown in the test method, or
+ * {@code null} if no exception was thrown
+ */
+ void updateState(Object testInstance, Method testMethod, Throwable testException) {
+ this.testInstance = testInstance;
+ this.testMethod = testMethod;
+ this.testException = testException;
+ }
+
+
+ /**
+ * Provide a String representation of this test context's state.
+ */
+ @Override
+ public String toString() {
+ return new ToStringCreator(this)
+ .append("testClass", this.testClass)
+ .append("testInstance", this.testInstance)
+ .append("testMethod", this.testMethod)
+ .append("testException", this.testException)
+ .append("mergedContextConfiguration", this.mergedContextConfiguration)
+ .toString();
+ }
+
+}
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
new file mode 100644
index 00000000..4cf8b13f
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/TestContextManager.java
@@ -0,0 +1,463 @@
+/*
+ * 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.context;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.BeanInstantiationException;
+import org.springframework.beans.BeanUtils;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * {@code TestContextManager} is the main entry point into the <em>Spring
+ * TestContext Framework</em>, which provides support for loading and accessing
+ * {@link org.springframework.context.ApplicationContext application contexts},
+ * dependency injection of test instances,
+ * {@link org.springframework.transaction.annotation.Transactional transactional}
+ * execution of test methods, etc.
+ *
+ * <p>Specifically, a {@code TestContextManager} is responsible for managing a
+ * single {@link TestContext} and signaling events to all registered
+ * {@link TestExecutionListener TestExecutionListeners} at well defined test
+ * execution points:
+ *
+ * <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>
+ * <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>
+ * <li>{@link #afterTestMethod(Object, Method, Throwable) after test method
+ * execution}: after any <em>after methods</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
+ * 4's {@link org.junit.AfterClass @AfterClass})</li>
+ * </ul>
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see TestContext
+ * @see TestExecutionListeners
+ * @see ContextConfiguration
+ * @see org.springframework.test.context.transaction.TransactionConfiguration
+ */
+public class TestContextManager {
+
+ private static final String[] DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES = new String[] {
+ "org.springframework.test.context.web.ServletTestExecutionListener",
+ "org.springframework.test.context.support.DependencyInjectionTestExecutionListener",
+ "org.springframework.test.context.support.DirtiesContextTestExecutionListener",
+ "org.springframework.test.context.transaction.TransactionalTestExecutionListener" };
+
+ private static final Log logger = LogFactory.getLog(TestContextManager.class);
+
+ /**
+ * Cache of Spring application contexts.
+ * <p>This needs to be static, since test instances may be destroyed and
+ * recreated between invocations of individual test methods, as is the case
+ * with JUnit.
+ */
+ static final ContextCache contextCache = new ContextCache();
+
+ private final TestContext testContext;
+
+ private final List<TestExecutionListener> testExecutionListeners = new ArrayList<TestExecutionListener>();
+
+
+ /**
+ * Construct a new {@code TestContextManager} for the specified {@linkplain Class test class}
+ * and automatically {@link #registerTestExecutionListeners register} the
+ * {@link TestExecutionListener TestExecutionListeners} configured for the test class
+ * via the {@link TestExecutionListeners &#064;TestExecutionListeners} annotation.
+ * <p>Delegates to {@link #TestContextManager(Class, String)} with a value of
+ * {@code null} for the default {@code ContextLoader} class name.
+ */
+ public TestContextManager(Class<?> testClass) {
+ this(testClass, null);
+ }
+
+ /**
+ * Construct a new {@code TestContextManager} for the specified {@linkplain Class test class}
+ * and automatically {@link #registerTestExecutionListeners register} the
+ * {@link TestExecutionListener TestExecutionListeners} configured for the test class
+ * via the {@link TestExecutionListeners &#064;TestExecutionListeners} annotation.
+ * @param testClass the test class to be managed
+ * @param defaultContextLoaderClassName the name of the default {@code ContextLoader} class
+ * to use (may be {@code null})
+ * @see #registerTestExecutionListeners
+ */
+ public TestContextManager(Class<?> testClass, String defaultContextLoaderClassName) {
+ this.testContext = new TestContext(testClass, contextCache, defaultContextLoaderClassName);
+ registerTestExecutionListeners(retrieveTestExecutionListeners(testClass));
+ }
+
+
+ /**
+ * Get the {@link TestContext} managed by this {@code TestContextManager}.
+ */
+ protected final TestContext getTestContext() {
+ return this.testContext;
+ }
+
+ /**
+ * Register the supplied {@link TestExecutionListener TestExecutionListeners}
+ * by appending them to the set of listeners used by this {@code TestContextManager}.
+ */
+ public void registerTestExecutionListeners(TestExecutionListener... testExecutionListeners) {
+ for (TestExecutionListener listener : testExecutionListeners) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Registering TestExecutionListener: " + listener);
+ }
+ this.testExecutionListeners.add(listener);
+ }
+ }
+
+ /**
+ * Get the current {@link TestExecutionListener TestExecutionListeners}
+ * registered for this {@code TestContextManager}.
+ * <p>Allows for modifications, e.g. adding a listener to the beginning of the list.
+ * However, make sure to keep the list stable while actually executing tests.
+ */
+ public final List<TestExecutionListener> getTestExecutionListeners() {
+ return this.testExecutionListeners;
+ }
+
+ /**
+ * Get a copy of the {@link TestExecutionListener TestExecutionListeners}
+ * registered for this {@code TestContextManager} in reverse order.
+ */
+ private List<TestExecutionListener> getReversedTestExecutionListeners() {
+ List<TestExecutionListener> listenersReversed = new ArrayList<TestExecutionListener>(getTestExecutionListeners());
+ Collections.reverse(listenersReversed);
+ return listenersReversed;
+ }
+
+ /**
+ * Retrieve an array of newly instantiated {@link TestExecutionListener TestExecutionListeners}
+ * for the specified {@link Class class}. If {@link TestExecutionListeners @TestExecutionListeners}
+ * is not <em>present</em> on the supplied class, the default listeners will be returned.
+ * <p>Note that the {@link TestExecutionListeners#inheritListeners() inheritListeners} flag of
+ * {@link TestExecutionListeners @TestExecutionListeners} will be taken into consideration.
+ * Specifically, if the {@code inheritListeners} flag is set to {@code true}, listeners
+ * defined in the annotated class will be appended to the listeners defined in superclasses.
+ * @param clazz the test class for which the listeners should be retrieved
+ * @return an array of TestExecutionListeners for the specified class
+ */
+ private TestExecutionListener[] retrieveTestExecutionListeners(Class<?> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ Class<TestExecutionListeners> annotationType = TestExecutionListeners.class;
+ List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>();
+ Class<?> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz);
+
+ // Use defaults?
+ if (declaringClass == null) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("@TestExecutionListeners is not present for class [" + clazz.getName() + "]: using defaults.");
+ }
+ classesList.addAll(getDefaultTestExecutionListenerClasses());
+ }
+ else {
+ // Traverse the class hierarchy...
+ while (declaringClass != null) {
+ TestExecutionListeners testExecutionListeners = declaringClass.getAnnotation(annotationType);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Retrieved @TestExecutionListeners [" + testExecutionListeners +
+ "] for declaring class [" + declaringClass.getName() + "].");
+ }
+
+ Class<? extends TestExecutionListener>[] valueListenerClasses = testExecutionListeners.value();
+ Class<? extends TestExecutionListener>[] listenerClasses = testExecutionListeners.listeners();
+ if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) {
+ throw new IllegalStateException(String.format("Class [%s] configured with @TestExecutionListeners' " +
+ "'value' [%s] and 'listeners' [%s] attributes. Use one or the other, but not both.",
+ declaringClass.getName(), ObjectUtils.nullSafeToString(valueListenerClasses),
+ ObjectUtils.nullSafeToString(listenerClasses)));
+ }
+ else if (!ObjectUtils.isEmpty(valueListenerClasses)) {
+ listenerClasses = valueListenerClasses;
+ }
+
+ if (listenerClasses != null) {
+ classesList.addAll(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses));
+ }
+ declaringClass = (testExecutionListeners.inheritListeners() ? AnnotationUtils.findAnnotationDeclaringClass(
+ annotationType, declaringClass.getSuperclass()) : null);
+ }
+ }
+
+ List<TestExecutionListener> listeners = new ArrayList<TestExecutionListener>(classesList.size());
+ for (Class<? extends TestExecutionListener> listenerClass : classesList) {
+ NoClassDefFoundError ncdfe = null;
+ try {
+ listeners.add(BeanUtils.instantiateClass(listenerClass));
+ }
+ catch (NoClassDefFoundError err) {
+ ncdfe = err;
+ }
+ catch (BeanInstantiationException ex) {
+ if (ex.getCause() instanceof NoClassDefFoundError) {
+ ncdfe = (NoClassDefFoundError) ex.getCause();
+ }
+ }
+ if (ncdfe != null) {
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format("Could not instantiate TestExecutionListener [%s]. " +
+ "Specify custom listener classes or make the default listener classes " +
+ "(and their required dependencies) available. Offending class: [%s]",
+ listenerClass.getName(), ncdfe.getMessage()));
+ }
+ }
+ }
+ return listeners.toArray(new TestExecutionListener[listeners.size()]);
+ }
+
+ /**
+ * Determine the default {@link TestExecutionListener} classes.
+ */
+ @SuppressWarnings("unchecked")
+ protected Set<Class<? extends TestExecutionListener>> getDefaultTestExecutionListenerClasses() {
+ Set<Class<? extends TestExecutionListener>> defaultListenerClasses = new LinkedHashSet<Class<? extends TestExecutionListener>>();
+ ClassLoader cl = getClass().getClassLoader();
+ for (String className : DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES) {
+ try {
+ defaultListenerClasses.add((Class<? extends TestExecutionListener>) ClassUtils.forName(className, cl));
+ }
+ catch (Throwable ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Could not load default TestExecutionListener class [" + className +
+ "]. Specify custom listener classes or make the default listener classes available.", ex);
+ }
+ }
+ }
+ return defaultListenerClasses;
+ }
+
+ /**
+ * Hook for pre-processing a test class <em>before</em> execution of any
+ * tests within the class. Should be called prior to any framework-specific
+ * <em>before class methods</em> (e.g., methods annotated with JUnit's
+ * {@link org.junit.BeforeClass @BeforeClass}).
+ * <p>An attempt will be made to give each registered
+ * {@link TestExecutionListener} a chance to pre-process the test class
+ * execution. If a listener throws an exception, however, the remaining
+ * registered listeners will <strong>not</strong> be called.
+ * @throws Exception if a registered TestExecutionListener throws an
+ * exception
+ * @see #getTestExecutionListeners()
+ */
+ public void beforeTestClass() throws Exception {
+ Class<?> testClass = getTestContext().getTestClass();
+ if (logger.isTraceEnabled()) {
+ logger.trace("beforeTestClass(): class [" + testClass.getName() + "]");
+ }
+ getTestContext().updateState(null, null, null);
+
+ for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
+ 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;
+ }
+ }
+ }
+
+ /**
+ * Hook for preparing a test instance prior to execution of any individual
+ * test methods, for example for injecting dependencies, etc. Should be
+ * called immediately after instantiation of the test instance.
+ * <p>The managed {@link TestContext} will be updated with the supplied
+ * {@code testInstance}.
+ * <p>An attempt will be made to give each registered
+ * {@link TestExecutionListener} a chance to prepare the test instance. If a
+ * listener throws an exception, however, the remaining registered listeners
+ * will <strong>not</strong> be called.
+ * @param testInstance the test instance to prepare (never {@code null})
+ * @throws Exception if a registered TestExecutionListener throws an exception
+ * @see #getTestExecutionListeners()
+ */
+ public void prepareTestInstance(Object testInstance) throws Exception {
+ Assert.notNull(testInstance, "testInstance must not be null");
+ if (logger.isTraceEnabled()) {
+ logger.trace("prepareTestInstance(): instance [" + testInstance + "]");
+ }
+ getTestContext().updateState(testInstance, null, null);
+
+ for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
+ try {
+ testExecutionListener.prepareTestInstance(getTestContext());
+ }
+ catch (Exception ex) {
+ logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener +
+ "] to prepare test instance [" + testInstance + "]", ex);
+ throw ex;
+ }
+ }
+ }
+
+ /**
+ * Hook for pre-processing a test <em>before</em> execution of the supplied
+ * {@link Method test method}, for example for setting up test fixtures,
+ * starting a transaction, etc. Should be called prior to any
+ * framework-specific <em>before methods</em> (e.g., methods annotated with
+ * JUnit's {@link org.junit.Before @Before}).
+ * <p>The managed {@link TestContext} will be updated with the supplied
+ * {@code testInstance} and {@code testMethod}.
+ * <p>An attempt will be made to give each registered
+ * {@link TestExecutionListener} a chance to pre-process the test method
+ * execution. If a listener throws an exception, however, the remaining
+ * registered listeners will <strong>not</strong> be called.
+ * @param testInstance the current test instance (never {@code null})
+ * @param testMethod the test method which is about to be executed on the
+ * test instance
+ * @throws Exception if a registered TestExecutionListener throws an exception
+ * @see #getTestExecutionListeners()
+ */
+ public void beforeTestMethod(Object testInstance, Method testMethod) throws Exception {
+ Assert.notNull(testInstance, "Test instance must not be null");
+ if (logger.isTraceEnabled()) {
+ logger.trace("beforeTestMethod(): instance [" + testInstance + "], method [" + testMethod + "]");
+ }
+ getTestContext().updateState(testInstance, testMethod, null);
+
+ for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
+ 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;
+ }
+ }
+ }
+
+ /**
+ * Hook for post-processing a test <em>after</em> execution of the supplied
+ * {@link Method test method}, for example for tearing down test fixtures,
+ * ending a transaction, etc. Should be called after any framework-specific
+ * <em>after methods</em> (e.g., methods annotated with JUnit's
+ * {@link org.junit.After @After}).
+ * <p>The managed {@link TestContext} will be updated with the supplied
+ * {@code testInstance}, {@code testMethod}, and
+ * {@code exception}.
+ * <p>Each registered {@link TestExecutionListener} will be given a chance to
+ * post-process the test method execution. If a listener throws an
+ * exception, the remaining registered listeners will still be called, but
+ * the first exception thrown will be tracked and rethrown after all
+ * listeners have executed. Note that registered listeners will be executed
+ * in the opposite order in which they were registered.
+ * @param testInstance the current test instance (never {@code null})
+ * @param testMethod the test method which has just been executed on the
+ * test instance
+ * @param exception the exception that was thrown during execution of the
+ * test method or by a TestExecutionListener, or {@code null} if none
+ * was thrown
+ * @throws Exception if a registered TestExecutionListener throws an exception
+ * @see #getTestExecutionListeners()
+ */
+ public void afterTestMethod(Object testInstance, Method testMethod, Throwable exception) throws Exception {
+ Assert.notNull(testInstance, "testInstance must not be null");
+ if (logger.isTraceEnabled()) {
+ logger.trace("afterTestMethod(): instance [" + testInstance + "], method [" + testMethod +
+ "], exception [" + exception + "]");
+ }
+ getTestContext().updateState(testInstance, testMethod, exception);
+
+ Exception 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);
+ if (afterTestMethodException == null) {
+ afterTestMethodException = ex;
+ }
+ }
+ }
+ if (afterTestMethodException != null) {
+ throw afterTestMethodException;
+ }
+ }
+
+ /**
+ * Hook for post-processing a test class <em>after</em> execution of all
+ * tests within the class. Should be called after any framework-specific
+ * <em>after class methods</em> (e.g., methods annotated with JUnit's
+ * {@link org.junit.AfterClass @AfterClass}).
+ * <p>Each registered {@link TestExecutionListener} will be given a chance to
+ * post-process the test class. If a listener throws an exception, the
+ * remaining registered listeners will still be called, but the first
+ * exception thrown will be tracked and rethrown after all listeners have
+ * executed. Note that registered listeners will be executed in the opposite
+ * order in which they were registered.
+ * @throws Exception if a registered TestExecutionListener throws an exception
+ * @see #getTestExecutionListeners()
+ */
+ public void afterTestClass() throws Exception {
+ Class<?> testClass = getTestContext().getTestClass();
+ if (logger.isTraceEnabled()) {
+ logger.trace("afterTestClass(): class [" + testClass.getName() + "]");
+ }
+ getTestContext().updateState(null, null, null);
+
+ Exception 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);
+ if (afterTestClassException == null) {
+ afterTestClassException = ex;
+ }
+ }
+ }
+ if (afterTestClassException != null) {
+ throw 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
new file mode 100644
index 00000000..a052523f
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListener.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+/**
+ * {@code TestExecutionListener} defines a <em>listener</em> API for reacting to
+ * test execution events published by the {@link TestContextManager} with which
+ * the listener is registered.
+ * <p>
+ * Concrete implementations must provide a {@code public} no-args constructor,
+ * so that listeners can be instantiated transparently by tools and configuration
+ * mechanisms.
+ * <p>
+ * Spring provides the following out-of-the-box implementations:
+ * <ul>
+ * <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener
+ * DependencyInjectionTestExecutionListener}</li>
+ * <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener
+ * DirtiesContextTestExecutionListener}</li>
+ * <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
+ * TransactionalTestExecutionListener}</li>
+ * <li>{@link org.springframework.test.context.web.ServletTestExecutionListener
+ * ServletTestExecutionListener}</li>
+ * </ul>
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public interface TestExecutionListener {
+
+ /**
+ * Pre-processes a test class <em>before</em> execution of all tests within
+ * the class.
+ * <p>
+ * This method should be called immediately before framework-specific
+ * <em>before class</em> lifecycle callbacks.
+ * <p>
+ * If a given testing framework (e.g., JUnit 3.8) does not support
+ * <em>before class</em> lifecycle callbacks, this method will not be called
+ * for that framework.
+ *
+ * @param testContext the test context for the test; never {@code null}
+ * @throws Exception allows any exception to propagate
+ */
+ void beforeTestClass(TestContext testContext) throws Exception;
+
+ /**
+ * Prepares the {@link Object test instance} of the supplied
+ * {@link TestContext test context}, for example by injecting dependencies.
+ * <p>
+ * This method should be called immediately after instantiation of the test
+ * instance but prior to any framework-specific lifecycle callbacks.
+ *
+ * @param testContext the test context for the test; never {@code null}
+ * @throws Exception allows any exception to propagate
+ */
+ void prepareTestInstance(TestContext testContext) throws Exception;
+
+ /**
+ * Pre-processes a test <em>before</em> execution of the
+ * {@link java.lang.reflect.Method test method} in the supplied
+ * {@link TestContext test context}, for example by setting up test
+ * fixtures.
+ * <p>
+ * This method should be called immediately prior to framework-specific
+ * <em>before</em> lifecycle callbacks.
+ *
+ * @param testContext the test context in which the test method will be
+ * executed; never {@code null}
+ * @throws Exception allows any exception to propagate
+ */
+ void beforeTestMethod(TestContext testContext) throws Exception;
+
+ /**
+ * Post-processes a test <em>after</em> execution of the
+ * {@link java.lang.reflect.Method test method} in the supplied
+ * {@link TestContext test context}, for example by tearing down test
+ * fixtures.
+ * <p>
+ * This method should be called immediately after framework-specific
+ * <em>after</em> lifecycle callbacks.
+ *
+ * @param testContext the test context in which the test method was
+ * executed; never {@code null}
+ * @throws Exception allows any exception to propagate
+ */
+ void afterTestMethod(TestContext testContext) throws Exception;
+
+ /**
+ * Post-processes a test class <em>after</em> execution of all tests within
+ * the class.
+ * <p>
+ * This method should be called immediately after framework-specific
+ * <em>after class</em> lifecycle callbacks.
+ * <p>
+ * If a given testing framework (e.g., JUnit 3.8) does not support
+ * <em>after class</em> lifecycle callbacks, this method will not be called
+ * for that framework.
+ *
+ * @param testContext the test context for the test; never {@code null}
+ * @throws Exception allows any exception to propagate
+ */
+ void afterTestClass(TestContext testContext) throws Exception;
+
+}
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
new file mode 100644
index 00000000..388f66b5
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/TestExecutionListeners.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+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;
+
+/**
+ * {@code TestExecutionListeners} defines class-level metadata for
+ * configuring which {@link TestExecutionListener TestExecutionListeners} should
+ * be registered with a {@link TestContextManager}.
+ *
+ * <p>Typically, {@code @TestExecutionListeners} will be used in conjunction with
+ * {@link ContextConfiguration @ContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see TestExecutionListener
+ * @see TestContextManager
+ * @see ContextConfiguration
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface TestExecutionListeners {
+
+ /**
+ * The {@link TestExecutionListener TestExecutionListeners} to register with
+ * a {@link TestContextManager}.
+ *
+ * @see org.springframework.test.context.web.ServletTestExecutionListener
+ * @see org.springframework.test.context.support.DependencyInjectionTestExecutionListener
+ * @see org.springframework.test.context.support.DirtiesContextTestExecutionListener
+ * @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
+ */
+ Class<? extends TestExecutionListener>[] listeners() default {};
+
+ /**
+ * Alias for {@link #listeners() listeners}.
+ */
+ Class<? extends TestExecutionListener>[] value() default {};
+
+ /**
+ * Whether or not {@link #value() TestExecutionListeners} from superclasses
+ * should be <em>inherited</em>.
+ * <p>
+ * The default value is {@code true}, which means that an annotated
+ * class will <em>inherit</em> the listeners defined by an annotated
+ * superclass. Specifically, the listeners for an annotated class will be
+ * appended to the list of listeners defined by an annotated superclass.
+ * Thus, subclasses have the option of <em>extending</em> the list of
+ * listeners. In the following example, {@code AbstractBaseTest} will
+ * be configured with {@code DependencyInjectionTestExecutionListener}
+ * and {@code DirtiesContextTestExecutionListener}; whereas,
+ * {@code TransactionalTest} will be configured with
+ * {@code DependencyInjectionTestExecutionListener},
+ * {@code DirtiesContextTestExecutionListener}, <strong>and</strong>
+ * {@code TransactionalTestExecutionListener}, in that order.
+ *
+ * <pre class="code">
+ * &#064;TestExecutionListeners({
+ * DependencyInjectionTestExecutionListener.class,
+ * DirtiesContextTestExecutionListener.class
+ * })
+ * public abstract class AbstractBaseTest {
+ * // ...
+ * }
+ *
+ * &#064;TestExecutionListeners(TransactionalTestExecutionListener.class)
+ * public class TransactionalTest extends AbstractBaseTest {
+ * // ...
+ * }</pre>
+ *
+ * <p>
+ * If {@code inheritListeners} is set to {@code false}, the listeners for the
+ * annotated class will <em>shadow</em> and effectively replace any listeners
+ * defined by a superclass.
+ */
+ boolean inheritListeners() default true;
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit38/AbstractJUnit38SpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/junit38/AbstractJUnit38SpringContextTests.java
new file mode 100644
index 00000000..732c1e0b
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit38/AbstractJUnit38SpringContextTests.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright 2002-2012 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.junit38;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.test.annotation.ExpectedException;
+import org.springframework.test.annotation.IfProfileValue;
+import org.springframework.test.annotation.ProfileValueSource;
+import org.springframework.test.annotation.ProfileValueUtils;
+import org.springframework.test.annotation.Repeat;
+import org.springframework.test.annotation.Timed;
+import org.springframework.test.context.TestContextManager;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
+
+/**
+ * <p>
+ * Abstract base {@link TestCase} which integrates the <i>Spring TestContext
+ * Framework</i> and explicit {@link ApplicationContext} testing support in a
+ * <strong>JUnit 3.8</strong> environment.
+ * </p>
+ * <p>
+ * Concrete subclasses:
+ * </p>
+ * <ul>
+ * <li>Typically declare a class-level
+ * {@link org.springframework.test.context.ContextConfiguration
+ * &#064;ContextConfiguration} annotation to configure the
+ * {@link ApplicationContext application context}
+ * {@link org.springframework.test.context.ContextConfiguration#locations()
+ * resource locations}. <i>If your test does not need to load an application
+ * context, you may choose to omit the
+ * {@link org.springframework.test.context.ContextConfiguration
+ * &#064;ContextConfiguration} declaration and configure the appropriate
+ * {@link org.springframework.test.context.TestExecutionListener
+ * TestExecutionListeners} manually.</i></li>
+ * <li>Must declare public constructors which match the signatures of
+ * {@link #AbstractJUnit38SpringContextTests()
+ * AbstractJUnit38SpringContextTests()} and
+ * {@link #AbstractJUnit38SpringContextTests(String)
+ * AbstractJUnit38SpringContextTests(String)} and delegate to
+ * {@code super();} and {@code super(name);} respectively.</li>
+ * </ul>
+ * <p>
+ * The following list constitutes all annotations currently supported directly
+ * by {@code AbstractJUnit38SpringContextTests}. <i>(Note that additional
+ * annotations may be supported by various
+ * {@link org.springframework.test.context.TestExecutionListener
+ * TestExecutionListeners})</i>
+ * </p>
+ * <ul>
+ * <li>{@link org.springframework.test.annotation.DirtiesContext
+ * &#064;DirtiesContext} (via the configured
+ * {@link DirtiesContextTestExecutionListener}; only supported on methods for
+ * JUnit 3.8)</li>
+ * <li>
+ * {@link org.springframework.test.annotation.ProfileValueSourceConfiguration
+ * &#064;ProfileValueSourceConfiguration}</li>
+ * <li>{@link IfProfileValue &#064;IfProfileValue}</li>
+ * <li>{@link ExpectedException &#064;ExpectedException}</li>
+ * <li>{@link Timed &#064;Timed}</li>
+ * <li>{@link Repeat &#064;Repeat}</li>
+ * </ul>
+ * <p>
+ * JUnit 3.8 does not support <i>before class</i> or <i>after class</i>
+ * lifecycle callbacks. The following
+ * {@link org.springframework.test.context.TestExecutionListener
+ * TestExecutionListener} methods are therefore unsupported in a JUnit 3.8
+ * environment:
+ * <ul>
+ * <li>
+ * {@link org.springframework.test.context.TestExecutionListener#beforeTestClass(org.springframework.test.context.TestContext)
+ * beforeTestClass()}</li>
+ * <li>
+ * {@link org.springframework.test.context.TestExecutionListener#afterTestClass(org.springframework.test.context.TestContext)
+ * afterTestClass()}</li>
+ * </ul>
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see org.springframework.test.context.TestContext
+ * @see org.springframework.test.context.TestContextManager
+ * @see org.springframework.test.context.TestExecutionListeners
+ * @see AbstractTransactionalJUnit38SpringContextTests
+ * @see org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests
+ * @see org.springframework.test.context.testng.AbstractTestNGSpringContextTests
+ * @deprecated as of Spring 3.1, in favor of using
+ * {@link org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests AbstractJUnit4SpringContextTests}
+ */
+@Deprecated
+@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
+public abstract class AbstractJUnit38SpringContextTests extends TestCase implements ApplicationContextAware {
+
+ private static int disabledTestCount = 0;
+
+
+ /**
+ * Return the number of tests disabled in this environment.
+ */
+ public static int getDisabledTestCount() {
+ return disabledTestCount;
+ }
+
+
+ /**
+ * Logger available to subclasses.
+ */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /**
+ * The {@link ApplicationContext} that was injected into this test instance
+ * via {@link #setApplicationContext(ApplicationContext)}.
+ */
+ protected ApplicationContext applicationContext;
+
+ /**
+ * {@link ProfileValueSource} available to subclasses but primarily intended
+ * for internal use to provide support for {@link IfProfileValue
+ * &#064;IfProfileValue}.
+ */
+ protected final ProfileValueSource profileValueSource;
+
+ private final TestContextManager testContextManager;
+
+
+ /**
+ * Constructs a new AbstractJUnit38SpringContextTests instance; initializes
+ * the internal {@link TestContextManager} for the current test; and
+ * retrieves the configured (or default) {@link ProfileValueSource}.
+ */
+ public AbstractJUnit38SpringContextTests() {
+ super();
+ this.testContextManager = new TestContextManager(getClass());
+ this.profileValueSource = ProfileValueUtils.retrieveProfileValueSource(getClass());
+ }
+
+ /**
+ * Constructs a new AbstractJUnit38SpringContextTests instance with the
+ * supplied {@code name}; initializes the internal
+ * {@link TestContextManager} for the current test; and retrieves the
+ * configured (or default) {@link ProfileValueSource}.
+ *
+ * @param name the name of the current test to execute
+ */
+ public AbstractJUnit38SpringContextTests(String name) {
+ super(name);
+ this.testContextManager = new TestContextManager(getClass());
+ this.profileValueSource = ProfileValueUtils.retrieveProfileValueSource(getClass());
+ }
+
+ /**
+ * Sets the {@link ApplicationContext} to be used by this test instance,
+ * provided via {@link ApplicationContextAware} semantics.
+ */
+ public final void setApplicationContext(final ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ /**
+ * Runs the <em>Spring TestContext Framework</em> test sequence.
+ * <p>
+ * In addition to standard {@link TestCase#runBare()} semantics, this
+ * implementation performs the following:
+ * <ul>
+ * <li>Calls {@link TestContextManager#prepareTestInstance(Object)
+ * prepareTestInstance()},
+ * {@link TestContextManager#beforeTestMethod(Object,Method)
+ * beforeTestMethod()}, and
+ * {@link TestContextManager#afterTestMethod(Object,Method,Throwable)
+ * afterTestMethod()} on this test's {@link TestContextManager} at the
+ * appropriate test execution points.</li>
+ * <li>Provides support for {@link IfProfileValue &#064;IfProfileValue}.</li>
+ * <li>Provides support for {@link Repeat &#064;Repeat}.</li>
+ * <li>Provides support for {@link Timed &#064;Timed}.</li>
+ * <li>Provides support for {@link ExpectedException
+ * &#064;ExpectedException}.</li>
+ * </ul>
+ *
+ * @see ProfileValueUtils#isTestEnabledInThisEnvironment
+ */
+ @Override
+ public void runBare() throws Throwable {
+ this.testContextManager.prepareTestInstance(this);
+ final Method testMethod = getTestMethod();
+
+ if (!ProfileValueUtils.isTestEnabledInThisEnvironment(this.profileValueSource, testMethod, getClass())) {
+ recordDisabled(testMethod);
+ return;
+ }
+
+ runTestTimed(new TestExecutionCallback() {
+
+ public void run() throws Throwable {
+ runManaged(testMethod);
+ }
+ }, testMethod);
+ }
+
+ /**
+ * Get the current test method.
+ */
+ private Method getTestMethod() {
+ assertNotNull("TestCase.getName() cannot be null", getName());
+ Method testMethod = null;
+ try {
+ testMethod = getClass().getMethod(getName(), (Class[]) null);
+ }
+ catch (NoSuchMethodException ex) {
+ fail("Method \"" + getName() + "\" not found");
+ }
+ if (!Modifier.isPublic(testMethod.getModifiers())) {
+ fail("Method \"" + getName() + "\" should be public");
+ }
+ return testMethod;
+ }
+
+ /**
+ * Runs a <em>timed</em> test via the supplied {@link TestExecutionCallback}
+ * , providing support for the {@link Timed &#064;Timed} annotation.
+ *
+ * @param tec the test execution callback to run
+ * @param testMethod the actual test method: used to retrieve the
+ * {@code timeout}
+ * @throws Throwable if any exception is thrown
+ * @see Timed
+ * @see #runTest
+ */
+ private void runTestTimed(TestExecutionCallback tec, Method testMethod) throws Throwable {
+ Timed timed = testMethod.getAnnotation(Timed.class);
+ if (timed == null) {
+ runTest(tec, testMethod);
+ }
+ else {
+ long startTime = System.currentTimeMillis();
+ try {
+ runTest(tec, testMethod);
+ }
+ finally {
+ long elapsed = System.currentTimeMillis() - startTime;
+ if (elapsed > timed.millis()) {
+ fail("Took " + elapsed + " ms; limit was " + timed.millis());
+ }
+ }
+ }
+ }
+
+ /**
+ * Runs a test via the supplied {@link TestExecutionCallback}, providing
+ * support for the {@link ExpectedException &#064;ExpectedException} and
+ * {@link Repeat &#064;Repeat} annotations.
+ *
+ * @param tec the test execution callback to run
+ * @param testMethod the actual test method: used to retrieve the
+ * {@link ExpectedException &#064;ExpectedException} and {@link Repeat
+ * &#064;Repeat} annotations
+ * @throws Throwable if any exception is thrown
+ * @see ExpectedException
+ * @see Repeat
+ */
+ private void runTest(TestExecutionCallback tec, Method testMethod) throws Throwable {
+ ExpectedException expectedExceptionAnnotation = testMethod.getAnnotation(ExpectedException.class);
+ boolean exceptionIsExpected = (expectedExceptionAnnotation != null && expectedExceptionAnnotation.value() != null);
+ Class<? extends Throwable> expectedException = (exceptionIsExpected ? expectedExceptionAnnotation.value()
+ : null);
+
+ Repeat repeat = testMethod.getAnnotation(Repeat.class);
+ int runs = ((repeat != null) && (repeat.value() > 1)) ? repeat.value() : 1;
+
+ for (int i = 0; i < runs; i++) {
+ try {
+ if (runs > 1 && this.logger.isInfoEnabled()) {
+ this.logger.info("Repetition " + (i + 1) + " of test " + testMethod.getName());
+ }
+ tec.run();
+ if (exceptionIsExpected) {
+ fail("Expected exception: " + expectedException.getName());
+ }
+ }
+ catch (Throwable ex) {
+ if (!exceptionIsExpected) {
+ throw ex;
+ }
+ if (!expectedException.isAssignableFrom(ex.getClass())) {
+ // Wrap the unexpected throwable with an explicit message.
+ AssertionFailedError assertionError = new AssertionFailedError("Unexpected exception, expected <"
+ + expectedException.getName() + "> but was <" + ex.getClass().getName() + ">");
+ assertionError.initCause(ex);
+ throw assertionError;
+ }
+ }
+ }
+ }
+
+ /**
+ * Calls {@link TestContextManager#beforeTestMethod(Object,Method)} and
+ * {@link TestContextManager#afterTestMethod(Object,Method,Throwable)} at
+ * the appropriate test execution points.
+ *
+ * @param testMethod the test method to run
+ * @throws Throwable if any exception is thrown
+ * @see #runBare()
+ * @see TestCase#runTest()
+ */
+ private void runManaged(Method testMethod) throws Throwable {
+ Throwable exception = null;
+ boolean reachedTest = false;
+
+ try {
+ this.testContextManager.beforeTestMethod(this, testMethod);
+ setUp();
+ reachedTest = true;
+ runTest();
+ }
+ catch (Throwable ex) {
+ exception = ex;
+ }
+ finally {
+ try {
+ if (reachedTest) {
+ tearDown();
+ }
+ }
+ catch (Throwable ex) {
+ if (exception == null) {
+ exception = ex;
+ }
+ }
+ finally {
+ try {
+ this.testContextManager.afterTestMethod(this, testMethod, exception);
+ }
+ catch (Throwable ex) {
+ if (exception == null) {
+ exception = ex;
+ }
+ }
+ }
+ }
+
+ if (exception != null) {
+ if (exception.getCause() instanceof AssertionError) {
+ exception = exception.getCause();
+ }
+ throw exception;
+ }
+ }
+
+ /**
+ * Records the supplied test method as <em>disabled</em> in the current
+ * environment by incrementing the total number of disabled tests and
+ * logging a debug message.
+ *
+ * @param testMethod the test method that is disabled.
+ * @see #getDisabledTestCount()
+ */
+ protected void recordDisabled(Method testMethod) {
+ disabledTestCount++;
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info("**** " + getClass().getName() + "." + getName() + "() is disabled in this environment. "
+ + "Total disabled tests = " + getDisabledTestCount());
+ }
+ }
+
+
+ /**
+ * Private inner class that defines a callback analogous to {@link Runnable}
+ * , just declaring Throwable.
+ */
+ private static interface TestExecutionCallback {
+
+ void run() throws Throwable;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit38/AbstractTransactionalJUnit38SpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/junit38/AbstractTransactionalJUnit38SpringContextTests.java
new file mode 100644
index 00000000..9b7fd03e
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit38/AbstractTransactionalJUnit38SpringContextTests.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2002-2012 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.junit38;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
+import org.springframework.test.jdbc.SimpleJdbcTestUtils;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * <p>
+ * Abstract {@link Transactional transactional} extension of
+ * {@link AbstractJUnit38SpringContextTests} which adds convenience
+ * functionality for JDBC access. Expects a {@link javax.sql.DataSource} bean
+ * and a {@link PlatformTransactionManager} bean to be defined in the Spring
+ * {@link ApplicationContext application context}.
+ * </p>
+ * <p>
+ * This class exposes a {@link SimpleJdbcTemplate} and provides an easy way to
+ * {@link #countRowsInTable(String) count the number of rows in a table} ,
+ * {@link #deleteFromTables(String...) delete from the database} , and
+ * {@link #executeSqlScript(String, boolean) execute SQL scripts} within a
+ * transaction.
+ * </p>
+ * <p>
+ * Concrete subclasses must fulfill the same requirements outlined in
+ * {@link AbstractJUnit38SpringContextTests}.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see AbstractJUnit38SpringContextTests
+ * @see org.springframework.test.context.ContextConfiguration
+ * @see org.springframework.test.context.TestExecutionListeners
+ * @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
+ * @see org.springframework.test.context.transaction.TransactionConfiguration
+ * @see org.springframework.transaction.annotation.Transactional
+ * @see org.springframework.test.annotation.NotTransactional
+ * @see org.springframework.test.annotation.Rollback
+ * @see org.springframework.test.context.transaction.BeforeTransaction
+ * @see org.springframework.test.context.transaction.AfterTransaction
+ * @see org.springframework.test.jdbc.SimpleJdbcTestUtils
+ * @see org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
+ * @see org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
+ * @deprecated as of Spring 3.1, in favor of using
+ * {@link org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests AbstractTransactionalJUnit4SpringContextTests}
+ */
+@Deprecated
+@TestExecutionListeners(TransactionalTestExecutionListener.class)
+@Transactional
+public abstract class AbstractTransactionalJUnit38SpringContextTests extends AbstractJUnit38SpringContextTests {
+
+ /**
+ * The SimpleJdbcTemplate that this base class manages, available to subclasses.
+ */
+ protected SimpleJdbcTemplate simpleJdbcTemplate;
+
+ private String sqlScriptEncoding;
+
+
+ /**
+ * Constructs a new AbstractTransactionalJUnit38SpringContextTests instance.
+ */
+ public AbstractTransactionalJUnit38SpringContextTests() {
+ super();
+ }
+
+ /**
+ * Constructs a new AbstractTransactionalJUnit38SpringContextTests instance
+ * with the supplied {@code name}.
+ * @param name the name of the current test to execute
+ */
+ public AbstractTransactionalJUnit38SpringContextTests(String name) {
+ super(name);
+ }
+
+
+ /**
+ * Set the DataSource, typically provided via Dependency Injection.
+ * @param dataSource The DataSource to inject
+ */
+ @Autowired
+ public void setDataSource(DataSource dataSource) {
+ this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ }
+
+ /**
+ * Specify the encoding for SQL scripts, if different from the platform encoding.
+ * @see #executeSqlScript
+ */
+ public void setSqlScriptEncoding(String sqlScriptEncoding) {
+ this.sqlScriptEncoding = sqlScriptEncoding;
+ }
+
+
+ /**
+ * Count the rows in the given table.
+ * @param tableName table name to count rows in
+ * @return the number of rows in the table
+ */
+ protected int countRowsInTable(String tableName) {
+ return SimpleJdbcTestUtils.countRowsInTable(this.simpleJdbcTemplate, tableName);
+ }
+
+ /**
+ * Convenience method for deleting all rows from the specified tables.
+ * Use with caution outside of a transaction!
+ * @param names the names of the tables from which to delete
+ * @return the total number of rows deleted from all specified tables
+ */
+ protected int deleteFromTables(String... names) {
+ return SimpleJdbcTestUtils.deleteFromTables(this.simpleJdbcTemplate, names);
+ }
+
+ /**
+ * Execute the given SQL script. Use with caution outside of a transaction!
+ * <p>The script will normally be loaded by classpath. There should be one statement
+ * per line. Any semicolons will be removed. <b>Do not use this method to execute
+ * DDL if you expect rollback.</b>
+ * @param sqlResourcePath the Spring resource path for the SQL script
+ * @param continueOnError whether or not to continue without throwing an
+ * exception in the event of an error
+ * @throws DataAccessException if there is an error executing a statement
+ * and continueOnError was {@code false}
+ */
+ protected void executeSqlScript(String sqlResourcePath, boolean continueOnError)
+ throws DataAccessException {
+
+ Resource resource = this.applicationContext.getResource(sqlResourcePath);
+ SimpleJdbcTestUtils.executeSqlScript(
+ this.simpleJdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding), continueOnError);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit38/package-info.java b/spring-test/src/main/java/org/springframework/test/context/junit38/package-info.java
new file mode 100644
index 00000000..22ead8d5
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit38/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * <p>Support classes for ApplicationContext-based and transactional
+ * tests run with JUnit 3.8 and the <em>Spring TestContext Framework</em>.</p>
+ */
+
+package org.springframework.test.context.junit38;
+
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
new file mode 100644
index 00000000..05d35d01
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractJUnit4SpringContextTests.java
@@ -0,0 +1,102 @@
+/*
+ * 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.context.junit4;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.junit.runner.RunWith;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.TestContextManager;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
+import org.springframework.test.context.web.ServletTestExecutionListener;
+
+/**
+ * Abstract base test class which integrates the <em>Spring TestContext
+ * Framework</em> with explicit {@link ApplicationContext} testing support in a
+ * <strong>JUnit 4.5+</strong> environment.
+ *
+ * <p>Concrete subclasses should typically declare a class-level
+ * {@link ContextConfiguration &#064;ContextConfiguration} annotation to
+ * configure the {@link ApplicationContext application context} {@link
+ * ContextConfiguration#locations() resource locations} or {@link
+ * ContextConfiguration#classes() annotated classes}. <em>If your test does not
+ * need to load an application context, you may choose to omit the {@link
+ * ContextConfiguration &#064;ContextConfiguration} declaration and to configure
+ * the appropriate {@link org.springframework.test.context.TestExecutionListener
+ * TestExecutionListeners} manually.</em>
+ *
+ * <p>The following {@link org.springframework.test.context.TestExecutionListener
+ * TestExecutionListeners} are configured by default:
+ *
+ * <ul>
+ * <li>{@link org.springframework.test.context.web.ServletTestExecutionListener}
+ * <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
+ * <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
+ * </ul>
+ *
+ * <p>Note: this class serves only as a convenience for extension. 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
+ * &#064;ContextConfiguration}, {@link TestExecutionListeners
+ * &#064;TestExecutionListeners}, etc.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see ContextConfiguration
+ * @see TestContext
+ * @see TestContextManager
+ * @see TestExecutionListeners
+ * @see ServletTestExecutionListener
+ * @see DependencyInjectionTestExecutionListener
+ * @see DirtiesContextTestExecutionListener
+ * @see AbstractTransactionalJUnit4SpringContextTests
+ * @see org.springframework.test.context.testng.AbstractTestNGSpringContextTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@TestExecutionListeners({ ServletTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
+ DirtiesContextTestExecutionListener.class })
+public abstract class AbstractJUnit4SpringContextTests implements ApplicationContextAware {
+
+ /**
+ * Logger available to subclasses.
+ */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /**
+ * The {@link ApplicationContext} that was injected into this test instance
+ * via {@link #setApplicationContext(ApplicationContext)}.
+ */
+ protected ApplicationContext applicationContext;
+
+
+ /**
+ * Set the {@link ApplicationContext} to be used by this test instance,
+ * provided via {@link ApplicationContextAware} semantics.
+ */
+ public final void setApplicationContext(final ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+}
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
new file mode 100644
index 00000000..083b0fae
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2002-2012 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 javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
+import org.springframework.test.jdbc.JdbcTestUtils;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Abstract {@linkplain Transactional transactional} extension of
+ * {@link AbstractJUnit4SpringContextTests} which adds convenience functionality
+ * for JDBC access. Expects a {@link DataSource} bean and a
+ * {@link PlatformTransactionManager} bean to be defined in the Spring
+ * {@linkplain ApplicationContext application context}.
+ *
+ * <p>This class exposes a {@link JdbcTemplate} and provides an easy way to
+ * {@linkplain #countRowsInTable count the number of rows in a table}
+ * (potentially {@linkplain #countRowsInTableWhere with a WHERE clause}),
+ * {@linkplain #deleteFromTables delete from tables},
+ * {@linkplain #dropTables drop tables}, and
+ * {@linkplain #executeSqlScript execute SQL scripts} within a transaction.
+ *
+ * <p>Concrete subclasses must fulfill the same requirements outlined in
+ * {@link AbstractJUnit4SpringContextTests}.
+ *
+ * <p>Note: this class serves only as a convenience for extension. 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
+ * &#064;ContextConfiguration}, {@link TestExecutionListeners
+ * &#064;TestExecutionListeners}, {@link Transactional &#064;Transactional},
+ * etc.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see AbstractJUnit4SpringContextTests
+ * @see org.springframework.test.context.ContextConfiguration
+ * @see org.springframework.test.context.TestExecutionListeners
+ * @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
+ * @see org.springframework.test.context.transaction.TransactionConfiguration
+ * @see org.springframework.transaction.annotation.Transactional
+ * @see org.springframework.test.annotation.NotTransactional
+ * @see org.springframework.test.annotation.Rollback
+ * @see org.springframework.test.context.transaction.BeforeTransaction
+ * @see org.springframework.test.context.transaction.AfterTransaction
+ * @see org.springframework.test.jdbc.JdbcTestUtils
+ * @see org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
+ */
+@TestExecutionListeners(TransactionalTestExecutionListener.class)
+@Transactional
+@SuppressWarnings("deprecation")
+public abstract class AbstractTransactionalJUnit4SpringContextTests extends AbstractJUnit4SpringContextTests {
+
+ /**
+ * The {@code SimpleJdbcTemplate} that this base class manages, available to subclasses.
+ * @deprecated As of Spring 3.2, use {@link #jdbcTemplate} instead.
+ */
+ @Deprecated
+ protected SimpleJdbcTemplate simpleJdbcTemplate;
+
+ /**
+ * The {@code JdbcTemplate} that this base class manages, available to subclasses.
+ * @since 3.2
+ */
+ protected JdbcTemplate jdbcTemplate;
+
+ private String sqlScriptEncoding;
+
+
+ /**
+ * Set the {@code DataSource}, typically provided via Dependency Injection.
+ * <p>This method also instantiates the {@link #simpleJdbcTemplate} and
+ * {@link #jdbcTemplate} instance variables.
+ */
+ @Autowired
+ public void setDataSource(DataSource dataSource) {
+ this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ /**
+ * Specify the encoding for SQL scripts, if different from the platform encoding.
+ * @see #executeSqlScript
+ */
+ public void setSqlScriptEncoding(String sqlScriptEncoding) {
+ this.sqlScriptEncoding = sqlScriptEncoding;
+ }
+
+ /**
+ * Count the rows in the given table.
+ * @param tableName table name to count rows in
+ * @return the number of rows in the table
+ */
+ protected int countRowsInTable(String tableName) {
+ return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
+ }
+
+ /**
+ * Count the rows in the given table, using the provided {@code WHERE} clause.
+ * <p>See the Javadoc for {@link JdbcTestUtils#countRowsInTableWhere} for details.
+ * @param tableName the name of the table to count rows in
+ * @param whereClause the {@code WHERE} clause to append to the query
+ * @return the number of rows in the table that match the provided
+ * {@code WHERE} clause
+ * @since 3.2
+ */
+ protected int countRowsInTableWhere(String tableName, String whereClause) {
+ return JdbcTestUtils.countRowsInTableWhere(this.jdbcTemplate, tableName, whereClause);
+ }
+
+ /**
+ * Convenience method for deleting all rows from the specified tables. Use
+ * with caution outside of a transaction!
+ * @param names the names of the tables from which to delete
+ * @return the total number of rows deleted from all specified tables
+ */
+ protected int deleteFromTables(String... names) {
+ return JdbcTestUtils.deleteFromTables(this.jdbcTemplate, names);
+ }
+
+ /**
+ * Convenience method for dropping all of the specified tables. Use
+ * with caution outside of a transaction!
+ * @param names the names of the tables to drop
+ * @since 3.2
+ */
+ protected void dropTables(String... names) {
+ JdbcTestUtils.dropTables(this.jdbcTemplate, names);
+ }
+
+ /**
+ * Execute the given SQL script. Use with caution outside of a transaction!
+ * <p>The script will normally be loaded by classpath. There should be one
+ * statement per line. Any semicolons will be removed. <b>Do not use this
+ * method to execute DDL if you expect rollback.</b>
+ * @param sqlResourcePath the Spring resource path for the SQL script
+ * @param continueOnError whether or not to continue without throwing an
+ * exception in the event of an error
+ * @throws DataAccessException if there is an error executing a statement
+ * and continueOnError was {@code false}
+ */
+ protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
+ Resource resource = this.applicationContext.getResource(sqlResourcePath);
+ JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource,
+ this.sqlScriptEncoding), continueOnError);
+ }
+
+}
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
new file mode 100644
index 00000000..5496b4ee
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright 2002-2012 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.lang.reflect.Method;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.internal.AssumptionViolatedException;
+import org.junit.internal.runners.model.EachTestNotifier;
+import org.junit.internal.runners.model.ReflectiveCallable;
+import org.junit.internal.runners.statements.ExpectException;
+import org.junit.internal.runners.statements.Fail;
+import org.junit.internal.runners.statements.FailOnTimeout;
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+import org.springframework.test.annotation.ExpectedException;
+import org.springframework.test.annotation.ProfileValueUtils;
+import org.springframework.test.annotation.Repeat;
+import org.springframework.test.annotation.Timed;
+import org.springframework.test.context.TestContextManager;
+import org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks;
+import org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks;
+import org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks;
+import org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks;
+import org.springframework.test.context.junit4.statements.SpringFailOnTimeout;
+import org.springframework.test.context.junit4.statements.SpringRepeat;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * <p>
+ * {@code SpringJUnit4ClassRunner} is a custom extension of
+ * {@link BlockJUnit4ClassRunner} which provides functionality of the
+ * <em>Spring TestContext Framework</em> to standard JUnit 4.5+ tests by means
+ * of the {@link TestContextManager} and associated support classes and
+ * annotations.
+ * </p>
+ * <p>
+ * The following list constitutes all annotations currently supported directly
+ * by {@code SpringJUnit4ClassRunner}.
+ * <em>(Note that additional annotations may be supported by various
+ * {@link org.springframework.test.context.TestExecutionListener
+ * TestExecutionListeners})</em>
+ * </p>
+ * <ul>
+ * <li>{@link Test#expected() &#064;Test(expected=...)}</li>
+ * <li>{@link ExpectedException &#064;ExpectedException}</li>
+ * <li>{@link Test#timeout() &#064;Test(timeout=...)}</li>
+ * <li>{@link Timed &#064;Timed}</li>
+ * <li>{@link Repeat &#064;Repeat}</li>
+ * <li>{@link Ignore &#064;Ignore}</li>
+ * <li>
+ * {@link org.springframework.test.annotation.ProfileValueSourceConfiguration
+ * &#064;ProfileValueSourceConfiguration}</li>
+ * <li>{@link org.springframework.test.annotation.IfProfileValue
+ * &#064;IfProfileValue}</li>
+ * </ul>
+ * <p>
+ * <b>NOTE:</b> As of Spring 3.0, {@code SpringJUnit4ClassRunner} requires
+ * JUnit 4.5+.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see TestContextManager
+ */
+@SuppressWarnings("deprecation")
+public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
+
+ private static final Log logger = LogFactory.getLog(SpringJUnit4ClassRunner.class);
+
+ private final TestContextManager testContextManager;
+
+
+ /**
+ * Constructs a new {@code SpringJUnit4ClassRunner} and initializes a
+ * {@link TestContextManager} to provide Spring testing functionality to
+ * standard JUnit tests.
+ * @param clazz the test class to be run
+ * @see #createTestContextManager(Class)
+ */
+ public SpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
+ super(clazz);
+ if (logger.isDebugEnabled()) {
+ logger.debug("SpringJUnit4ClassRunner constructor called with [" + clazz + "].");
+ }
+ this.testContextManager = createTestContextManager(clazz);
+ }
+
+ /**
+ * Creates a new {@link TestContextManager} for the supplied test class and
+ * the configured <em>default {@code ContextLoader} class name</em>.
+ * Can be overridden by subclasses.
+ * @param clazz the test class to be managed
+ * @see #getDefaultContextLoaderClassName(Class)
+ */
+ protected TestContextManager createTestContextManager(Class<?> clazz) {
+ return new TestContextManager(clazz, getDefaultContextLoaderClassName(clazz));
+ }
+
+ /**
+ * Get the {@link TestContextManager} associated with this runner.
+ */
+ protected final TestContextManager getTestContextManager() {
+ return this.testContextManager;
+ }
+
+ /**
+ * Get the name of the default {@code ContextLoader} class to use for
+ * the supplied test class. The named class will be used if the test class
+ * does not explicitly declare a {@code ContextLoader} class via the
+ * {@code &#064;ContextConfiguration} annotation.
+ * <p>The default implementation returns {@code null}, thus implying use
+ * of the <em>standard</em> default {@code ContextLoader} class name.
+ * Can be overridden by subclasses.
+ * @param clazz the test class
+ * @return {@code null}
+ */
+ protected String getDefaultContextLoaderClassName(Class<?> clazz) {
+ return null;
+ }
+
+ /**
+ * Returns a description suitable for an ignored test class if the test is
+ * disabled via {@code &#064;IfProfileValue} at the class-level, and
+ * otherwise delegates to the parent implementation.
+ * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class)
+ */
+ @Override
+ public Description getDescription() {
+ if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) {
+ return Description.createSuiteDescription(getTestClass().getJavaClass());
+ }
+ return super.getDescription();
+ }
+
+ /**
+ * Check whether the test is enabled in the first place. This prevents
+ * classes with a non-matching {@code &#064;IfProfileValue} annotation
+ * from running altogether, even skipping the execution of
+ * {@code prepareTestInstance()} {@code TestExecutionListener}
+ * methods.
+ * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class)
+ * @see org.springframework.test.annotation.IfProfileValue
+ * @see org.springframework.test.context.TestExecutionListener
+ */
+ @Override
+ public void run(RunNotifier notifier) {
+ if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) {
+ notifier.fireTestIgnored(getDescription());
+ return;
+ }
+ super.run(notifier);
+ }
+
+ /**
+ * Wraps the {@link Statement} returned by the parent implementation with a
+ * {@link RunBeforeTestClassCallbacks} statement, thus preserving the
+ * default functionality but adding support for the Spring TestContext
+ * Framework.
+ * @see RunBeforeTestClassCallbacks
+ */
+ @Override
+ protected Statement withBeforeClasses(Statement statement) {
+ Statement junitBeforeClasses = super.withBeforeClasses(statement);
+ return new RunBeforeTestClassCallbacks(junitBeforeClasses, getTestContextManager());
+ }
+
+ /**
+ * Wraps the {@link Statement} returned by the parent implementation with a
+ * {@link RunAfterTestClassCallbacks} statement, thus preserving the default
+ * functionality but adding support for the Spring TestContext Framework.
+ * @see RunAfterTestClassCallbacks
+ */
+ @Override
+ protected Statement withAfterClasses(Statement statement) {
+ Statement junitAfterClasses = super.withAfterClasses(statement);
+ return new RunAfterTestClassCallbacks(junitAfterClasses, getTestContextManager());
+ }
+
+ /**
+ * Delegates to the parent implementation for creating the test instance and
+ * then allows the {@link #getTestContextManager() TestContextManager} to
+ * prepare the test instance before returning it.
+ * @see TestContextManager#prepareTestInstance(Object)
+ */
+ @Override
+ protected Object createTest() throws Exception {
+ Object testInstance = super.createTest();
+ getTestContextManager().prepareTestInstance(testInstance);
+ return testInstance;
+ }
+
+ /**
+ * Performs the same logic as
+ * {@link BlockJUnit4ClassRunner#runChild(FrameworkMethod, RunNotifier)},
+ * except that tests are determined to be <em>ignored</em> by
+ * {@link #isTestMethodIgnored(FrameworkMethod)}.
+ */
+ @Override
+ protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) {
+ EachTestNotifier eachNotifier = springMakeNotifier(frameworkMethod, notifier);
+ if (isTestMethodIgnored(frameworkMethod)) {
+ eachNotifier.fireTestIgnored();
+ return;
+ }
+
+ eachNotifier.fireTestStarted();
+ try {
+ methodBlock(frameworkMethod).evaluate();
+ }
+ catch (AssumptionViolatedException e) {
+ eachNotifier.addFailedAssumption(e);
+ }
+ catch (Throwable e) {
+ eachNotifier.addFailure(e);
+ }
+ finally {
+ eachNotifier.fireTestFinished();
+ }
+ }
+
+ /**
+ * {@code springMakeNotifier()} is an exact copy of
+ * {@link BlockJUnit4ClassRunner BlockJUnit4ClassRunner's}
+ * {@code makeNotifier()} method, but we have decided to prefix it with
+ * "spring" and keep it {@code private} in order to avoid the
+ * compatibility clashes that were introduced in JUnit between versions 4.5,
+ * 4.6, and 4.7.
+ */
+ private EachTestNotifier springMakeNotifier(FrameworkMethod method, RunNotifier notifier) {
+ Description description = describeChild(method);
+ return new EachTestNotifier(notifier, description);
+ }
+
+ /**
+ * Augments the default JUnit behavior
+ * {@link #withPotentialRepeat(FrameworkMethod, Object, Statement) with
+ * potential repeats} of the entire execution chain.
+ * <p>Furthermore, support for timeouts has been moved down the execution chain
+ * in order to include execution of {@link org.junit.Before &#064;Before}
+ * and {@link org.junit.After &#064;After} methods within the timed
+ * execution. Note that this differs from the default JUnit behavior of
+ * executing {@code &#064;Before} and {@code &#064;After} methods
+ * in the main thread while executing the actual test method in a separate
+ * thread. Thus, the end effect is that {@code &#064;Before} and
+ * {@code &#064;After} methods will be executed in the same thread as
+ * the test method. As a consequence, JUnit-specified timeouts will work
+ * fine in combination with Spring transactions. Note that JUnit-specific
+ * timeouts still differ from Spring-specific timeouts in that the former
+ * execute in a separate thread while the latter simply execute in the main
+ * thread (like regular tests).
+ * @see #possiblyExpectingExceptions(FrameworkMethod, Object, Statement)
+ * @see #withBefores(FrameworkMethod, Object, Statement)
+ * @see #withAfters(FrameworkMethod, Object, Statement)
+ * @see #withPotentialTimeout(FrameworkMethod, Object, Statement)
+ * @see #withPotentialRepeat(FrameworkMethod, Object, Statement)
+ */
+ @Override
+ protected Statement methodBlock(FrameworkMethod frameworkMethod) {
+ Object testInstance;
+ try {
+ testInstance = new ReflectiveCallable() {
+
+ @Override
+ protected Object runReflectiveCall() throws Throwable {
+ return createTest();
+ }
+ }.run();
+ }
+ catch (Throwable ex) {
+ return new Fail(ex);
+ }
+
+ Statement statement = methodInvoker(frameworkMethod, testInstance);
+ statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement);
+ statement = withBefores(frameworkMethod, testInstance, statement);
+ statement = withAfters(frameworkMethod, testInstance, statement);
+ statement = withRulesReflectively(frameworkMethod, testInstance, statement);
+ statement = withPotentialRepeat(frameworkMethod, testInstance, statement);
+ statement = withPotentialTimeout(frameworkMethod, testInstance, statement);
+
+ return statement;
+ }
+
+ /**
+ * Invokes JUnit 4.7's private {@code withRules()} method using
+ * reflection. This is necessary for backwards compatibility with the JUnit
+ * 4.5 and 4.6 implementations of {@link BlockJUnit4ClassRunner}.
+ */
+ private Statement withRulesReflectively(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
+ Method withRulesMethod = ReflectionUtils.findMethod(getClass(), "withRules", FrameworkMethod.class,
+ Object.class, Statement.class);
+ if (withRulesMethod != null) {
+ // Original JUnit 4.7 code:
+ // statement = withRules(frameworkMethod, testInstance, statement);
+ ReflectionUtils.makeAccessible(withRulesMethod);
+ statement = (Statement) ReflectionUtils.invokeMethod(withRulesMethod, this, frameworkMethod, testInstance,
+ statement);
+ }
+ return statement;
+ }
+
+ /**
+ * Returns {@code true} if {@link Ignore &#064;Ignore} is present for
+ * the supplied {@link FrameworkMethod test method} or if the test method is
+ * disabled via {@code &#064;IfProfileValue}.
+ * @see ProfileValueUtils#isTestEnabledInThisEnvironment(Method, Class)
+ */
+ protected boolean isTestMethodIgnored(FrameworkMethod frameworkMethod) {
+ Method method = frameworkMethod.getMethod();
+ return (method.isAnnotationPresent(Ignore.class) || !ProfileValueUtils.isTestEnabledInThisEnvironment(method,
+ getTestClass().getJavaClass()));
+ }
+
+ /**
+ * Performs the same logic as
+ * {@link BlockJUnit4ClassRunner#possiblyExpectingExceptions(FrameworkMethod, Object, Statement)}
+ * except that the <em>expected exception</em> is retrieved using
+ * {@link #getExpectedException(FrameworkMethod)}.
+ */
+ @Override
+ protected Statement possiblyExpectingExceptions(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
+ Class<? extends Throwable> expectedException = getExpectedException(frameworkMethod);
+ return expectedException != null ? new ExpectException(next, expectedException) : next;
+ }
+
+ /**
+ * Get the {@code exception} that the supplied {@link FrameworkMethod
+ * test method} is expected to throw.
+ * <p>Supports both Spring's {@link ExpectedException @ExpectedException(...)}
+ * and JUnit's {@link Test#expected() @Test(expected=...)} annotations, but
+ * not both simultaneously.
+ * @return the expected exception, or {@code null} if none was specified
+ */
+ protected Class<? extends Throwable> getExpectedException(FrameworkMethod frameworkMethod) {
+ Test testAnnotation = frameworkMethod.getAnnotation(Test.class);
+ Class<? extends Throwable> junitExpectedException = (testAnnotation != null
+ && testAnnotation.expected() != Test.None.class ? testAnnotation.expected() : null);
+
+ ExpectedException expectedExAnn = frameworkMethod.getAnnotation(ExpectedException.class);
+ Class<? extends Throwable> springExpectedException = (expectedExAnn != null ? expectedExAnn.value() : null);
+
+ if (springExpectedException != null && junitExpectedException != null) {
+ String msg = "Test method [" + frameworkMethod.getMethod()
+ + "] has been configured with Spring's @ExpectedException(" + springExpectedException.getName()
+ + ".class) and JUnit's @Test(expected=" + junitExpectedException.getName()
+ + ".class) annotations. "
+ + "Only one declaration of an 'expected exception' is permitted per test method.";
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+
+ return springExpectedException != null ? springExpectedException : junitExpectedException;
+ }
+
+ /**
+ * Supports both Spring's {@link Timed &#064;Timed} and JUnit's
+ * {@link Test#timeout() &#064;Test(timeout=...)} annotations, but not both
+ * simultaneously. Returns either a {@link SpringFailOnTimeout}, a
+ * {@link FailOnTimeout}, or the unmodified, supplied {@link Statement} as
+ * appropriate.
+ * @see #getSpringTimeout(FrameworkMethod)
+ * @see #getJUnitTimeout(FrameworkMethod)
+ */
+ @Override
+ protected Statement withPotentialTimeout(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
+ Statement statement = null;
+ long springTimeout = getSpringTimeout(frameworkMethod);
+ long junitTimeout = getJUnitTimeout(frameworkMethod);
+ if (springTimeout > 0 && junitTimeout > 0) {
+ String msg = "Test method [" + frameworkMethod.getMethod()
+ + "] has been configured with Spring's @Timed(millis=" + springTimeout
+ + ") and JUnit's @Test(timeout=" + junitTimeout
+ + ") annotations. Only one declaration of a 'timeout' is permitted per test method.";
+ logger.error(msg);
+ throw new IllegalStateException(msg);
+ }
+ else if (springTimeout > 0) {
+ statement = new SpringFailOnTimeout(next, springTimeout);
+ }
+ else if (junitTimeout > 0) {
+ statement = new FailOnTimeout(next, junitTimeout);
+ }
+ else {
+ statement = next;
+ }
+
+ return statement;
+ }
+
+ /**
+ * Retrieves the configured JUnit {@code timeout} from the {@link Test
+ * &#064;Test} annotation on the supplied {@link FrameworkMethod test method}.
+ * @return the timeout, or {@code 0} if none was specified.
+ */
+ protected long getJUnitTimeout(FrameworkMethod frameworkMethod) {
+ Test testAnnotation = frameworkMethod.getAnnotation(Test.class);
+ return (testAnnotation != null && testAnnotation.timeout() > 0 ? testAnnotation.timeout() : 0);
+ }
+
+ /**
+ * Retrieves the configured Spring-specific {@code timeout} from the
+ * {@link Timed &#064;Timed} annotation on the supplied
+ * {@link FrameworkMethod test method}.
+ * @return the timeout, or {@code 0} if none was specified.
+ */
+ protected long getSpringTimeout(FrameworkMethod frameworkMethod) {
+ Timed timedAnnotation = frameworkMethod.getAnnotation(Timed.class);
+ return (timedAnnotation != null && timedAnnotation.millis() > 0 ? timedAnnotation.millis() : 0);
+ }
+
+ /**
+ * Wraps the {@link Statement} returned by the parent implementation with a
+ * {@link RunBeforeTestMethodCallbacks} statement, thus preserving the
+ * default functionality but adding support for the Spring TestContext
+ * Framework.
+ * @see RunBeforeTestMethodCallbacks
+ */
+ @Override
+ protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
+ Statement junitBefores = super.withBefores(frameworkMethod, testInstance, statement);
+ return new RunBeforeTestMethodCallbacks(junitBefores, testInstance, frameworkMethod.getMethod(),
+ getTestContextManager());
+ }
+
+ /**
+ * Wraps the {@link Statement} returned by the parent implementation with a
+ * {@link RunAfterTestMethodCallbacks} statement, thus preserving the
+ * default functionality but adding support for the Spring TestContext
+ * Framework.
+ * @see RunAfterTestMethodCallbacks
+ */
+ @Override
+ protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
+ Statement junitAfters = super.withAfters(frameworkMethod, testInstance, statement);
+ return new RunAfterTestMethodCallbacks(junitAfters, testInstance, frameworkMethod.getMethod(),
+ getTestContextManager());
+ }
+
+ /**
+ * Supports Spring's {@link Repeat &#064;Repeat} annotation by returning a
+ * {@link SpringRepeat} statement initialized with the configured repeat
+ * count or {@code 1} if no repeat count is configured.
+ * @see SpringRepeat
+ */
+ protected Statement withPotentialRepeat(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
+ Repeat repeatAnnotation = frameworkMethod.getAnnotation(Repeat.class);
+ int repeat = (repeatAnnotation != null ? repeatAnnotation.value() : 1);
+ return new SpringRepeat(next, frameworkMethod.getMethod(), repeat);
+ }
+
+}
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
new file mode 100644
index 00000000..64f30c8f
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * <p>Support classes for ApplicationContext-based and transactional
+ * tests run with JUnit 4.5+ and the <em>Spring TestContext Framework</em>.</p>
+ */
+
+package org.springframework.test.context.junit4;
+
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
new file mode 100644
index 00000000..c3d6c8f5
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestClassCallbacks.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2002-2012 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.statements;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.internal.runners.model.MultipleFailureException;
+import org.junit.runners.model.Statement;
+import org.springframework.test.context.TestContextManager;
+
+/**
+ * {@code RunAfterTestClassCallbacks} is a custom JUnit 4.5+
+ * {@link Statement} which allows the <em>Spring TestContext Framework</em> to
+ * be plugged into the JUnit execution chain by calling
+ * {@link TestContextManager#afterTestClass() afterTestClass()} on the supplied
+ * {@link TestContextManager}.
+ *
+ * @see #evaluate()
+ * @see RunBeforeTestMethodCallbacks
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@SuppressWarnings("deprecation")
+public class RunAfterTestClassCallbacks extends Statement {
+
+ private final Statement next;
+
+ private final TestContextManager testContextManager;
+
+
+ /**
+ * Constructs a new {@code RunAfterTestClassCallbacks} statement.
+ *
+ * @param next the next {@code Statement} in the execution chain
+ * @param testContextManager the TestContextManager upon which to call
+ * {@code afterTestClass()}
+ */
+ public RunAfterTestClassCallbacks(Statement next, TestContextManager testContextManager) {
+ this.next = next;
+ this.testContextManager = testContextManager;
+ }
+
+ /**
+ * Invokes the next {@link Statement} in the execution chain (typically an
+ * instance of {@link org.junit.internal.runners.statements.RunAfters
+ * RunAfters}), catching any exceptions thrown, and then calls
+ * {@link TestContextManager#afterTestClass()}. If the call to
+ * {@code afterTestClass()} throws an exception, it will also be
+ * tracked. Multiple exceptions will be combined into a
+ * {@link MultipleFailureException}.
+ */
+ @Override
+ public void evaluate() throws Throwable {
+ List<Throwable> errors = new ArrayList<Throwable>();
+ try {
+ this.next.evaluate();
+ }
+ catch (Throwable e) {
+ errors.add(e);
+ }
+
+ try {
+ this.testContextManager.afterTestClass();
+ }
+ catch (Exception e) {
+ errors.add(e);
+ }
+
+ if (errors.isEmpty()) {
+ return;
+ }
+ if (errors.size() == 1) {
+ throw errors.get(0);
+ }
+ throw new MultipleFailureException(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
new file mode 100644
index 00000000..0fa60566
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunAfterTestMethodCallbacks.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2002-2012 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.statements;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.internal.runners.model.MultipleFailureException;
+import org.junit.runners.model.Statement;
+import org.springframework.test.context.TestContextManager;
+
+/**
+ * {@code RunAfterTestMethodCallbacks} is a custom JUnit 4.5+
+ * {@link Statement} which allows the <em>Spring TestContext Framework</em> to
+ * be plugged into the JUnit execution chain by calling
+ * {@link TestContextManager#afterTestMethod(Object, Method, Throwable) afterTestMethod()}
+ * on the supplied {@link TestContextManager}.
+ *
+ * @see #evaluate()
+ * @see RunBeforeTestMethodCallbacks
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@SuppressWarnings("deprecation")
+public class RunAfterTestMethodCallbacks extends Statement {
+
+ private final Statement next;
+
+ private final Object testInstance;
+
+ private final Method testMethod;
+
+ private final TestContextManager testContextManager;
+
+
+ /**
+ * Constructs a new {@code RunAfterTestMethodCallbacks} statement.
+ *
+ * @param next the next {@code Statement} in the execution chain
+ * @param testInstance the current test instance (never {@code null})
+ * @param testMethod the test method which has just been executed on the
+ * test instance
+ * @param testContextManager the TestContextManager upon which to call
+ * {@code afterTestMethod()}
+ */
+ public RunAfterTestMethodCallbacks(Statement next, Object testInstance, Method testMethod,
+ TestContextManager testContextManager) {
+ this.next = next;
+ this.testInstance = testInstance;
+ this.testMethod = testMethod;
+ this.testContextManager = testContextManager;
+ }
+
+ /**
+ * Invokes the next {@link Statement} in the execution chain (typically an
+ * instance of {@link org.junit.internal.runners.statements.RunAfters
+ * RunAfters}), catching any exceptions thrown, and then calls
+ * {@link TestContextManager#afterTestMethod(Object, Method, Throwable)} with the first
+ * caught exception (if any). If the call to {@code afterTestMethod()}
+ * throws an exception, it will also be tracked. Multiple exceptions will be
+ * combined into a {@link MultipleFailureException}.
+ */
+ @Override
+ public void evaluate() throws Throwable {
+ Throwable testException = null;
+ List<Throwable> errors = new ArrayList<Throwable>();
+ try {
+ this.next.evaluate();
+ }
+ catch (Throwable e) {
+ testException = e;
+ errors.add(e);
+ }
+
+ try {
+ this.testContextManager.afterTestMethod(this.testInstance, this.testMethod, testException);
+ }
+ catch (Exception e) {
+ errors.add(e);
+ }
+
+ if (errors.isEmpty()) {
+ return;
+ }
+ if (errors.size() == 1) {
+ throw errors.get(0);
+ }
+ throw new MultipleFailureException(errors);
+ }
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestClassCallbacks.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestClassCallbacks.java
new file mode 100644
index 00000000..16c6f97d
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestClassCallbacks.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2012 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.statements;
+
+import org.junit.runners.model.Statement;
+import org.springframework.test.context.TestContextManager;
+
+/**
+ * {@code RunBeforeTestClassCallbacks} is a custom JUnit 4.5+
+ * {@link Statement} which allows the <em>Spring TestContext Framework</em> to
+ * be plugged into the JUnit execution chain by calling
+ * {@link TestContextManager#beforeTestClass() beforeTestClass()} on the
+ * supplied {@link TestContextManager}.
+ *
+ * @see #evaluate()
+ * @see RunAfterTestMethodCallbacks
+ * @author Sam Brannen
+ * @since 3.0
+ */
+public class RunBeforeTestClassCallbacks extends Statement {
+
+ private final Statement next;
+
+ private final TestContextManager testContextManager;
+
+
+ /**
+ * Constructs a new {@code RunBeforeTestClassCallbacks} statement.
+ *
+ * @param next the next {@code Statement} in the execution chain
+ * @param testContextManager the TestContextManager upon which to call
+ * {@code beforeTestClass()}
+ */
+ public RunBeforeTestClassCallbacks(Statement next, TestContextManager testContextManager) {
+ this.next = next;
+ this.testContextManager = testContextManager;
+ }
+
+ /**
+ * Calls {@link TestContextManager#beforeTestClass()} and then invokes the
+ * next {@link Statement} in the execution chain (typically an instance of
+ * {@link org.junit.internal.runners.statements.RunBefores RunBefores}).
+ */
+ @Override
+ public void evaluate() throws Throwable {
+ this.testContextManager.beforeTestClass();
+ this.next.evaluate();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestMethodCallbacks.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestMethodCallbacks.java
new file mode 100644
index 00000000..92d32ea5
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/RunBeforeTestMethodCallbacks.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2012 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.statements;
+
+import java.lang.reflect.Method;
+
+import org.junit.runners.model.Statement;
+import org.springframework.test.context.TestContextManager;
+
+/**
+ * {@code RunBeforeTestMethodCallbacks} is a custom JUnit 4.5+
+ * {@link Statement} which allows the <em>Spring TestContext Framework</em> to
+ * be plugged into the JUnit execution chain by calling
+ * {@link TestContextManager#beforeTestMethod(Object, Method)
+ * beforeTestMethod()} on the supplied {@link TestContextManager}.
+ *
+ * @see #evaluate()
+ * @see RunAfterTestMethodCallbacks
+ * @author Sam Brannen
+ * @since 3.0
+ */
+public class RunBeforeTestMethodCallbacks extends Statement {
+
+ private final Statement next;
+
+ private final Object testInstance;
+
+ private final Method testMethod;
+
+ private final TestContextManager testContextManager;
+
+
+ /**
+ * Constructs a new {@code RunBeforeTestMethodCallbacks} statement.
+ *
+ * @param next the next {@code Statement} in the execution chain
+ * @param testInstance the current test instance (never {@code null})
+ * @param testMethod the test method which is about to be executed on the
+ * test instance
+ * @param testContextManager the TestContextManager upon which to call
+ * {@code beforeTestMethod()}
+ */
+ public RunBeforeTestMethodCallbacks(Statement next, Object testInstance, Method testMethod,
+ TestContextManager testContextManager) {
+ this.next = next;
+ this.testInstance = testInstance;
+ this.testMethod = testMethod;
+ this.testContextManager = testContextManager;
+ }
+
+ /**
+ * Calls {@link TestContextManager#beforeTestMethod(Object, Method)} and
+ * then invokes the next {@link Statement} in the execution chain (typically
+ * an instance of {@link org.junit.internal.runners.statements.RunBefores
+ * RunBefores}).
+ */
+ @Override
+ public void evaluate() throws Throwable {
+ this.testContextManager.beforeTestMethod(this.testInstance, this.testMethod);
+ this.next.evaluate();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/SpringFailOnTimeout.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/SpringFailOnTimeout.java
new file mode 100644
index 00000000..093ae49b
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/SpringFailOnTimeout.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2002-2012 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.statements;
+
+import java.util.concurrent.TimeoutException;
+
+import org.junit.runners.model.Statement;
+import org.springframework.test.annotation.Timed;
+
+/**
+ * {@code SpringFailOnTimeout} is a custom JUnit 4.5+ {@link Statement}
+ * which adds support for Spring's {@link Timed @Timed} annotation by throwing
+ * an exception if the next statement in the execution chain takes more than the
+ * specified number of milliseconds.
+ *
+ * @see #evaluate()
+ * @author Sam Brannen
+ * @since 3.0
+ */
+public class SpringFailOnTimeout extends Statement {
+
+ private final Statement next;
+
+ private final long timeout;
+
+
+ /**
+ * Constructs a new {@code SpringFailOnTimeout} statement.
+ *
+ * @param next the next {@code Statement} in the execution chain
+ * @param timeout the configured {@code timeout} for the current test
+ * @see Timed#millis()
+ */
+ public SpringFailOnTimeout(Statement next, long timeout) {
+ this.next = next;
+ this.timeout = timeout;
+ }
+
+ /**
+ * Invokes the next {@link Statement statement} in the execution chain
+ * (typically an instance of
+ * {@link org.junit.internal.runners.statements.InvokeMethod InvokeMethod}
+ * or {@link org.junit.internal.runners.statements.ExpectException
+ * ExpectException}) and throws an exception if the next
+ * {@code statement} takes more than the specified {@code timeout}
+ * .
+ */
+ @Override
+ public void evaluate() throws Throwable {
+ long startTime = System.currentTimeMillis();
+ try {
+ this.next.evaluate();
+ }
+ finally {
+ long elapsed = System.currentTimeMillis() - startTime;
+ if (elapsed > this.timeout) {
+ throw new TimeoutException(String.format("Test took %s ms; limit was %s ms.", elapsed, this.timeout));
+ }
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/SpringRepeat.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/SpringRepeat.java
new file mode 100644
index 00000000..0aa21888
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/SpringRepeat.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2002-2012 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.statements;
+
+import java.lang.reflect.Method;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.runners.model.Statement;
+import org.springframework.test.annotation.Repeat;
+import org.springframework.util.ClassUtils;
+
+/**
+ * {@code SpringRepeat} is a custom JUnit 4.5+ {@link Statement} which adds
+ * support for Spring's {@link Repeat &#064;Repeat} annotation by repeating the
+ * test for the specified number of times.
+ *
+ * @see #evaluate()
+ * @author Sam Brannen
+ * @since 3.0
+ */
+public class SpringRepeat extends Statement {
+
+ protected static final Log logger = LogFactory.getLog(SpringRepeat.class);
+
+ private final Statement next;
+
+ private final Method testMethod;
+
+ private final int repeat;
+
+
+ /**
+ * Constructs a new {@code SpringRepeat} statement.
+ *
+ * @param next the next {@code Statement} in the execution chain
+ * @param testMethod the current test method
+ * @param repeat the configured repeat count for the current test method
+ * @see Repeat#value()
+ */
+ public SpringRepeat(Statement next, Method testMethod, int repeat) {
+ this.next = next;
+ this.testMethod = testMethod;
+ this.repeat = Math.max(1, repeat);
+ }
+
+ /**
+ * Invokes the next {@link Statement statement} in the execution chain for
+ * the specified repeat count.
+ */
+ @Override
+ public void evaluate() throws Throwable {
+ for (int i = 0; i < this.repeat; i++) {
+ if (this.repeat > 1 && logger.isInfoEnabled()) {
+ logger.info(String.format("Repetition %d of test %s#%s()", (i + 1),
+ ClassUtils.getShortName(this.testMethod.getDeclaringClass()), this.testMethod.getName()));
+ }
+ this.next.evaluate();
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/junit4/statements/package-info.java b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/package-info.java
new file mode 100644
index 00000000..116e5fe1
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/junit4/statements/package-info.java
@@ -0,0 +1,7 @@
+/**
+ *
+ * <p>JUnit 4.5 based {@code statements} used in the <em>Spring TestContext Framework</em>.</p>
+ *
+ */
+package org.springframework.test.context.junit4.statements;
+
diff --git a/spring-test/src/main/java/org/springframework/test/context/package-info.java b/spring-test/src/main/java/org/springframework/test/context/package-info.java
new file mode 100644
index 00000000..b02e0b44
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/package-info.java
@@ -0,0 +1,16 @@
+/**
+ * <p>This package contains the <em>Spring TestContext Framework</em> which
+ * provides annotation-driven unit and integration testing support that is
+ * agnostic of the actual testing framework in use. The same techniques and
+ * annotation-based configuration used in, for example, a JUnit 4.5+ environment
+ * can also be applied to tests written with TestNG, etc.
+ *
+ * <p>In addition to providing generic and extensible testing infrastructure,
+ * the Spring TestContext Framework provides out-of-the-box support for
+ * Spring-specific integration testing functionality such as context management
+ * and caching, dependency injection of test fixtures, and transactional test
+ * management with default rollback semantics.
+ */
+
+package org.springframework.test.context;
+
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
new file mode 100644
index 00000000..8b497ccc
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.support;
+
+import java.util.ArrayList;
+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.context.ApplicationContext;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.support.ResourcePatternUtils;
+import org.springframework.test.context.ContextConfigurationAttributes;
+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.ClassUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ResourceUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Abstract application context loader that provides a basis for all concrete
+ * implementations of the {@link ContextLoader} SPI. Provides a
+ * <em>Template Method</em> based approach for {@link #processLocations processing}
+ * resource locations.
+ *
+ * <p>As of Spring 3.1, {@code AbstractContextLoader} also provides a basis
+ * for all concrete implementations of the {@link SmartContextLoader} SPI. For
+ * backwards compatibility with the {@code ContextLoader} SPI,
+ * {@link #processContextConfiguration(ContextConfigurationAttributes)} delegates
+ * to {@link #processLocations(Class, String...)}.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see #generateDefaultLocations
+ * @see #modifyLocations
+ */
+public abstract class AbstractContextLoader implements SmartContextLoader {
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ private static final String SLASH = "/";
+
+ private static final Log logger = LogFactory.getLog(AbstractContextLoader.class);
+
+
+ // --- SmartContextLoader -----------------------------------------------
+
+ /**
+ * For backwards compatibility with the {@link ContextLoader} SPI, the
+ * default implementation simply delegates to {@link #processLocations(Class, String...)},
+ * passing it the {@link ContextConfigurationAttributes#getDeclaringClass()
+ * declaring class} and {@link ContextConfigurationAttributes#getLocations()
+ * resource locations} retrieved from the supplied
+ * {@link ContextConfigurationAttributes configuration attributes}. The
+ * processed locations are then
+ * {@link ContextConfigurationAttributes#setLocations(String[]) set} in
+ * the supplied configuration attributes.
+ * <p>Can be overridden in subclasses &mdash; for example, to process
+ * annotated classes instead of resource locations.
+ * @since 3.1
+ * @see #processLocations(Class, String...)
+ */
+ public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
+ String[] processedLocations = processLocations(configAttributes.getDeclaringClass(), configAttributes.getLocations());
+ configAttributes.setLocations(processedLocations);
+ }
+
+ /**
+ * Prepare the {@link ConfigurableApplicationContext} created by this
+ * {@code SmartContextLoader} <i>before</i> bean definitions are read.
+ * <p>The default implementation:
+ * <ul>
+ * <li>Sets the <em>active bean definition profiles</em> from the supplied
+ * {@code MergedContextConfiguration} in the
+ * {@link org.springframework.core.env.Environment Environment} of the context.</li>
+ * <li>Determines what (if any) context initializer classes have been supplied
+ * via the {@code MergedContextConfiguration} and
+ * {@linkplain ApplicationContextInitializer#initialize invokes each} with the
+ * given application context.</li>
+ * </ul>
+ * <p>Any {@code ApplicationContextInitializers} implementing
+ * {@link org.springframework.core.Ordered Ordered} or marked with {@link
+ * org.springframework.core.annotation.Order @Order} will be sorted appropriately.
+ * @param context the newly created application context
+ * @param mergedConfig the merged context configuration
+ * @since 3.2
+ * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
+ * @see #loadContext(MergedContextConfiguration)
+ * @see ConfigurableApplicationContext#setId
+ */
+ @SuppressWarnings("unchecked")
+ protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
+ context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles());
+
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses =
+ mergedConfig.getContextInitializerClasses();
+ if (initializerClasses.isEmpty()) {
+ // no ApplicationContextInitializers have been declared -> nothing to do
+ return;
+ }
+
+ List<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances =
+ new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
+ Class<?> contextClass = context.getClass();
+
+ for (Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>> initializerClass : initializerClasses) {
+ Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass,
+ ApplicationContextInitializer.class);
+ Assert.isAssignable(initializerContextClass, contextClass, String.format(
+ "Could not add context initializer [%s] since its generic parameter [%s] " +
+ "is not assignable from the type of application context used by this " +
+ "context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(),
+ contextClass.getName()));
+ initializerInstances.add((ApplicationContextInitializer<ConfigurableApplicationContext>) BeanUtils.instantiateClass(initializerClass));
+ }
+
+ AnnotationAwareOrderComparator.sort(initializerInstances);
+ for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : initializerInstances) {
+ initializer.initialize(context);
+ }
+ }
+
+ // --- ContextLoader -------------------------------------------------------
+
+ /**
+ * If the supplied {@code locations} are {@code null} or
+ * <em>empty</em> and {@link #isGenerateDefaultLocations()} returns
+ * {@code true}, default locations will be
+ * {@link #generateDefaultLocations(Class) generated} for the specified
+ * {@link Class class} and the configured
+ * {@link #getResourceSuffix() resource suffix}; otherwise, the supplied
+ * {@code locations} will be {@link #modifyLocations modified} if
+ * necessary and returned.
+ * @param clazz the class with which the locations are associated: to be
+ * used when generating default locations
+ * @param locations the unmodified locations to use for loading the
+ * application context (can be {@code null} or empty)
+ * @return a processed array of application context resource locations
+ * @since 2.5
+ * @see #isGenerateDefaultLocations()
+ * @see #generateDefaultLocations(Class)
+ * @see #modifyLocations(Class, String...)
+ * @see org.springframework.test.context.ContextLoader#processLocations(Class, String...)
+ * @see #processContextConfiguration(ContextConfigurationAttributes)
+ */
+ public final String[] processLocations(Class<?> clazz, String... locations) {
+ return (ObjectUtils.isEmpty(locations) && isGenerateDefaultLocations()) ?
+ generateDefaultLocations(clazz) : modifyLocations(clazz, locations);
+ }
+
+ /**
+ * Generate the default classpath resource locations array based on the
+ * supplied class.
+ * <p>For example, if the supplied class is {@code com.example.MyTest},
+ * the generated locations will contain a single string with a value of
+ * &quot;classpath:/com/example/MyTest{@code <suffix>}&quot;,
+ * where {@code <suffix>} is the value of the
+ * {@link #getResourceSuffix() resource suffix} string.
+ * <p>As of Spring 3.1, the implementation of this method adheres to the
+ * contract defined in the {@link SmartContextLoader} SPI. Specifically,
+ * this method will <em>preemptively</em> verify that the generated default
+ * location actually exists. If it does not exist, this method will log a
+ * warning and return an empty array.
+ * <p>Subclasses can override this method to implement a different
+ * <em>default location generation</em> strategy.
+ * @param clazz the class for which the default locations are to be generated
+ * @return an array of default application context resource locations
+ * @since 2.5
+ * @see #getResourceSuffix()
+ */
+ protected String[] generateDefaultLocations(Class<?> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ String suffix = getResourceSuffix();
+ Assert.hasText(suffix, "Resource suffix must not be empty");
+ String resourcePath = SLASH + ClassUtils.convertClassNameToResourcePath(clazz.getName()) + suffix;
+ String prefixedResourcePath = ResourceUtils.CLASSPATH_URL_PREFIX + resourcePath;
+ ClassPathResource classPathResource = new ClassPathResource(resourcePath, clazz);
+
+ if (classPathResource.exists()) {
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format("Detected default resource location \"%s\" for test class [%s]",
+ prefixedResourcePath, clazz.getName()));
+ }
+ return new String[] {prefixedResourcePath};
+ }
+ else {
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format("Could not detect default resource locations for test class [%s]: " +
+ "%s does not exist", clazz.getName(), classPathResource));
+ }
+ return EMPTY_STRING_ARRAY;
+ }
+ }
+
+ /**
+ * Generate a modified version of the supplied locations array and return it.
+ * <p>A plain path &mdash; for example, &quot;context.xml&quot; &mdash; will
+ * be treated as a classpath resource that is relative to the package in which
+ * the specified class is defined. A path starting with a slash is treated
+ * as an absolute classpath location, for example:
+ * &quot;/org/springframework/whatever/foo.xml&quot;. A path which
+ * references a URL (e.g., a path prefixed with
+ * {@link ResourceUtils#CLASSPATH_URL_PREFIX classpath:},
+ * {@link ResourceUtils#FILE_URL_PREFIX file:}, {@code http:},
+ * etc.) will be added to the results unchanged.
+ * <p>Subclasses can override this method to implement a different
+ * <em>location modification</em> strategy.
+ * @param clazz the class with which the locations are associated
+ * @param locations the resource locations to be modified
+ * @return an array of modified application context resource locations
+ * @since 2.5
+ */
+ protected String[] modifyLocations(Class<?> clazz, String... locations) {
+ String[] modifiedLocations = new String[locations.length];
+ for (int i = 0; i < locations.length; i++) {
+ String path = locations[i];
+ if (path.startsWith(SLASH)) {
+ modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + path;
+ }
+ else if (!ResourcePatternUtils.isUrl(path)) {
+ modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + SLASH +
+ StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(clazz) + SLASH + path);
+ }
+ else {
+ modifiedLocations[i] = StringUtils.cleanPath(path);
+ }
+ }
+ return modifiedLocations;
+ }
+
+ /**
+ * Determine whether or not <em>default</em> resource locations should be
+ * generated if the {@code locations} provided to
+ * {@link #processLocations(Class, String...)} are {@code null} or empty.
+ * <p>As of Spring 3.1, the semantics of this method have been overloaded
+ * to include detection of either default resource locations or default
+ * configuration classes. Consequently, this method can also be used to
+ * determine whether or not <em>default</em> configuration classes should be
+ * detected if the {@code classes} present in the
+ * {@link ContextConfigurationAttributes configuration attributes} supplied
+ * to {@link #processContextConfiguration(ContextConfigurationAttributes)}
+ * are {@code null} or empty.
+ * <p>Can be overridden by subclasses to change the default behavior.
+ * @return always {@code true} by default
+ * @since 2.5
+ */
+ protected boolean isGenerateDefaultLocations() {
+ return true;
+ }
+
+ /**
+ * Get the suffix to append to {@link ApplicationContext} resource locations
+ * when generating default locations.
+ * <p>Must be implemented by subclasses.
+ * @return the resource suffix; should not be {@code null} or empty
+ * @since 2.5
+ * @see #generateDefaultLocations(Class)
+ */
+ protected abstract String getResourceSuffix();
+
+}
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
new file mode 100644
index 00000000..edf34158
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java
@@ -0,0 +1,286 @@
+/*
+ * 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.context.support;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextConfigurationAttributes;
+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
+ * for implementations of the {@link SmartContextLoader} SPI that delegate to a
+ * set of <em>candidate</em> SmartContextLoaders (i.e., one that supports XML
+ * configuration files and one that supports annotated classes) to determine which
+ * context loader is appropriate for a given test class's configuration. Each
+ * candidate is given a chance to {@link #processContextConfiguration process} the
+ * {@link ContextConfigurationAttributes} for each class in the test class hierarchy
+ * that is annotated with {@link ContextConfiguration @ContextConfiguration}, and
+ * the candidate that supports the merged, processed configuration will be used to
+ * actually {@link #loadContext load} the context.
+ *
+ * <p>Placing an empty {@code @ContextConfiguration} annotation on a test class signals
+ * that default resource locations (i.e., XML configuration files) or default
+ * {@link org.springframework.context.annotation.Configuration configuration classes}
+ * should be detected. Furthermore, if a specific {@link ContextLoader} or
+ * {@link SmartContextLoader} is not explicitly declared via
+ * {@code @ContextConfiguration}, a concrete subclass of
+ * {@code AbstractDelegatingSmartContextLoader} will be used as the default loader,
+ * thus providing automatic support for either XML configuration files or annotated
+ * classes, but not both simultaneously.
+ *
+ * <p>As of Spring 3.2, a test class may optionally declare neither XML configuration
+ * files nor annotated classes and instead declare only {@linkplain
+ * ContextConfiguration#initializers application context initializers}. In such
+ * cases, an attempt will still be made to detect defaults, but their absence will
+ * not result in an exception.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ * @see SmartContextLoader
+ */
+public abstract class AbstractDelegatingSmartContextLoader implements SmartContextLoader {
+
+ private static final Log logger = LogFactory.getLog(AbstractDelegatingSmartContextLoader.class);
+
+
+ /**
+ * Get the delegate {@code SmartContextLoader} that supports XML configuration files.
+ */
+ protected abstract SmartContextLoader getXmlLoader();
+
+ /**
+ * Get the delegate {@code SmartContextLoader} that supports annotated classes.
+ */
+ protected abstract SmartContextLoader getAnnotationConfigLoader();
+
+
+ // --- SmartContextLoader --------------------------------------------------
+
+ private static String name(SmartContextLoader loader) {
+ return loader.getClass().getSimpleName();
+ }
+
+ private static void delegateProcessing(SmartContextLoader loader, ContextConfigurationAttributes configAttributes) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Delegating to %s to process context configuration %s.", name(loader),
+ configAttributes));
+ }
+ loader.processContextConfiguration(configAttributes);
+ }
+
+ private static ApplicationContext delegateLoading(SmartContextLoader loader, MergedContextConfiguration mergedConfig)
+ throws Exception {
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Delegating to %s to load context from %s.", name(loader), mergedConfig));
+ }
+ return loader.loadContext(mergedConfig);
+ }
+
+ private boolean supports(SmartContextLoader loader, MergedContextConfiguration mergedConfig) {
+ if (loader == getAnnotationConfigLoader()) {
+ return ObjectUtils.isEmpty(mergedConfig.getLocations()) && !ObjectUtils.isEmpty(mergedConfig.getClasses());
+ }
+ else {
+ return !ObjectUtils.isEmpty(mergedConfig.getLocations()) && ObjectUtils.isEmpty(mergedConfig.getClasses());
+ }
+ }
+
+ /**
+ * Delegates to candidate {@code SmartContextLoaders} to process the supplied
+ * {@link ContextConfigurationAttributes}.
+ * <p>Delegation is based on explicit knowledge of the implementations of the
+ * default loaders for {@link #getXmlLoader() XML configuration files} and
+ * {@link #getAnnotationConfigLoader() annotated classes}. Specifically, the
+ * delegation algorithm is as follows:
+ * <ul>
+ * <li>If the resource locations or annotated classes in the supplied
+ * {@code ContextConfigurationAttributes} are not empty, the appropriate
+ * candidate loader will be allowed to process the configuration <em>as is</em>,
+ * without any checks for detection of defaults.</li>
+ * <li>Otherwise, the XML-based loader will be allowed to process
+ * the configuration in order to detect default resource locations. If
+ * the XML-based loader detects default resource locations,
+ * an {@code info} message will be logged.</li>
+ * <li>Subsequently, the annotation-based loader will be allowed to
+ * process the configuration in order to detect default configuration classes.
+ * If the annotation-based loader detects default configuration
+ * classes, an {@code info} message will be logged.</li>
+ * </ul>
+ * @param configAttributes the context configuration attributes to process
+ * @throws IllegalArgumentException if the supplied configuration attributes are
+ * {@code null}, or if the supplied configuration attributes include both
+ * resource locations and annotated classes
+ * @throws IllegalStateException if the XML-based loader detects default
+ * configuration classes; if the annotation-based loader detects default
+ * resource locations; if neither candidate loader detects defaults for the supplied
+ * context configuration; or if both candidate loaders detect defaults for the
+ * supplied context configuration
+ */
+ public void processContextConfiguration(final ContextConfigurationAttributes configAttributes) {
+
+ Assert.notNull(configAttributes, "configAttributes must not be null");
+ Assert.isTrue(!(configAttributes.hasLocations() && configAttributes.hasClasses()), String.format(
+ "Cannot process locations AND classes for context configuration %s; configure one or the other, but not both.",
+ configAttributes));
+
+ // If the original locations or classes were not empty, there's no
+ // need to bother with default detection checks; just let the
+ // appropriate loader process the configuration.
+ if (configAttributes.hasLocations()) {
+ delegateProcessing(getXmlLoader(), configAttributes);
+ }
+ else if (configAttributes.hasClasses()) {
+ delegateProcessing(getAnnotationConfigLoader(), configAttributes);
+ }
+ else {
+ // Else attempt to detect defaults...
+
+ // Let the XML loader process the configuration.
+ delegateProcessing(getXmlLoader(), configAttributes);
+ boolean xmlLoaderDetectedDefaults = configAttributes.hasLocations();
+
+ if (xmlLoaderDetectedDefaults) {
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format("%s detected default locations for context configuration %s.",
+ name(getXmlLoader()), configAttributes));
+ }
+ }
+
+ if (configAttributes.hasClasses()) {
+ throw new IllegalStateException(String.format(
+ "%s should NOT have detected default configuration classes for context configuration %s.",
+ name(getXmlLoader()), configAttributes));
+ }
+
+ // Now let the annotation config loader process the configuration.
+ delegateProcessing(getAnnotationConfigLoader(), configAttributes);
+
+ if (configAttributes.hasClasses()) {
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format(
+ "%s detected default configuration classes for context configuration %s.",
+ name(getAnnotationConfigLoader()), configAttributes));
+ }
+ }
+
+ if (!xmlLoaderDetectedDefaults && configAttributes.hasLocations()) {
+ throw new IllegalStateException(String.format(
+ "%s should NOT have detected default locations for context configuration %s.",
+ 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 " +
+ "were detected for context configuration %s; configure one or the other, but not both.",
+ configAttributes);
+ logger.error(message);
+ throw new IllegalStateException(message);
+ }
+ }
+ }
+
+ /**
+ * Delegates to an appropriate candidate {@code SmartContextLoader} to load
+ * an {@link ApplicationContext}.
+ * <p>Delegation is based on explicit knowledge of the implementations of the
+ * default loaders for {@link #getXmlLoader() XML configuration files} and
+ * {@link #getAnnotationConfigLoader() annotated classes}. Specifically, the
+ * delegation algorithm is as follows:
+ * <ul>
+ * <li>If the resource locations in the supplied {@code MergedContextConfiguration}
+ * are not empty and the annotated classes are empty,
+ * the XML-based loader will load the {@code ApplicationContext}.</li>
+ * <li>If the annotated classes in the supplied {@code MergedContextConfiguration}
+ * are not empty and the resource locations are empty,
+ * the annotation-based loader will load the {@code ApplicationContext}.</li>
+ * </ul>
+ * @param mergedConfig the merged context configuration to use to load the application context
+ * @throws IllegalArgumentException if the supplied merged configuration is {@code null}
+ * @throws IllegalStateException if neither candidate loader is capable of loading an
+ * {@code ApplicationContext} from the supplied merged context configuration
+ */
+ public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
+ Assert.notNull(mergedConfig, "mergedConfig must not be null");
+ List<SmartContextLoader> candidates = Arrays.asList(getXmlLoader(), getAnnotationConfigLoader());
+
+ for (SmartContextLoader loader : candidates) {
+ // Determine if each loader can load a context from the mergedConfig. If it
+ // can, let it; otherwise, keep iterating.
+ if (supports(loader, mergedConfig)) {
+ return delegateLoading(loader, mergedConfig);
+ }
+ }
+
+ // 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()) {
+ return delegateLoading(getAnnotationConfigLoader(), mergedConfig);
+ }
+
+ throw new IllegalStateException(String.format(
+ "Neither %s nor %s was able to load an ApplicationContext from %s.", name(getXmlLoader()),
+ name(getAnnotationConfigLoader()), mergedConfig));
+ }
+
+
+ // --- ContextLoader -------------------------------------------------------
+
+ /**
+ * {@code AbstractDelegatingSmartContextLoader} does not support the
+ * {@link ContextLoader#processLocations(Class, String...)} method. Call
+ * {@link #processContextConfiguration(ContextConfigurationAttributes)} instead.
+ * @throws UnsupportedOperationException
+ */
+ public final String[] processLocations(Class<?> clazz, String... locations) {
+ throw new UnsupportedOperationException("DelegatingSmartContextLoaders do not support the ContextLoader SPI. " +
+ "Call processContextConfiguration(ContextConfigurationAttributes) instead.");
+ }
+
+ /**
+ * {@code AbstractDelegatingSmartContextLoader} does not support the
+ * {@link ContextLoader#loadContext(String...) } method. Call
+ * {@link #loadContext(MergedContextConfiguration)} instead.
+ * @throws UnsupportedOperationException
+ */
+ public final ApplicationContext loadContext(String... locations) throws Exception {
+ throw new UnsupportedOperationException("DelegatingSmartContextLoaders do not support the ContextLoader SPI. " +
+ "Call loadContext(MergedContextConfiguration) instead.");
+ }
+
+}
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
new file mode 100644
index 00000000..bd84068a
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.support;
+
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.support.BeanDefinitionReader;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigUtils;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.util.StringUtils;
+
+/**
+ * Abstract, generic extension of {@link AbstractContextLoader} that loads a
+ * {@link GenericApplicationContext}.
+ *
+ * <ul>
+ * <li>If instances of concrete subclasses are invoked via the
+ * {@link org.springframework.test.context.ContextLoader ContextLoader} SPI, the
+ * context will be loaded from the <em>locations</em> provided to
+ * {@link #loadContext(String...)}.</li>
+ * <li>If instances of concrete subclasses are invoked via the
+ * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
+ * SPI, the context will be loaded from the {@link MergedContextConfiguration}
+ * provided to {@link #loadContext(MergedContextConfiguration)}. In such cases, a
+ * {@code SmartContextLoader} will decide whether to load the context from
+ * <em>locations</em> or <em>annotated classes</em>.</li>
+ * </ul>
+ *
+ * <p>Concrete subclasses must provide an appropriate implementation of
+ * {@link #createBeanDefinitionReader createBeanDefinitionReader()},
+ * potentially overriding {@link #loadBeanDefinitions loadBeanDefinitions()}
+ * as well.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see #loadContext(MergedContextConfiguration)
+ * @see #loadContext(String...)
+ */
+public abstract class AbstractGenericContextLoader extends AbstractContextLoader {
+
+ protected static final Log logger = LogFactory.getLog(AbstractGenericContextLoader.class);
+
+
+ /**
+ * Load a Spring ApplicationContext from the supplied {@link MergedContextConfiguration}.
+ *
+ * <p>Implementation details:
+ *
+ * <ul>
+ * <li>Creates a {@link GenericApplicationContext} instance.</li>
+ * <li>If the supplied {@code MergedContextConfiguration} references a
+ * {@linkplain MergedContextConfiguration#getParent() parent configuration},
+ * the corresponding {@link MergedContextConfiguration#getParentApplicationContext()
+ * ApplicationContext} will be retrieved and
+ * {@linkplain GenericApplicationContext#setParent(ApplicationContext) set as the parent}
+ * for the context created by this method.</li>
+ * <li>Calls {@link #prepareContext(GenericApplicationContext)} for backwards
+ * compatibility with the {@link org.springframework.test.context.ContextLoader
+ * ContextLoader} SPI.</li>
+ * <li>Calls {@link #prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)}
+ * to allow for customizing the context before bean definitions are loaded.</li>
+ * <li>Calls {@link #customizeBeanFactory(DefaultListableBeanFactory)} to allow for customizing the
+ * context's {@code DefaultListableBeanFactory}.</li>
+ * <li>Delegates to {@link #loadBeanDefinitions(GenericApplicationContext, MergedContextConfiguration)}
+ * to populate the context from the locations or classes in the supplied
+ * {@code MergedContextConfiguration}.</li>
+ * <li>Delegates to {@link AnnotationConfigUtils} for
+ * {@link AnnotationConfigUtils#registerAnnotationConfigProcessors registering}
+ * annotation configuration processors.</li>
+ * <li>Calls {@link #customizeContext(GenericApplicationContext)} 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>
+ *
+ * @return a new application context
+ * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration)
+ * @see GenericApplicationContext
+ * @since 3.1
+ */
+ public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Loading ApplicationContext for merged context configuration [%s].",
+ mergedConfig));
+ }
+
+ GenericApplicationContext context = new GenericApplicationContext();
+
+ ApplicationContext parent = mergedConfig.getParentApplicationContext();
+ if (parent != null) {
+ context.setParent(parent);
+ }
+ prepareContext(context);
+ prepareContext(context, mergedConfig);
+ customizeBeanFactory(context.getDefaultListableBeanFactory());
+ loadBeanDefinitions(context, mergedConfig);
+ AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
+ customizeContext(context);
+ context.refresh();
+ context.registerShutdownHook();
+ return context;
+ }
+
+ /**
+ * Load a Spring ApplicationContext from the supplied {@code locations}.
+ *
+ * <p>Implementation details:
+ *
+ * <ul>
+ * <li>Creates a {@link GenericApplicationContext} instance.</li>
+ * <li>Calls {@link #prepareContext(GenericApplicationContext)} to allow for customizing the context
+ * before bean definitions are loaded.</li>
+ * <li>Calls {@link #customizeBeanFactory(DefaultListableBeanFactory)} to allow for customizing the
+ * context's {@code DefaultListableBeanFactory}.</li>
+ * <li>Delegates to {@link #createBeanDefinitionReader(GenericApplicationContext)} to create a
+ * {@link BeanDefinitionReader} which is then used to populate the context
+ * from the specified locations.</li>
+ * <li>Delegates to {@link AnnotationConfigUtils} for
+ * {@link AnnotationConfigUtils#registerAnnotationConfigProcessors registering}
+ * annotation configuration processors.</li>
+ * <li>Calls {@link #customizeContext(GenericApplicationContext)} 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>
+ *
+ * <p><b>Note</b>: this method does not provide a means to set active bean definition
+ * profiles for the loaded context. See {@link #loadContext(MergedContextConfiguration)}
+ * and {@link AbstractContextLoader#prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)}
+ * for an alternative.
+ *
+ * @return a new application context
+ * @see org.springframework.test.context.ContextLoader#loadContext
+ * @see GenericApplicationContext
+ * @see #loadContext(MergedContextConfiguration)
+ * @since 2.5
+ */
+ public final ConfigurableApplicationContext loadContext(String... locations) throws Exception {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Loading ApplicationContext for locations [%s].",
+ StringUtils.arrayToCommaDelimitedString(locations)));
+ }
+ GenericApplicationContext context = new GenericApplicationContext();
+ prepareContext(context);
+ customizeBeanFactory(context.getDefaultListableBeanFactory());
+ createBeanDefinitionReader(context).loadBeanDefinitions(locations);
+ AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
+ customizeContext(context);
+ context.refresh();
+ context.registerShutdownHook();
+ return context;
+ }
+
+ /**
+ * Prepare the {@link GenericApplicationContext} created by this {@code ContextLoader}.
+ * Called <i>before</i> bean definitions are read.
+ *
+ * <p>The default implementation is empty. Can be overridden in subclasses to
+ * customize {@code GenericApplicationContext}'s standard settings.
+ *
+ * @param context the context that should be prepared
+ * @see #loadContext(MergedContextConfiguration)
+ * @see #loadContext(String...)
+ * @see GenericApplicationContext#setAllowBeanDefinitionOverriding
+ * @see GenericApplicationContext#setResourceLoader
+ * @see GenericApplicationContext#setId
+ * @since 2.5
+ */
+ protected void prepareContext(GenericApplicationContext context) {
+ }
+
+ /**
+ * Customize the internal bean factory of the ApplicationContext created by
+ * this {@code ContextLoader}.
+ *
+ * <p>The default implementation is empty but can be overridden in subclasses
+ * to customize {@code DefaultListableBeanFactory}'s standard settings.
+ *
+ * @param beanFactory the bean factory created by this {@code ContextLoader}
+ * @see #loadContext(MergedContextConfiguration)
+ * @see #loadContext(String...)
+ * @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
+ * @see DefaultListableBeanFactory#setAllowEagerClassLoading
+ * @see DefaultListableBeanFactory#setAllowCircularReferences
+ * @see DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping
+ * @since 2.5
+ */
+ protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
+ }
+
+ /**
+ * Load bean definitions into the supplied {@link GenericApplicationContext context}
+ * from the locations or classes in the supplied {@code MergedContextConfiguration}.
+ *
+ * <p>The default implementation delegates to the {@link BeanDefinitionReader}
+ * returned by {@link #createBeanDefinitionReader(GenericApplicationContext)} to
+ * {@link BeanDefinitionReader#loadBeanDefinitions(String) load} the
+ * bean definitions.
+ *
+ * <p>Subclasses must provide an appropriate implementation of
+ * {@link #createBeanDefinitionReader(GenericApplicationContext)}. Alternatively subclasses
+ * may provide a <em>no-op</em> implementation of {@code createBeanDefinitionReader()}
+ * and override this method to provide a custom strategy for loading or
+ * registering bean definitions.
+ *
+ * @param context the context into which the bean definitions should be loaded
+ * @param mergedConfig the merged context configuration
+ * @see #loadContext(MergedContextConfiguration)
+ * @since 3.1
+ */
+ protected void loadBeanDefinitions(GenericApplicationContext context, MergedContextConfiguration mergedConfig) {
+ createBeanDefinitionReader(context).loadBeanDefinitions(mergedConfig.getLocations());
+ }
+
+ /**
+ * Factory method for creating a new {@link BeanDefinitionReader} for loading
+ * bean definitions into the supplied {@link GenericApplicationContext context}.
+ *
+ * @param context the context for which the {@code BeanDefinitionReader}
+ * should be created
+ * @return a {@code BeanDefinitionReader} for the supplied context
+ * @see #loadContext(String...)
+ * @see #loadBeanDefinitions
+ * @see BeanDefinitionReader
+ * @since 2.5
+ */
+ protected abstract BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context);
+
+ /**
+ * Customize the {@link GenericApplicationContext} created by this
+ * {@code ContextLoader} <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 application context.
+ *
+ * @param context the newly created application context
+ * @see #loadContext(MergedContextConfiguration)
+ * @see #loadContext(String...)
+ * @since 2.5
+ */
+ protected void customizeContext(GenericApplicationContext context) {
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestExecutionListener.java
new file mode 100644
index 00000000..34dfe47e
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestExecutionListener.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.TestExecutionListener;
+
+/**
+ * Abstract implementation of the {@link TestExecutionListener} interface which
+ * provides empty method stubs. Subclasses can extend this class and override
+ * only those methods suitable for the task at hand.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public abstract class AbstractTestExecutionListener implements TestExecutionListener {
+
+ /**
+ * The default implementation is <em>empty</em>. Can be overridden by
+ * subclasses as necessary.
+ */
+ public void beforeTestClass(TestContext testContext) throws Exception {
+ /* no-op */
+ }
+
+ /**
+ * The default implementation is <em>empty</em>. Can be overridden by
+ * subclasses as necessary.
+ */
+ public void prepareTestInstance(TestContext testContext) throws Exception {
+ /* no-op */
+ }
+
+ /**
+ * The default implementation is <em>empty</em>. Can be overridden by
+ * subclasses as necessary.
+ */
+ public void beforeTestMethod(TestContext testContext) throws Exception {
+ /* no-op */
+ }
+
+ /**
+ * The default implementation is <em>empty</em>. Can be overridden by
+ * subclasses as necessary.
+ */
+ public void afterTestMethod(TestContext testContext) throws Exception {
+ /* no-op */
+ }
+
+ /**
+ * The default implementation is <em>empty</em>. Can be overridden by
+ * subclasses as necessary.
+ */
+ public void afterTestClass(TestContext testContext) throws Exception {
+ /* no-op */
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java
new file mode 100644
index 00000000..39e76f6e
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.support.BeanDefinitionReader;
+import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Concrete implementation of {@link AbstractGenericContextLoader} that loads
+ * bean definitions from annotated classes.
+ *
+ * <p>See the Javadoc for
+ * {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration}
+ * for a definition of <em>annotated class</em>.
+ *
+ * <p>Note: {@code AnnotationConfigContextLoader} supports <em>annotated classes</em>
+ * rather than the String-based resource locations defined by the legacy
+ * {@link org.springframework.test.context.ContextLoader ContextLoader} API. Thus,
+ * although {@code AnnotationConfigContextLoader} extends
+ * {@code AbstractGenericContextLoader}, {@code AnnotationConfigContextLoader}
+ * does <em>not</em> support any String-based methods defined by
+ * {@code AbstractContextLoader} or {@code AbstractGenericContextLoader}.
+ * Consequently, {@code AnnotationConfigContextLoader} should chiefly be
+ * considered a {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
+ * rather than a {@link org.springframework.test.context.ContextLoader ContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see #processContextConfiguration(ContextConfigurationAttributes)
+ * @see #detectDefaultConfigurationClasses(Class)
+ * @see #loadBeanDefinitions(GenericApplicationContext, MergedContextConfiguration)
+ */
+public class AnnotationConfigContextLoader extends AbstractGenericContextLoader {
+
+ private static final Log logger = LogFactory.getLog(AnnotationConfigContextLoader.class);
+
+
+ // --- SmartContextLoader -----------------------------------------------
+
+ /**
+ * Process <em>annotated classes</em> in the supplied {@link ContextConfigurationAttributes}.
+ *
+ * <p>If the <em>annotated classes</em> are {@code null} or empty and
+ * {@link #isGenerateDefaultLocations()} returns {@code true}, this
+ * {@code SmartContextLoader} will attempt to {@link
+ * #detectDefaultConfigurationClasses detect default configuration classes}.
+ * If defaults are detected they will be
+ * {@link ContextConfigurationAttributes#setClasses(Class[]) set} in the
+ * supplied configuration attributes. Otherwise, properties in the supplied
+ * configuration attributes will not be modified.
+ *
+ * @param configAttributes the context configuration attributes to process
+ * @see org.springframework.test.context.SmartContextLoader#processContextConfiguration(ContextConfigurationAttributes)
+ * @see #isGenerateDefaultLocations()
+ * @see #detectDefaultConfigurationClasses(Class)
+ */
+ public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
+ if (ObjectUtils.isEmpty(configAttributes.getClasses()) && isGenerateDefaultLocations()) {
+ Class<?>[] defaultConfigClasses = detectDefaultConfigurationClasses(configAttributes.getDeclaringClass());
+ configAttributes.setClasses(defaultConfigClasses);
+ }
+ }
+
+ // --- AnnotationConfigContextLoader ---------------------------------------
+
+ /**
+ * Detect the default configuration classes for the supplied test class.
+ *
+ * <p>The default implementation simply delegates to
+ * {@link AnnotationConfigContextLoaderUtils#detectDefaultConfigurationClasses(Class)}.
+ *
+ * @param declaringClass the test class that declared {@code @ContextConfiguration}
+ * @return an array of default configuration classes, potentially empty but
+ * never {@code null}
+ * @see AnnotationConfigContextLoaderUtils
+ */
+ protected Class<?>[] detectDefaultConfigurationClasses(Class<?> declaringClass) {
+ return AnnotationConfigContextLoaderUtils.detectDefaultConfigurationClasses(declaringClass);
+ }
+
+ // --- AbstractContextLoader -----------------------------------------------
+
+ /**
+ * {@code AnnotationConfigContextLoader} should be used as a
+ * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
+ * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
+ * Consequently, this method is not supported.
+ *
+ * @see AbstractContextLoader#modifyLocations
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ protected String[] modifyLocations(Class<?> clazz, String... locations) {
+ throw new UnsupportedOperationException(
+ "AnnotationConfigContextLoader does not support the modifyLocations(Class, String...) method");
+ }
+
+ /**
+ * {@code AnnotationConfigContextLoader} should be used as a
+ * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
+ * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
+ * Consequently, this method is not supported.
+ *
+ * @see AbstractContextLoader#generateDefaultLocations
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ protected String[] generateDefaultLocations(Class<?> clazz) {
+ throw new UnsupportedOperationException(
+ "AnnotationConfigContextLoader does not support the generateDefaultLocations(Class) method");
+ }
+
+ /**
+ * {@code AnnotationConfigContextLoader} should be used as a
+ * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
+ * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
+ * Consequently, this method is not supported.
+ *
+ * @see AbstractContextLoader#getResourceSuffix
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ protected String getResourceSuffix() {
+ throw new UnsupportedOperationException(
+ "AnnotationConfigContextLoader does not support the getResourceSuffix() method");
+ }
+
+ // --- AbstractGenericContextLoader ----------------------------------------
+
+ /**
+ * Register classes in the supplied {@link GenericApplicationContext context}
+ * from the classes in the supplied {@link MergedContextConfiguration}.
+ *
+ * <p>Each class must represent an <em>annotated class</em>. An
+ * {@link AnnotatedBeanDefinitionReader} is used to register the appropriate
+ * bean definitions.
+ *
+ * <p>Note that this method does not call {@link #createBeanDefinitionReader}
+ * since {@code AnnotatedBeanDefinitionReader} is not an instance of
+ * {@link BeanDefinitionReader}.
+ *
+ * @param context the context in which the annotated classes should be registered
+ * @param mergedConfig the merged configuration from which the classes should be retrieved
+ *
+ * @see AbstractGenericContextLoader#loadBeanDefinitions
+ */
+ @Override
+ protected void loadBeanDefinitions(GenericApplicationContext context, MergedContextConfiguration mergedConfig) {
+ Class<?>[] annotatedClasses = mergedConfig.getClasses();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Registering annotated classes: " + ObjectUtils.nullSafeToString(annotatedClasses));
+ }
+ new AnnotatedBeanDefinitionReader(context).register(annotatedClasses);
+ }
+
+ /**
+ * {@code AnnotationConfigContextLoader} should be used as a
+ * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
+ * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
+ * Consequently, this method is not supported.
+ *
+ * @see #loadBeanDefinitions
+ * @see AbstractGenericContextLoader#createBeanDefinitionReader
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) {
+ throw new UnsupportedOperationException(
+ "AnnotationConfigContextLoader does not support the createBeanDefinitionReader(GenericApplicationContext) method");
+ }
+
+}
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
new file mode 100644
index 00000000..0cdbe30f
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoaderUtils.java
@@ -0,0 +1,112 @@
+/*
+ * 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.context.support;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.SmartContextLoader;
+import org.springframework.util.Assert;
+
+/**
+ * Utility methods for {@link SmartContextLoader SmartContextLoaders} that deal
+ * with annotated classes (e.g., {@link Configuration @Configuration} classes).
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+public abstract class AnnotationConfigContextLoaderUtils {
+
+ private static final Log logger = LogFactory.getLog(AnnotationConfigContextLoaderUtils.class);
+
+
+ /**
+ * Determine if the supplied {@link Class} meets the criteria for being
+ * considered a <em>default configuration class</em> candidate.
+ * <p>Specifically, such candidates:
+ * <ul>
+ * <li>must not be {@code null}</li>
+ * <li>must not be {@code private}</li>
+ * <li>must not be {@code final}</li>
+ * <li>must be {@code static}</li>
+ * <li>must be annotated with {@code @Configuration}</li>
+ * </ul>
+ * @param clazz the class to check
+ * @return {@code true} if the supplied class meets the candidate criteria
+ */
+ private static boolean isDefaultConfigurationClassCandidate(Class<?> clazz) {
+ return clazz != null && isStaticNonPrivateAndNonFinal(clazz) && clazz.isAnnotationPresent(Configuration.class);
+ }
+
+ private static boolean isStaticNonPrivateAndNonFinal(Class<?> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ int modifiers = clazz.getModifiers();
+ return (Modifier.isStatic(modifiers) && !Modifier.isPrivate(modifiers) && !Modifier.isFinal(modifiers));
+ }
+
+ /**
+ * Detect the default configuration classes for the supplied test class.
+ * <p>The returned class array will contain all static inner classes of
+ * the supplied class that meet the requirements for {@code @Configuration}
+ * class implementations as specified in the documentation for
+ * {@link Configuration @Configuration}.
+ * <p>The implementation of this method adheres to the contract defined in the
+ * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
+ * SPI. Specifically, this method uses introspection to detect default
+ * configuration classes that comply with the constraints required of
+ * {@code @Configuration} class implementations. If a potential candidate
+ * configuration class does not meet these requirements, this method will log a
+ * debug message, and the potential candidate class will be ignored.
+ * @param declaringClass the test class that declared {@code @ContextConfiguration}
+ * @return an array of default configuration classes, potentially empty but
+ * never {@code null}
+ */
+ public static Class<?>[] detectDefaultConfigurationClasses(Class<?> declaringClass) {
+ Assert.notNull(declaringClass, "Declaring class must not be null");
+ List<Class<?>> configClasses = new ArrayList<Class<?>>();
+
+ for (Class<?> candidate : declaringClass.getDeclaredClasses()) {
+ if (isDefaultConfigurationClassCandidate(candidate)) {
+ configClasses.add(candidate);
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format(
+ "Ignoring class [%s]; it must be static, non-private, non-final, and annotated " +
+ "with @Configuration to be considered a default configuration class.",
+ candidate.getName()));
+ }
+ }
+ }
+
+ if (configClasses.isEmpty()) {
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format("Could not detect default configuration classes for test class [%s]: " +
+ "%s does not declare any static, non-private, non-final, inner classes " +
+ "annotated with @Configuration.", declaringClass.getName(), declaringClass.getSimpleName()));
+ }
+ }
+
+ return configClasses.toArray(new Class<?>[configClasses.size()]);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java
new file mode 100644
index 00000000..32442fe3
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import org.springframework.test.context.SmartContextLoader;
+
+/**
+ * {@code DelegatingSmartContextLoader} is a concrete implementation of
+ * {@link AbstractDelegatingSmartContextLoader} that delegates to a
+ * {@link GenericXmlContextLoader} and an {@link AnnotationConfigContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see SmartContextLoader
+ * @see AbstractDelegatingSmartContextLoader
+ * @see GenericXmlContextLoader
+ * @see AnnotationConfigContextLoader
+ */
+public class DelegatingSmartContextLoader extends AbstractDelegatingSmartContextLoader {
+
+ private final SmartContextLoader xmlLoader = new GenericXmlContextLoader();
+ private final SmartContextLoader annotationConfigLoader = new AnnotationConfigContextLoader();
+
+
+ protected SmartContextLoader getXmlLoader() {
+ return this.xmlLoader;
+ }
+
+ protected SmartContextLoader getAnnotationConfigLoader() {
+ return this.annotationConfigLoader;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DependencyInjectionTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/support/DependencyInjectionTestExecutionListener.java
new file mode 100644
index 00000000..cb852237
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DependencyInjectionTestExecutionListener.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.core.Conventions;
+import org.springframework.test.context.TestContext;
+
+/**
+ * {@code TestExecutionListener} which provides support for dependency
+ * injection and initialization of test instances.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class DependencyInjectionTestExecutionListener extends AbstractTestExecutionListener {
+
+ /**
+ * Attribute name for a {@link TestContext} attribute which indicates
+ * whether or not the dependencies of a test instance should be
+ * <em>reinjected</em> in
+ * {@link #beforeTestMethod(TestContext) beforeTestMethod()}. Note that
+ * dependencies will be injected in
+ * {@link #prepareTestInstance(TestContext) prepareTestInstance()} in any
+ * case.
+ * <p>Clients of a {@link TestContext} (e.g., other
+ * {@link org.springframework.test.context.TestExecutionListener TestExecutionListeners})
+ * may therefore choose to set this attribute to signal that dependencies
+ * should be reinjected <em>between</em> execution of individual test
+ * methods.
+ * <p>Permissible values include {@link Boolean#TRUE} and {@link Boolean#FALSE}.
+ */
+ public static final String REINJECT_DEPENDENCIES_ATTRIBUTE = Conventions.getQualifiedAttributeName(
+ DependencyInjectionTestExecutionListener.class, "reinjectDependencies");
+
+ private static final Log logger = LogFactory.getLog(DependencyInjectionTestExecutionListener.class);
+
+
+ /**
+ * Performs dependency injection on the
+ * {@link TestContext#getTestInstance() test instance} of the supplied
+ * {@link TestContext test context} by
+ * {@link AutowireCapableBeanFactory#autowireBeanProperties(Object, int, boolean) autowiring}
+ * and
+ * {@link AutowireCapableBeanFactory#initializeBean(Object, String) initializing}
+ * the test instance via its own
+ * {@link TestContext#getApplicationContext() application context} (without
+ * checking dependencies).
+ * <p>The {@link #REINJECT_DEPENDENCIES_ATTRIBUTE} will be subsequently removed
+ * from the test context, regardless of its value.
+ */
+ @Override
+ public void prepareTestInstance(final TestContext testContext) throws Exception {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Performing dependency injection for test context [" + testContext + "].");
+ }
+ injectDependencies(testContext);
+ }
+
+ /**
+ * If the {@link #REINJECT_DEPENDENCIES_ATTRIBUTE} in the supplied
+ * {@link TestContext test context} has a value of {@link Boolean#TRUE},
+ * this method will have the same effect as
+ * {@link #prepareTestInstance(TestContext) prepareTestInstance()};
+ * otherwise, this method will have no effect.
+ */
+ @Override
+ public void beforeTestMethod(final TestContext testContext) throws Exception {
+ if (Boolean.TRUE.equals(testContext.getAttribute(REINJECT_DEPENDENCIES_ATTRIBUTE))) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Reinjecting dependencies for test context [" + testContext + "].");
+ }
+ injectDependencies(testContext);
+ }
+ }
+
+ /**
+ * Performs dependency injection and bean initialization for the supplied
+ * {@link TestContext} as described in
+ * {@link #prepareTestInstance(TestContext) prepareTestInstance()}.
+ * <p>The {@link #REINJECT_DEPENDENCIES_ATTRIBUTE} will be subsequently removed
+ * from the test context, regardless of its value.
+ * @param testContext the test context for which dependency injection should
+ * be performed (never {@code null})
+ * @throws Exception allows any exception to propagate
+ * @see #prepareTestInstance(TestContext)
+ * @see #beforeTestMethod(TestContext)
+ */
+ protected void injectDependencies(final TestContext testContext) throws Exception {
+ Object bean = testContext.getTestInstance();
+ AutowireCapableBeanFactory beanFactory = testContext.getApplicationContext().getAutowireCapableBeanFactory();
+ beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false);
+ beanFactory.initializeBean(bean, testContext.getTestClass().getName());
+ testContext.removeAttribute(REINJECT_DEPENDENCIES_ATTRIBUTE);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java
new file mode 100644
index 00000000..f2a45a46
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DirtiesContextTestExecutionListener.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.support;
+
+import java.lang.reflect.Method;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
+import org.springframework.test.context.TestContext;
+import org.springframework.util.Assert;
+
+/**
+ * {@code TestExecutionListener} which provides support for marking the
+ * {@code ApplicationContext} associated with a test as <em>dirty</em> for
+ * both test classes and test methods configured with the {@link DirtiesContext
+ * &#064;DirtiesContext} annotation.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see DirtiesContext
+ */
+public class DirtiesContextTestExecutionListener extends AbstractTestExecutionListener {
+
+ private static final Log logger = LogFactory.getLog(DirtiesContextTestExecutionListener.class);
+
+
+ /**
+ * Marks the {@linkplain ApplicationContext application context} of the supplied
+ * {@linkplain TestContext test context} as
+ * {@linkplain TestContext#markApplicationContextDirty() dirty}, and sets the
+ * {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
+ * in the test context to {@code true}.
+ * @param testContext the test context whose application context should
+ * marked as dirty
+ * @deprecated as of Spring 3.2.2, use {@link #dirtyContext(TestContext, HierarchyMode)} instead.
+ */
+ @Deprecated
+ protected void dirtyContext(TestContext testContext) {
+ testContext.markApplicationContextDirty();
+ testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
+ }
+
+ /**
+ * Marks the {@linkplain ApplicationContext application context} of the supplied
+ * {@linkplain TestContext test context} as
+ * {@linkplain TestContext#markApplicationContextDirty(DirtiesContext.HierarchyMode) dirty}
+ * and sets {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
+ * in the test context to {@code true}.
+ * @param testContext the test context whose application context should
+ * marked as dirty
+ * @param hierarchyMode the context cache clearing mode to be applied if the
+ * context is part of a hierarchy; may be {@code null}
+ * @since 3.2.2
+ */
+ protected void dirtyContext(TestContext testContext, HierarchyMode hierarchyMode) {
+ testContext.markApplicationContextDirty(hierarchyMode);
+ testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
+ }
+
+ /**
+ * If the current test method of the supplied {@linkplain TestContext test
+ * context} is annotated with {@link DirtiesContext &#064;DirtiesContext},
+ * or if the test class is annotated with {@link DirtiesContext
+ * &#064;DirtiesContext} and the {@linkplain DirtiesContext#classMode() class
+ * mode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
+ * AFTER_EACH_TEST_METHOD}, the {@linkplain ApplicationContext application
+ * context} of the test context will be
+ * {@linkplain TestContext#markApplicationContextDirty() marked as dirty} and the
+ * {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE}
+ * in the test context will be set to {@code true}.
+ */
+ @Override
+ public void afterTestMethod(TestContext testContext) throws Exception {
+ Class<?> testClass = testContext.getTestClass();
+ Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
+ Method testMethod = testContext.getTestMethod();
+ Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
+
+ final Class<DirtiesContext> annotationType = DirtiesContext.class;
+
+ boolean methodDirtiesContext = testMethod.isAnnotationPresent(annotationType);
+ boolean classDirtiesContext = testClass.isAnnotationPresent(annotationType);
+ DirtiesContext classDirtiesContextAnnotation = testClass.getAnnotation(annotationType);
+ ClassMode classMode = classDirtiesContext ? classDirtiesContextAnnotation.classMode() : null;
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("After test method: context [" + testContext + "], class dirties context ["
+ + classDirtiesContext + "], class mode [" + classMode + "], method dirties context ["
+ + methodDirtiesContext + "].");
+ }
+
+ if (methodDirtiesContext || (classDirtiesContext && classMode == ClassMode.AFTER_EACH_TEST_METHOD)) {
+ HierarchyMode hierarchyMode = methodDirtiesContext ? testMethod.getAnnotation(annotationType).hierarchyMode()
+ : classDirtiesContextAnnotation.hierarchyMode();
+ dirtyContext(testContext, hierarchyMode);
+ }
+ }
+
+ /**
+ * If the test class of the supplied {@linkplain TestContext test context} is
+ * annotated with {@link DirtiesContext &#064;DirtiesContext}, the
+ * {@linkplain ApplicationContext application context} of the test context will
+ * be {@linkplain TestContext#markApplicationContextDirty() marked as dirty} ,
+ * and the
+ * {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
+ * REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
+ * {@code true}.
+ */
+ @Override
+ public void afterTestClass(TestContext testContext) throws Exception {
+ Class<?> testClass = testContext.getTestClass();
+ Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
+
+ final Class<DirtiesContext> annotationType = DirtiesContext.class;
+
+ boolean dirtiesContext = testClass.isAnnotationPresent(annotationType);
+ if (logger.isDebugEnabled()) {
+ logger.debug("After test class: context [" + testContext + "], dirtiesContext [" + dirtiesContext + "].");
+ }
+ if (dirtiesContext) {
+ HierarchyMode hierarchyMode = testClass.getAnnotation(annotationType).hierarchyMode();
+ dirtyContext(testContext, hierarchyMode);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/GenericPropertiesContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/GenericPropertiesContextLoader.java
new file mode 100644
index 00000000..82368879
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/GenericPropertiesContextLoader.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import java.util.Properties;
+
+import org.springframework.beans.factory.support.BeanDefinitionReader;
+import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;
+import org.springframework.context.support.GenericApplicationContext;
+
+/**
+ * Concrete implementation of {@link AbstractGenericContextLoader} that reads
+ * bean definitions from Java {@link Properties} resources.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class GenericPropertiesContextLoader extends AbstractGenericContextLoader {
+
+ /**
+ * Creates a new {@link PropertiesBeanDefinitionReader}.
+ * @return a new PropertiesBeanDefinitionReader
+ * @see PropertiesBeanDefinitionReader
+ */
+ @Override
+ protected BeanDefinitionReader createBeanDefinitionReader(final GenericApplicationContext context) {
+ return new PropertiesBeanDefinitionReader(context);
+ }
+
+ /**
+ * Returns &quot;{@code -context.properties}&quot;.
+ */
+ @Override
+ public String getResourceSuffix() {
+ return "-context.properties";
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/GenericXmlContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/GenericXmlContextLoader.java
new file mode 100644
index 00000000..9c636e0e
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/GenericXmlContextLoader.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import org.springframework.beans.factory.support.BeanDefinitionReader;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.context.support.GenericApplicationContext;
+
+/**
+ * Concrete implementation of {@link AbstractGenericContextLoader} that reads
+ * bean definitions from XML resources.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class GenericXmlContextLoader extends AbstractGenericContextLoader {
+
+ /**
+ * Create a new {@link XmlBeanDefinitionReader}.
+ * @return a new XmlBeanDefinitionReader
+ * @see XmlBeanDefinitionReader
+ */
+ @Override
+ protected BeanDefinitionReader createBeanDefinitionReader(final GenericApplicationContext context) {
+ return new XmlBeanDefinitionReader(context);
+ }
+
+ /**
+ * Returns &quot;{@code -context.xml}&quot;.
+ */
+ @Override
+ public String getResourceSuffix() {
+ return "-context.xml";
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/package-info.java b/spring-test/src/main/java/org/springframework/test/context/support/package-info.java
new file mode 100644
index 00000000..0212c4a0
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/support/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * <p>Support classes for the <em>Spring TestContext Framework</em>.</p>
+ */
+
+package org.springframework.test.context.support;
+
diff --git a/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTestNGSpringContextTests.java b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTestNGSpringContextTests.java
new file mode 100644
index 00000000..80c74a5b
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTestNGSpringContextTests.java
@@ -0,0 +1,210 @@
+/*
+ * 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.context.testng;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.TestContextManager;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
+import org.springframework.test.context.web.ServletTestExecutionListener;
+
+import org.testng.IHookCallBack;
+import org.testng.IHookable;
+import org.testng.ITestResult;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+
+/**
+ * Abstract base test class which integrates the <em>Spring TestContext Framework</em>
+ * with explicit {@link ApplicationContext} testing support in a <strong>TestNG</strong>
+ * environment.
+ *
+ * <p>Concrete subclasses:
+ * <ul>
+ * <li>Typically declare a class-level {@link ContextConfiguration
+ * &#064;ContextConfiguration} annotation to configure the {@link ApplicationContext
+ * application context} {@link ContextConfiguration#locations() resource locations}
+ * or {@link ContextConfiguration#classes() annotated classes}. <em>If your test
+ * does not need to load an application context, you may choose to omit the
+ * {@link ContextConfiguration &#064;ContextConfiguration} declaration and to
+ * configure the appropriate
+ * {@link org.springframework.test.context.TestExecutionListener TestExecutionListeners}
+ * manually.</em></li>
+ * <li>Must have constructors which either implicitly or explicitly delegate to
+ * {@code super();}.</li>
+ * </ul>
+ *
+ * <p>The following {@link org.springframework.test.context.TestExecutionListener
+ * TestExecutionListeners} are configured by default:
+ *
+ * <ul>
+ * <li>{@link org.springframework.test.context.web.ServletTestExecutionListener}
+ * <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener}
+ * <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener}
+ * </ul>
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see ContextConfiguration
+ * @see TestContext
+ * @see TestContextManager
+ * @see TestExecutionListeners
+ * @see ServletTestExecutionListener
+ * @see DependencyInjectionTestExecutionListener
+ * @see DirtiesContextTestExecutionListener
+ * @see AbstractTransactionalTestNGSpringContextTests
+ * @see org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests
+ */
+@TestExecutionListeners({ ServletTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
+ DirtiesContextTestExecutionListener.class })
+public abstract class AbstractTestNGSpringContextTests implements IHookable, ApplicationContextAware {
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ /**
+ * The {@link ApplicationContext} that was injected into this test instance
+ * via {@link #setApplicationContext(ApplicationContext)}.
+ */
+ protected ApplicationContext applicationContext;
+
+ private final TestContextManager testContextManager;
+
+ private Throwable testException;
+
+
+ /**
+ * Construct a new AbstractTestNGSpringContextTests instance and initialize
+ * the internal {@link TestContextManager} for the current test.
+ */
+ public AbstractTestNGSpringContextTests() {
+ this.testContextManager = new TestContextManager(getClass());
+ }
+
+ /**
+ * Set the {@link ApplicationContext} to be used by this test instance,
+ * provided via {@link ApplicationContextAware} semantics.
+ *
+ * @param applicationContext the applicationContext to set
+ */
+ public final void setApplicationContext(ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ /**
+ * Delegates to the configured {@link TestContextManager} to call
+ * {@link TestContextManager#beforeTestClass() 'before test class'}
+ * callbacks.
+ *
+ * @throws Exception if a registered TestExecutionListener throws an
+ * exception
+ */
+ @BeforeClass(alwaysRun = true)
+ protected void springTestContextBeforeTestClass() throws Exception {
+ this.testContextManager.beforeTestClass();
+ }
+
+ /**
+ * Delegates to the configured {@link TestContextManager} to
+ * {@link TestContextManager#prepareTestInstance(Object) prepare} this test
+ * instance prior to execution of any individual tests, for example for
+ * injecting dependencies, etc.
+ *
+ * @throws Exception if a registered TestExecutionListener throws an
+ * exception
+ */
+ @BeforeClass(alwaysRun = true, dependsOnMethods = "springTestContextBeforeTestClass")
+ protected void springTestContextPrepareTestInstance() throws Exception {
+ this.testContextManager.prepareTestInstance(this);
+ }
+
+ /**
+ * Delegates to the configured {@link TestContextManager} to
+ * {@link TestContextManager#beforeTestMethod(Object,Method) pre-process}
+ * the test method before the actual test is executed.
+ *
+ * @param testMethod the test method which is about to be executed.
+ * @throws Exception allows all exceptions to propagate.
+ */
+ @BeforeMethod(alwaysRun = true)
+ protected void springTestContextBeforeTestMethod(Method testMethod) throws Exception {
+ this.testContextManager.beforeTestMethod(this, testMethod);
+ }
+
+ /**
+ * Delegates to the {@link IHookCallBack#runTestMethod(ITestResult) test
+ * method} in the supplied {@code callback} to execute the actual test
+ * and then tracks the exception thrown during test execution, if any.
+ *
+ * @see org.testng.IHookable#run(org.testng.IHookCallBack,
+ * org.testng.ITestResult)
+ */
+ public void run(IHookCallBack callBack, ITestResult testResult) {
+ callBack.runTestMethod(testResult);
+
+ Throwable testResultException = testResult.getThrowable();
+ if (testResultException instanceof InvocationTargetException) {
+ testResultException = ((InvocationTargetException) testResultException).getCause();
+ }
+ this.testException = testResultException;
+ }
+
+ /**
+ * Delegates to the configured {@link TestContextManager} to
+ * {@link TestContextManager#afterTestMethod(Object, Method, Throwable)
+ * post-process} the test method after the actual test has executed.
+ *
+ * @param testMethod the test method which has just been executed on the
+ * test instance
+ * @throws Exception allows all exceptions to propagate
+ */
+ @AfterMethod(alwaysRun = true)
+ protected void springTestContextAfterTestMethod(Method testMethod) throws Exception {
+ try {
+ this.testContextManager.afterTestMethod(this, testMethod, this.testException);
+ }
+ finally {
+ this.testException = null;
+ }
+ }
+
+ /**
+ * Delegates to the configured {@link TestContextManager} to call
+ * {@link TestContextManager#afterTestClass() 'after test class'} callbacks.
+ *
+ * @throws Exception if a registered TestExecutionListener throws an
+ * exception
+ */
+ @AfterClass(alwaysRun = true)
+ protected void springTestContextAfterTestClass() throws Exception {
+ this.testContextManager.afterTestClass();
+ }
+
+}
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
new file mode 100644
index 00000000..41e70a70
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/testng/AbstractTransactionalTestNGSpringContextTests.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2002-2012 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.testng;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
+import org.springframework.test.jdbc.JdbcTestUtils;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Abstract {@linkplain Transactional transactional} extension of
+ * {@link AbstractTestNGSpringContextTests} which adds convenience functionality
+ * for JDBC access. Expects a {@link DataSource} bean and a
+ * {@link PlatformTransactionManager} bean to be defined in the Spring
+ * {@linkplain ApplicationContext application context}.
+ *
+ * <p>This class exposes a {@link JdbcTemplate} and provides an easy way to
+ * {@linkplain #countRowsInTable count the number of rows in a table}
+ * (potentially {@linkplain #countRowsInTableWhere with a WHERE clause}),
+ * {@linkplain #deleteFromTables delete from tables},
+ * {@linkplain #dropTables drop tables}, and
+ * {@linkplain #executeSqlScript execute SQL scripts} within a transaction.
+ *
+ * <p>Concrete subclasses must fulfill the same requirements outlined in
+ * {@link AbstractTestNGSpringContextTests}.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see AbstractTestNGSpringContextTests
+ * @see org.springframework.test.context.ContextConfiguration
+ * @see org.springframework.test.context.TestExecutionListeners
+ * @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
+ * @see org.springframework.test.context.transaction.TransactionConfiguration
+ * @see org.springframework.transaction.annotation.Transactional
+ * @see org.springframework.test.annotation.NotTransactional
+ * @see org.springframework.test.annotation.Rollback
+ * @see org.springframework.test.context.transaction.BeforeTransaction
+ * @see org.springframework.test.context.transaction.AfterTransaction
+ * @see org.springframework.test.jdbc.JdbcTestUtils
+ * @see org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
+ */
+@TestExecutionListeners(TransactionalTestExecutionListener.class)
+@Transactional
+@SuppressWarnings("deprecation")
+public abstract class AbstractTransactionalTestNGSpringContextTests extends AbstractTestNGSpringContextTests {
+
+ /**
+ * The {@code SimpleJdbcTemplate} that this base class manages, available to subclasses.
+ * @deprecated As of Spring 3.2, use {@link #jdbcTemplate} instead.
+ */
+ @Deprecated
+ protected SimpleJdbcTemplate simpleJdbcTemplate;
+
+ /**
+ * The {@code JdbcTemplate} that this base class manages, available to subclasses.
+ * @since 3.2
+ */
+ protected JdbcTemplate jdbcTemplate;
+
+ private String sqlScriptEncoding;
+
+
+ /**
+ * Set the {@code DataSource}, typically provided via Dependency Injection.
+ * <p>This method also instantiates the {@link #simpleJdbcTemplate} and
+ * {@link #jdbcTemplate} instance variables.
+ */
+ @Autowired
+ public void setDataSource(DataSource dataSource) {
+ this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ /**
+ * Specify the encoding for SQL scripts, if different from the platform encoding.
+ * @see #executeSqlScript
+ */
+ public void setSqlScriptEncoding(String sqlScriptEncoding) {
+ this.sqlScriptEncoding = sqlScriptEncoding;
+ }
+
+ /**
+ * Count the rows in the given table.
+ * @param tableName table name to count rows in
+ * @return the number of rows in the table
+ */
+ protected int countRowsInTable(String tableName) {
+ return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
+ }
+
+ /**
+ * Count the rows in the given table, using the provided {@code WHERE} clause.
+ * <p>See the Javadoc for {@link JdbcTestUtils#countRowsInTableWhere} for details.
+ * @param tableName the name of the table to count rows in
+ * @param whereClause the {@code WHERE} clause to append to the query
+ * @return the number of rows in the table that match the provided
+ * {@code WHERE} clause
+ * @since 3.2
+ */
+ protected int countRowsInTableWhere(String tableName, String whereClause) {
+ return JdbcTestUtils.countRowsInTableWhere(this.jdbcTemplate, tableName, whereClause);
+ }
+
+ /**
+ * Convenience method for deleting all rows from the specified tables. Use
+ * with caution outside of a transaction!
+ * @param names the names of the tables from which to delete
+ * @return the total number of rows deleted from all specified tables
+ */
+ protected int deleteFromTables(String... names) {
+ return JdbcTestUtils.deleteFromTables(this.jdbcTemplate, names);
+ }
+
+ /**
+ * Convenience method for dropping all of the specified tables. Use
+ * with caution outside of a transaction!
+ * @param names the names of the tables to drop
+ * @since 3.2
+ */
+ protected void dropTables(String... names) {
+ JdbcTestUtils.dropTables(this.jdbcTemplate, names);
+ }
+
+ /**
+ * Execute the given SQL script. Use with caution outside of a transaction!
+ * <p>The script will normally be loaded by classpath. There should be one
+ * statement per line. Any semicolons will be removed. <b>Do not use this
+ * method to execute DDL if you expect rollback.</b>
+ * @param sqlResourcePath the Spring resource path for the SQL script
+ * @param continueOnError whether or not to continue without throwing an
+ * exception in the event of an error
+ * @throws DataAccessException if there is an error executing a statement
+ * and continueOnError was {@code false}
+ */
+ protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
+ Resource resource = this.applicationContext.getResource(sqlResourcePath);
+ JdbcTestUtils.executeSqlScript(this.jdbcTemplate, new EncodedResource(resource,
+ this.sqlScriptEncoding), continueOnError);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/testng/package-info.java b/spring-test/src/main/java/org/springframework/test/context/testng/package-info.java
new file mode 100644
index 00000000..3d2045a4
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/testng/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * <p>Support classes for ApplicationContext-based and transactional
+ * tests run with TestNG and the <em>Spring TestContext Framework</em>.</p>
+ */
+
+package org.springframework.test.context.testng;
+
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
new file mode 100644
index 00000000..bb25f1f8
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2012 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 java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Test annotation to indicate that the annotated {@code public void}
+ * method should be executed <em>after</em> a transaction is ended for test
+ * methods configured to run within a transaction via the
+ * {@code &#064;Transactional} annotation.
+ * </p>
+ * <p>
+ * The {@code &#064;AfterTransaction} methods of superclasses will be
+ * executed after those of the current class.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see org.springframework.transaction.annotation.Transactional
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface AfterTransaction {
+}
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
new file mode 100644
index 00000000..d3625009
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2012 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 java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>
+ * Test annotation to indicate that the annotated {@code public void}
+ * method should be executed <em>before</em> a transaction is started for test
+ * methods configured to run within a transaction via the
+ * {@code &#064;Transactional} annotation.
+ * </p>
+ * <p>
+ * The {@code &#064;BeforeTransaction} methods of superclasses will be
+ * executed before those of the current class.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see org.springframework.transaction.annotation.Transactional
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface BeforeTransaction {
+}
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
new file mode 100644
index 00000000..7d91bcba
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfiguration.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2012 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 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;
+
+/**
+ * {@code TransactionConfiguration} defines class-level metadata for configuring
+ * transactional tests.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see TransactionalTestExecutionListener
+ * @see org.springframework.test.context.ContextConfiguration
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface TransactionConfiguration {
+
+ /**
+ * The bean name of the {@link org.springframework.transaction.PlatformTransactionManager
+ * PlatformTransactionManager} that should be used to drive transactions.
+ *
+ * <p>This attribute is not required and only needs to be declared if there
+ * are multiple beans of type {@code PlatformTransactionManager} in the test's
+ * {@code ApplicationContext} <em>and</em> if one of the following is true.
+ * <ul>
+ * <li>the bean name of the desired {@code PlatformTransactionManager} is not
+ * "transactionManager"</li>
+ * <li>{@link org.springframework.transaction.annotation.TransactionManagementConfigurer
+ * TransactionManagementConfigurer} was not implemented to specify which
+ * {@code PlatformTransactionManager} bean should be used for annotation-driven
+ * transaction management
+ * </ul>
+ *
+ * <p><b>NOTE:</b> The XML {@code <tx:annotation-driven>} element also refers
+ * to a bean named "transactionManager" by default. If you are using both
+ * features in combination, make sure to point to the same transaction manager
+ * bean - here in {@code @TransactionConfiguration} and also in
+ * {@code <tx:annotation-driven transaction-manager="...">}.
+ */
+ String transactionManager() default "transactionManager";
+
+ /**
+ * Should transactions be rolled back by default?
+ */
+ boolean defaultRollback() default true;
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfigurationAttributes.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfigurationAttributes.java
new file mode 100644
index 00000000..c6124703
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionConfigurationAttributes.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2008 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 org.springframework.core.style.ToStringCreator;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.util.Assert;
+
+/**
+ * Configuration attributes for configuring transactional tests.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see TransactionConfiguration
+ */
+public class TransactionConfigurationAttributes {
+
+ private final String transactionManagerName;
+
+ private final boolean defaultRollback;
+
+
+ /**
+ * Construct a new TransactionConfigurationAttributes instance from the
+ * supplied arguments.
+ * @param transactionManagerName the bean name of the
+ * {@link PlatformTransactionManager} that is to be used to drive transactions
+ * @param defaultRollback whether or not transactions should be rolled back by default
+ */
+ public TransactionConfigurationAttributes(String transactionManagerName, boolean defaultRollback) {
+ Assert.notNull(transactionManagerName, "transactionManagerName can not be null");
+ this.transactionManagerName = transactionManagerName;
+ this.defaultRollback = defaultRollback;
+ }
+
+
+ /**
+ * Get the bean name of the {@link PlatformTransactionManager} that is to
+ * be used to drive transactions.
+ */
+ public final String getTransactionManagerName() {
+ return this.transactionManagerName;
+ }
+
+ /**
+ * Whether or not transactions should be rolled back by default.
+ * @return the <em>default rollback</em> flag
+ */
+ public final boolean isDefaultRollback() {
+ return this.defaultRollback;
+ }
+
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this)
+ .append("transactionManagerName", this.transactionManagerName)
+ .append("defaultRollback", this.defaultRollback)
+ .toString();
+ }
+
+}
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
new file mode 100644
index 00000000..5535fa97
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java
@@ -0,0 +1,596 @@
+/*
+ * Copyright 2002-2012 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 java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.test.annotation.NotTransactional;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.support.AbstractTestExecutionListener;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
+import org.springframework.transaction.annotation.TransactionManagementConfigurer;
+import org.springframework.transaction.interceptor.DelegatingTransactionAttribute;
+import org.springframework.transaction.interceptor.TransactionAttribute;
+import org.springframework.transaction.interceptor.TransactionAttributeSource;
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@code TestExecutionListener} that provides support for executing tests
+ * within transactions by honoring the
+ * {@link org.springframework.transaction.annotation.Transactional &#064;Transactional}
+ * and {@link NotTransactional &#064;NotTransactional} annotations. Expects a
+ * {@link PlatformTransactionManager} bean to be defined in the Spring
+ * {@link ApplicationContext} for the test.
+ *
+ * <p>Changes to the database during a test that is run with {@code @Transactional}
+ * will be run within a transaction that will, by default, be automatically
+ * <em>rolled back</em> after completion of the test; whereas, changes to the
+ * database during a test that is run with {@code @NotTransactional} will
+ * <strong>not</strong> be run within a transaction. Test methods that are not
+ * annotated with {@code @Transactional} (at the class or method level) will not
+ * be run within a transaction.
+ *
+ * <p>Transactional commit and rollback behavior can be configured via the
+ * class-level {@link TransactionConfiguration @TransactionConfiguration} and
+ * method-level {@link Rollback @Rollback} annotations.
+ *
+ * <p>In case there are multiple instances of {@code PlatformTransactionManager}
+ * within the test's {@code ApplicationContext}, {@code @TransactionConfiguration}
+ * supports configuring the bean name of the {@code PlatformTransactionManager}
+ * that should be used to drive transactions. Alternatively,
+ * {@link TransactionManagementConfigurer} can be implemented in an
+ * {@link org.springframework.context.annotation.Configuration @Configuration}
+ * class.
+ *
+ * <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} and
+ * {@link AfterTransaction @AfterTransaction}.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see TransactionConfiguration
+ * @see TransactionManagementConfigurer
+ * @see org.springframework.transaction.annotation.Transactional
+ * @see org.springframework.test.annotation.NotTransactional
+ * @see org.springframework.test.annotation.Rollback
+ * @see BeforeTransaction
+ * @see AfterTransaction
+ */
+@SuppressWarnings("deprecation")
+public class TransactionalTestExecutionListener extends AbstractTestExecutionListener {
+
+ private static final Log logger = LogFactory.getLog(TransactionalTestExecutionListener.class);
+
+ private static final String DEFAULT_TRANSACTION_MANAGER_NAME = (String) AnnotationUtils.getDefaultValue(
+ TransactionConfiguration.class, "transactionManager");
+
+ private static final Boolean DEFAULT_DEFAULT_ROLLBACK = (Boolean) AnnotationUtils.getDefaultValue(
+ TransactionConfiguration.class, "defaultRollback");
+
+ protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource();
+
+ private final Map<Method, TransactionContext> transactionContextCache =
+ new ConcurrentHashMap<Method, TransactionContext>(8);
+
+ private TransactionConfigurationAttributes configurationAttributes;
+
+ private volatile int transactionsStarted = 0;
+
+
+ /**
+ * If the test method of the supplied {@link TestContext test context} is
+ * configured to run within a transaction, this method will run
+ * {@link BeforeTransaction &#064;BeforeTransaction methods} and start a new
+ * transaction.
+ * <p>Note that if a {@code @BeforeTransaction} method fails, any remaining
+ * {@code @BeforeTransaction} methods will not be invoked, and a transaction
+ * will not be started.
+ * @see org.springframework.transaction.annotation.Transactional
+ * @see org.springframework.test.annotation.NotTransactional
+ * @see #getTransactionManager(TestContext, String)
+ */
+ @SuppressWarnings("serial")
+ @Override
+ public void beforeTestMethod(TestContext testContext) throws Exception {
+ final Method testMethod = testContext.getTestMethod();
+ Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
+
+ if (this.transactionContextCache.remove(testMethod) != null) {
+ throw new IllegalStateException("Cannot start new transaction without ending existing transaction: "
+ + "Invoke endTransaction() before startNewTransaction().");
+ }
+
+ if (testMethod.isAnnotationPresent(NotTransactional.class)) {
+ return;
+ }
+
+ PlatformTransactionManager tm = null;
+ TransactionAttribute transactionAttribute = this.attributeSource.getTransactionAttribute(testMethod,
+ testContext.getTestClass());
+
+ if (transactionAttribute != null) {
+ transactionAttribute = new DelegatingTransactionAttribute(transactionAttribute) {
+
+ public String getName() {
+ return testMethod.getName();
+ }
+ };
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Explicit transaction definition [" + transactionAttribute + "] found for test context "
+ + testContext);
+ }
+ tm = getTransactionManager(testContext, transactionAttribute.getQualifier());
+ }
+
+ if (tm != null) {
+ TransactionContext txContext = new TransactionContext(tm, transactionAttribute);
+ runBeforeTransactionMethods(testContext);
+ startNewTransaction(testContext, txContext);
+ this.transactionContextCache.put(testMethod, txContext);
+ }
+ }
+
+ /**
+ * If a transaction is currently active for the test method of the supplied
+ * {@link TestContext test context}, this method will end the transaction
+ * and run {@link AfterTransaction &#064;AfterTransaction methods}.
+ * <p>{@code @AfterTransaction} methods are guaranteed to be
+ * invoked even if an error occurs while ending the transaction.
+ */
+ @Override
+ public void afterTestMethod(TestContext testContext) throws Exception {
+ Method testMethod = testContext.getTestMethod();
+ Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
+
+ // If the transaction is still active...
+ TransactionContext txContext = this.transactionContextCache.remove(testMethod);
+ if (txContext != null && !txContext.transactionStatus.isCompleted()) {
+ try {
+ endTransaction(testContext, txContext);
+ }
+ finally {
+ runAfterTransactionMethods(testContext);
+ }
+ }
+ }
+
+ /**
+ * Run all {@link BeforeTransaction &#064;BeforeTransaction methods} for the
+ * specified {@link TestContext test context}. If one of the methods fails,
+ * however, the caught exception will be rethrown in a wrapped
+ * {@link RuntimeException}, and the remaining methods will <strong>not</strong>
+ * be given a chance to execute.
+ * @param testContext the current test context
+ */
+ protected void runBeforeTransactionMethods(TestContext testContext) throws Exception {
+ try {
+ List<Method> methods = getAnnotatedMethods(testContext.getTestClass(), BeforeTransaction.class);
+ Collections.reverse(methods);
+ for (Method method : methods) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing @BeforeTransaction method [" + method + "] for test context " + testContext);
+ }
+ method.invoke(testContext.getTestInstance());
+ }
+ }
+ catch (InvocationTargetException ex) {
+ logger.error("Exception encountered while executing @BeforeTransaction methods for test context "
+ + testContext + ".", ex.getTargetException());
+ ReflectionUtils.rethrowException(ex.getTargetException());
+ }
+ }
+
+ /**
+ * Run all {@link AfterTransaction &#064;AfterTransaction methods} for the
+ * specified {@link TestContext test context}. If one of the methods fails,
+ * the caught exception will be logged as an error, and the remaining
+ * methods will be given a chance to execute. After all methods have
+ * executed, the first caught exception, if any, will be rethrown.
+ * @param testContext the current test context
+ */
+ protected void runAfterTransactionMethods(TestContext testContext) throws Exception {
+ Throwable afterTransactionException = null;
+
+ List<Method> methods = getAnnotatedMethods(testContext.getTestClass(), AfterTransaction.class);
+ for (Method method : methods) {
+ try {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing @AfterTransaction method [" + method + "] for test context " + testContext);
+ }
+ method.invoke(testContext.getTestInstance());
+ }
+ catch (InvocationTargetException ex) {
+ Throwable targetException = ex.getTargetException();
+ if (afterTransactionException == null) {
+ afterTransactionException = 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);
+ }
+ }
+
+ if (afterTransactionException != null) {
+ ReflectionUtils.rethrowException(afterTransactionException);
+ }
+ }
+
+ /**
+ * Start a new transaction for the supplied {@link TestContext test context}.
+ * <p>Only call this method if {@link #endTransaction} has been called or if no
+ * transaction has been previously started.
+ * @param testContext the current test context
+ * @throws TransactionException if starting the transaction fails
+ * @throws Exception if an error occurs while retrieving the transaction manager
+ */
+ private void startNewTransaction(TestContext testContext, TransactionContext txContext) throws Exception {
+ txContext.startTransaction();
+ ++this.transactionsStarted;
+ if (logger.isInfoEnabled()) {
+ logger.info("Began transaction (" + this.transactionsStarted + "): transaction manager ["
+ + txContext.transactionManager + "]; rollback [" + isRollback(testContext) + "]");
+ }
+ }
+
+ /**
+ * Immediately force a <em>commit</em> or <em>rollback</em> of the
+ * transaction for the supplied {@link TestContext test context}, according
+ * to the commit and rollback flags.
+ * @param testContext the current test context
+ * @throws Exception if an error occurs while retrieving the transaction manager
+ */
+ private void endTransaction(TestContext testContext, TransactionContext txContext) throws Exception {
+ boolean rollback = isRollback(testContext);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Ending transaction for test context " + testContext + "; transaction manager ["
+ + txContext.transactionStatus + "]; rollback [" + rollback + "]");
+ }
+ txContext.endTransaction(rollback);
+ if (logger.isInfoEnabled()) {
+ logger.info((rollback ? "Rolled back" : "Committed")
+ + " transaction after test execution for test context " + testContext);
+ }
+ }
+
+ /**
+ * Get the {@link PlatformTransactionManager transaction manager} to use
+ * for the supplied {@link TestContext test context} and {@code qualifier}.
+ * <p>Delegates to {@link #getTransactionManager(TestContext)} if the
+ * supplied {@code qualifier} is {@code null} or empty.
+ * @param testContext the test context for which the transaction manager
+ * should be retrieved
+ * @param qualifier the qualifier for selecting between multiple bean matches;
+ * may be {@code null} or empty
+ * @return the transaction manager to use, or {@code null} if not found
+ * @throws BeansException if an error occurs while retrieving the transaction manager
+ * @see #getTransactionManager(TestContext)
+ */
+ protected final PlatformTransactionManager getTransactionManager(TestContext testContext, String qualifier) {
+ // 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).
+ BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory();
+
+ return BeanFactoryAnnotationUtils.qualifiedBeanOfType(bf, PlatformTransactionManager.class, qualifier);
+ } catch (RuntimeException ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Caught exception while retrieving transaction manager for test context " + testContext
+ + " and qualifier [" + qualifier + "]", ex);
+ }
+ throw ex;
+ }
+ }
+
+ // else
+ return getTransactionManager(testContext);
+ }
+
+ /**
+ * Get the {@link PlatformTransactionManager transaction manager} to use
+ * for the supplied {@link TestContext test context}.
+ * @param testContext the test context for which the transaction manager
+ * should be retrieved
+ * @return the transaction manager to use, or {@code null} if not found
+ * @throws BeansException if an error occurs while retrieving the transaction manager
+ * @see #getTransactionManager(TestContext, String)
+ */
+ protected final PlatformTransactionManager getTransactionManager(TestContext testContext) {
+ BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory();
+ String tmName = retrieveConfigurationAttributes(testContext).getTransactionManagerName();
+
+ try {
+ // look up by type and explicit name from @TransactionConfiguration
+ if (StringUtils.hasText(tmName) && !DEFAULT_TRANSACTION_MANAGER_NAME.equals(tmName)) {
+ return bf.getBean(tmName, PlatformTransactionManager.class);
+ }
+
+ if (bf instanceof ListableBeanFactory) {
+ ListableBeanFactory lbf = (ListableBeanFactory) bf;
+
+ // look up single bean by type
+ Map<String, PlatformTransactionManager> txMgrs = BeanFactoryUtils.beansOfTypeIncludingAncestors(
+ lbf, PlatformTransactionManager.class);
+ if (txMgrs.size() == 1) {
+ return txMgrs.values().iterator().next();
+ }
+
+ // look up single TransactionManagementConfigurer
+ Map<String, TransactionManagementConfigurer> configurers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
+ lbf, TransactionManagementConfigurer.class);
+ if (configurers.size() > 1) {
+ throw new IllegalStateException(
+ "Only one TransactionManagementConfigurer may exist in the ApplicationContext");
+ }
+ if (configurers.size() == 1) {
+ return configurers.values().iterator().next().annotationDrivenTransactionManager();
+ }
+ }
+
+ // look up by type and default name from @TransactionConfiguration
+ return bf.getBean(DEFAULT_TRANSACTION_MANAGER_NAME, PlatformTransactionManager.class);
+
+ } catch (BeansException ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Caught exception while retrieving transaction manager for test context " + testContext, ex);
+ }
+ throw ex;
+ }
+ }
+
+ /**
+ * Determine whether or not to rollback transactions by default for the
+ * supplied {@link TestContext test context}.
+ * @param testContext the test context for which the default rollback flag
+ * should be retrieved
+ * @return the <em>default rollback</em> flag for the supplied test context
+ * @throws Exception if an error occurs while determining the default rollback flag
+ */
+ protected final boolean isDefaultRollback(TestContext testContext) throws Exception {
+ return retrieveConfigurationAttributes(testContext).isDefaultRollback();
+ }
+
+ /**
+ * Determine whether or not to rollback transactions for the supplied
+ * {@link TestContext test context} by taking into consideration the
+ * {@link #isDefaultRollback(TestContext) default rollback} flag and a
+ * possible method-level override via the {@link Rollback} annotation.
+ * @param testContext the test context for which the rollback flag
+ * should be retrieved
+ * @return the <em>rollback</em> flag for the supplied test context
+ * @throws Exception if an error occurs while determining the rollback flag
+ */
+ protected final boolean isRollback(TestContext testContext) throws Exception {
+ boolean rollback = isDefaultRollback(testContext);
+ Rollback rollbackAnnotation = testContext.getTestMethod().getAnnotation(Rollback.class);
+ if (rollbackAnnotation != null) {
+ boolean rollbackOverride = rollbackAnnotation.value();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Method-level @Rollback(" + rollbackOverride + ") overrides default rollback [" + rollback
+ + "] for test context " + testContext);
+ }
+ rollback = rollbackOverride;
+ }
+ else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("No method-level @Rollback override: using default rollback [" + rollback
+ + "] for test context " + 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.
+ * <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.
+ */
+ private List<Class<?>> getSuperClasses(Class<?> clazz) {
+ ArrayList<Class<?>> results = new ArrayList<Class<?>>();
+ Class<?> current = clazz;
+ while (current != null) {
+ results.add(current);
+ current = current.getSuperclass();
+ }
+ return results;
+ }
+
+ /**
+ * Gets 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.
+ * @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
+ */
+ private List<Method> getAnnotatedMethods(Class<?> clazz, Class<? extends Annotation> annotationType) {
+ List<Method> results = new ArrayList<Method>();
+ for (Class<?> eachClass : getSuperClasses(clazz)) {
+ Method[] methods = eachClass.getDeclaredMethods();
+ for (Method eachMethod : methods) {
+ Annotation annotation = eachMethod.getAnnotation(annotationType);
+ if (annotation != null && !isShadowed(eachMethod, results)) {
+ results.add(eachMethod);
+ }
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Determines if the supplied {@link Method method} is <em>shadowed</em>
+ * by a method in 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;
+ }
+
+ /**
+ * Determines if the supplied {@link Method current method} is
+ * <em>shadowed</em> by a {@link 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;
+ }
+
+ /**
+ * Retrieves the {@link TransactionConfigurationAttributes} for the
+ * specified {@link Class class} which may optionally declare or inherit
+ * {@link TransactionConfiguration &#064;TransactionConfiguration}. If
+ * {@code @TransactionConfiguration} is not present for the supplied
+ * class, the <em>default values</em> for attributes defined in
+ * {@code @TransactionConfiguration} will be used instead.
+ * @param testContext the test context for which the configuration
+ * attributes should be retrieved
+ * @return a new TransactionConfigurationAttributes instance
+ */
+ private TransactionConfigurationAttributes retrieveConfigurationAttributes(TestContext testContext) {
+ if (this.configurationAttributes == null) {
+ Class<?> clazz = testContext.getTestClass();
+ TransactionConfiguration config = clazz.getAnnotation(TransactionConfiguration.class);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Retrieved @TransactionConfiguration [" + config + "] for test class [" + clazz + "]");
+ }
+
+ String transactionManagerName;
+ boolean defaultRollback;
+ if (config != null) {
+ transactionManagerName = config.transactionManager();
+ defaultRollback = config.defaultRollback();
+ }
+ else {
+ transactionManagerName = DEFAULT_TRANSACTION_MANAGER_NAME;
+ defaultRollback = DEFAULT_DEFAULT_ROLLBACK;
+ }
+
+ TransactionConfigurationAttributes configAttributes = new TransactionConfigurationAttributes(
+ transactionManagerName, defaultRollback);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Retrieved TransactionConfigurationAttributes " + configAttributes + " for class ["
+ + clazz + "]");
+ }
+ this.configurationAttributes = configAttributes;
+ }
+ return this.configurationAttributes;
+ }
+
+
+ /**
+ * Internal context holder for a specific test method.
+ */
+ private static class TransactionContext {
+
+ private final PlatformTransactionManager transactionManager;
+
+ private final TransactionDefinition transactionDefinition;
+
+ private TransactionStatus transactionStatus;
+
+
+ public TransactionContext(PlatformTransactionManager transactionManager,
+ TransactionDefinition transactionDefinition) {
+ this.transactionManager = transactionManager;
+ this.transactionDefinition = transactionDefinition;
+ }
+
+ public void startTransaction() {
+ this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition);
+ }
+
+ public void endTransaction(boolean rollback) {
+ if (rollback) {
+ this.transactionManager.rollback(this.transactionStatus);
+ }
+ else {
+ this.transactionManager.commit(this.transactionStatus);
+ }
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/package-info.java b/spring-test/src/main/java/org/springframework/test/context/transaction/package-info.java
new file mode 100644
index 00000000..3c685db1
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/transaction/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * <p>Transactional support classes for the <em>Spring TestContext
+ * Framework</em>.</p>
+ */
+
+package org.springframework.test.context.transaction;
+
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
new file mode 100644
index 00000000..75c363dd
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.web;
+
+import javax.servlet.ServletContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigUtils;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.FileSystemResourceLoader;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.test.context.support.AbstractContextLoader;
+import org.springframework.util.Assert;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.GenericWebApplicationContext;
+
+/**
+ * Abstract, generic extension of {@link AbstractContextLoader} that loads a
+ * {@link GenericWebApplicationContext}.
+ *
+ * <p>If instances of concrete subclasses are invoked via the
+ * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
+ * SPI, the context will be loaded from the {@link MergedContextConfiguration}
+ * provided to {@link #loadContext(MergedContextConfiguration)}. In such cases, a
+ * {@code SmartContextLoader} will decide whether to load the context from
+ * <em>locations</em> or <em>annotated classes</em>. Note that {@code
+ * AbstractGenericWebContextLoader} does not support the {@code
+ * loadContext(String... locations)} method from the legacy
+ * {@link org.springframework.test.context.ContextLoader ContextLoader} SPI.
+ *
+ * <p>Concrete subclasses must provide an appropriate implementation of
+ * {@link #loadBeanDefinitions}.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ * @see #loadContext(MergedContextConfiguration)
+ * @see #loadContext(String...)
+ */
+public abstract class AbstractGenericWebContextLoader extends AbstractContextLoader {
+
+ private static final Log logger = LogFactory.getLog(AbstractGenericWebContextLoader.class);
+
+
+ // --- SmartContextLoader -----------------------------------------------
+
+ /**
+ * Load a Spring {@link WebApplicationContext} from the supplied
+ * {@link MergedContextConfiguration}.
+ *
+ * <p>Implementation details:
+ *
+ * <ul>
+ * <li>Creates a {@link GenericWebApplicationContext} instance.</li>
+ * <li>If the supplied {@code MergedContextConfiguration} references a
+ * {@linkplain MergedContextConfiguration#getParent() parent configuration},
+ * the corresponding {@link MergedContextConfiguration#getParentApplicationContext()
+ * ApplicationContext} will be retrieved and
+ * {@linkplain GenericWebApplicationContext#setParent(ApplicationContext) set as the parent}
+ * for the context created by this method.</li>
+ * <li>Delegates to {@link #configureWebResources} to create the
+ * {@link MockServletContext} and set it in the {@code WebApplicationContext}.</li>
+ * <li>Calls {@link #prepareContext} to allow for customizing the context
+ * before bean definitions are loaded.</li>
+ * <li>Calls {@link #customizeBeanFactory} to allow for customizing the
+ * context's {@code DefaultListableBeanFactory}.</li>
+ * <li>Delegates to {@link #loadBeanDefinitions} to populate the context
+ * from the locations or classes in the supplied {@code MergedContextConfiguration}.</li>
+ * <li>Delegates to {@link AnnotationConfigUtils} for
+ * {@linkplain AnnotationConfigUtils#registerAnnotationConfigProcessors registering}
+ * annotation configuration processors.</li>
+ * <li>Calls {@link #customizeContext} 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>
+ *
+ * @return a new web application context
+ * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration)
+ * @see GenericWebApplicationContext
+ */
+ public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
+
+ if (!(mergedConfig instanceof WebMergedContextConfiguration)) {
+ throw new IllegalArgumentException(String.format(
+ "Cannot load WebApplicationContext from non-web merged context configuration %s. "
+ + "Consider annotating your test class with @WebAppConfiguration.", mergedConfig));
+ }
+ WebMergedContextConfiguration webMergedConfig = (WebMergedContextConfiguration) mergedConfig;
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Loading WebApplicationContext for merged context configuration %s.",
+ webMergedConfig));
+ }
+
+ GenericWebApplicationContext context = new GenericWebApplicationContext();
+
+ ApplicationContext parent = mergedConfig.getParentApplicationContext();
+ if (parent != null) {
+ context.setParent(parent);
+ }
+ configureWebResources(context, webMergedConfig);
+ prepareContext(context, webMergedConfig);
+ customizeBeanFactory(context.getDefaultListableBeanFactory(), webMergedConfig);
+ loadBeanDefinitions(context, webMergedConfig);
+ AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
+ customizeContext(context, webMergedConfig);
+ context.refresh();
+ context.registerShutdownHook();
+ return context;
+ }
+
+ /**
+ * Configures web resources for the supplied web application context (WAC).
+ *
+ * <h4>Implementation Details</h4>
+ *
+ * <p>If the supplied WAC has no parent or its parent is not a WAC, the
+ * supplied WAC will be configured as the Root WAC (see "<em>Root WAC
+ * Configuration</em>" below).
+ *
+ * <p>Otherwise the context hierarchy of the supplied WAC will be traversed
+ * to find the top-most WAC (i.e., the root); and the {@link ServletContext}
+ * of the Root WAC will be set as the {@code ServletContext} for the supplied
+ * WAC.
+ *
+ * <h4>Root WAC Configuration</h4>
+ *
+ * <ul>
+ * <li>The resource base path is retrieved from the supplied
+ * {@code WebMergedContextConfiguration}.</li>
+ * <li>A {@link ResourceLoader} is instantiated for the {@link MockServletContext}:
+ * if the resource base path is prefixed with "{@code classpath:}", a
+ * {@link DefaultResourceLoader} will be used; otherwise, a
+ * {@link FileSystemResourceLoader} will be used.</li>
+ * <li>A {@code MockServletContext} will be created using the resource base
+ * path and resource loader.</li>
+ * <li>The supplied {@link GenericWebApplicationContext} is then stored in
+ * the {@code MockServletContext} under the
+ * {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} key.</li>
+ * <li>Finally, the {@code MockServletContext} is set in the
+ * {@code WebApplicationContext}.</li>
+ *
+ * @param context the web application context for which to configure the web
+ * resources
+ * @param webMergedConfig the merged context configuration to use to load the
+ * web application context
+ */
+ protected void configureWebResources(GenericWebApplicationContext context,
+ WebMergedContextConfiguration webMergedConfig) {
+
+ ApplicationContext parent = context.getParent();
+
+ // if the WAC has no parent or the parent is not a WAC, set the WAC as
+ // the Root WAC:
+ if (parent == null || (!(parent instanceof WebApplicationContext))) {
+ String resourceBasePath = webMergedConfig.getResourceBasePath();
+ ResourceLoader resourceLoader = resourceBasePath.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX) ? new DefaultResourceLoader()
+ : new FileSystemResourceLoader();
+
+ ServletContext servletContext = new MockServletContext(resourceBasePath, resourceLoader);
+ servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
+ context.setServletContext(servletContext);
+ }
+ else {
+ ServletContext servletContext = null;
+
+ // find the Root WAC
+ while (parent != null) {
+ if (parent instanceof WebApplicationContext && !(parent.getParent() instanceof WebApplicationContext)) {
+ servletContext = ((WebApplicationContext) parent).getServletContext();
+ break;
+ }
+ parent = parent.getParent();
+ }
+ Assert.state(servletContext != null, "Failed to find Root WebApplicationContext in the context hierarchy");
+ context.setServletContext(servletContext);
+ }
+ }
+
+ /**
+ * Customize the internal bean factory of the {@code WebApplicationContext}
+ * created by this context loader.
+ *
+ * <p>The default implementation is empty but can be overridden in subclasses
+ * to customize {@code DefaultListableBeanFactory}'s standard settings.
+ *
+ * @param beanFactory the bean factory created by this context loader
+ * @param webMergedConfig the merged context configuration to use to load the
+ * web application context
+ * @see #loadContext(MergedContextConfiguration)
+ * @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
+ * @see DefaultListableBeanFactory#setAllowEagerClassLoading
+ * @see DefaultListableBeanFactory#setAllowCircularReferences
+ * @see DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping
+ */
+ protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory,
+ WebMergedContextConfiguration webMergedConfig) {
+ }
+
+ /**
+ * Load bean definitions into the supplied {@link GenericWebApplicationContext context}
+ * from the locations or classes in the supplied {@code WebMergedContextConfiguration}.
+ *
+ * <p>Concrete subclasses must provide an appropriate implementation.
+ *
+ * @param context the context into which the bean definitions should be loaded
+ * @param webMergedConfig the merged context configuration to use to load the
+ * web application context
+ * @see #loadContext(MergedContextConfiguration)
+ */
+ protected abstract void loadBeanDefinitions(GenericWebApplicationContext context,
+ WebMergedContextConfiguration webMergedConfig);
+
+ /**
+ * Customize the {@link GenericWebApplicationContext} created by this context
+ * 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.
+ *
+ * @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)
+ */
+ protected void customizeContext(GenericWebApplicationContext context, WebMergedContextConfiguration webMergedConfig) {
+ }
+
+ // --- ContextLoader -------------------------------------------------------
+
+ /**
+ * {@code AbstractGenericWebContextLoader} should be used as a
+ * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
+ * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
+ * Consequently, this method is not supported.
+ *
+ * @see org.springframework.test.context.ContextLoader#loadContext(java.lang.String[])
+ * @throws UnsupportedOperationException
+ */
+ public final ApplicationContext loadContext(String... locations) throws Exception {
+ throw new UnsupportedOperationException(
+ "AbstractGenericWebContextLoader does not support the loadContext(String... locations) method");
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/AnnotationConfigWebContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/web/AnnotationConfigWebContextLoader.java
new file mode 100644
index 00000000..05acb463
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/AnnotationConfigWebContextLoader.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2002-2012 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.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.support.AnnotationConfigContextLoaderUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.context.support.GenericWebApplicationContext;
+
+/**
+ * Concrete implementation of {@link AbstractGenericWebContextLoader} that loads
+ * bean definitions from annotated classes.
+ *
+ * <p>See the Javadoc for
+ * {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration}
+ * for a definition of <em>annotated class</em>.
+ *
+ * <p>Note: {@code AnnotationConfigWebContextLoader} supports <em>annotated classes</em>
+ * rather than the String-based resource locations defined by the legacy
+ * {@link org.springframework.test.context.ContextLoader ContextLoader} API. Thus,
+ * although {@code AnnotationConfigWebContextLoader} extends
+ * {@code AbstractGenericWebContextLoader}, {@code AnnotationConfigWebContextLoader}
+ * does <em>not</em> support any String-based methods defined by
+ * {@link org.springframework.test.context.support.AbstractContextLoader
+ * AbstractContextLoader} or {@code AbstractGenericWebContextLoader}.
+ * Consequently, {@code AnnotationConfigWebContextLoader} should chiefly be
+ * considered a {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
+ * rather than a {@link org.springframework.test.context.ContextLoader ContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ * @see #processContextConfiguration(ContextConfigurationAttributes)
+ * @see #detectDefaultConfigurationClasses(Class)
+ * @see #loadBeanDefinitions(GenericWebApplicationContext, WebMergedContextConfiguration)
+ */
+public class AnnotationConfigWebContextLoader extends AbstractGenericWebContextLoader {
+
+ private static final Log logger = LogFactory.getLog(AnnotationConfigWebContextLoader.class);
+
+
+ // --- SmartContextLoader -----------------------------------------------
+
+ /**
+ * Process <em>annotated classes</em> in the supplied {@link ContextConfigurationAttributes}.
+ *
+ * <p>If the <em>annotated classes</em> are {@code null} or empty and
+ * {@link #isGenerateDefaultLocations()} returns {@code true}, this
+ * {@code SmartContextLoader} will attempt to {@linkplain
+ * #detectDefaultConfigurationClasses detect default configuration classes}.
+ * If defaults are detected they will be
+ * {@linkplain ContextConfigurationAttributes#setClasses(Class[]) set} in the
+ * supplied configuration attributes. Otherwise, properties in the supplied
+ * configuration attributes will not be modified.
+ *
+ * @param configAttributes the context configuration attributes to process
+ * @see org.springframework.test.context.SmartContextLoader#processContextConfiguration(ContextConfigurationAttributes)
+ * @see #isGenerateDefaultLocations()
+ * @see #detectDefaultConfigurationClasses(Class)
+ */
+ public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
+ if (ObjectUtils.isEmpty(configAttributes.getClasses()) && isGenerateDefaultLocations()) {
+ Class<?>[] defaultConfigClasses = detectDefaultConfigurationClasses(configAttributes.getDeclaringClass());
+ configAttributes.setClasses(defaultConfigClasses);
+ }
+ }
+
+ /**
+ * Detect the default configuration classes for the supplied test class.
+ *
+ * <p>The default implementation simply delegates to
+ * {@link AnnotationConfigContextLoaderUtils#detectDefaultConfigurationClasses(Class)}.
+ *
+ * @param declaringClass the test class that declared {@code @ContextConfiguration}
+ * @return an array of default configuration classes, potentially empty but
+ * never {@code null}
+ * @see AnnotationConfigContextLoaderUtils
+ */
+ protected Class<?>[] detectDefaultConfigurationClasses(Class<?> declaringClass) {
+ return AnnotationConfigContextLoaderUtils.detectDefaultConfigurationClasses(declaringClass);
+ }
+
+ // --- AbstractContextLoader -----------------------------------------------
+
+ /**
+ * {@code AnnotationConfigWebContextLoader} should be used as a
+ * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
+ * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
+ * Consequently, this method is not supported.
+ *
+ * @see org.springframework.test.context.support.AbstractContextLoader#modifyLocations
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ protected String[] modifyLocations(Class<?> clazz, String... locations) {
+ throw new UnsupportedOperationException(
+ "AnnotationConfigWebContextLoader does not support the modifyLocations(Class, String...) method");
+ }
+
+ /**
+ * {@code AnnotationConfigWebContextLoader} should be used as a
+ * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
+ * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
+ * Consequently, this method is not supported.
+ *
+ * @see org.springframework.test.context.support.AbstractContextLoader#generateDefaultLocations
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ protected String[] generateDefaultLocations(Class<?> clazz) {
+ throw new UnsupportedOperationException(
+ "AnnotationConfigWebContextLoader does not support the generateDefaultLocations(Class) method");
+ }
+
+ /**
+ * {@code AnnotationConfigWebContextLoader} should be used as a
+ * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
+ * not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
+ * Consequently, this method is not supported.
+ *
+ * @see org.springframework.test.context.support.AbstractContextLoader#getResourceSuffix
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ protected String getResourceSuffix() {
+ throw new UnsupportedOperationException(
+ "AnnotationConfigWebContextLoader does not support the getResourceSuffix() method");
+ }
+
+ // --- AbstractGenericWebContextLoader -------------------------------------
+
+ /**
+ * Register classes in the supplied {@linkplain GenericWebApplicationContext context}
+ * from the classes in the supplied {@link WebMergedContextConfiguration}.
+ *
+ * <p>Each class must represent an <em>annotated class</em>. An
+ * {@link AnnotatedBeanDefinitionReader} is used to register the appropriate
+ * bean definitions.
+ *
+ * @param context the context in which the annotated classes should be registered
+ * @param webMergedConfig the merged configuration from which the classes should be retrieved
+ *
+ * @see AbstractGenericWebContextLoader#loadBeanDefinitions
+ */
+ @Override
+ protected void loadBeanDefinitions(GenericWebApplicationContext context,
+ WebMergedContextConfiguration webMergedConfig) {
+ Class<?>[] annotatedClasses = webMergedConfig.getClasses();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Registering annotated classes: " + ObjectUtils.nullSafeToString(annotatedClasses));
+ }
+ new AnnotatedBeanDefinitionReader(context).register(annotatedClasses);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/GenericXmlWebContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/web/GenericXmlWebContextLoader.java
new file mode 100644
index 00000000..0bf295e0
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/GenericXmlWebContextLoader.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2012 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.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.web.context.support.GenericWebApplicationContext;
+
+/**
+ * Concrete implementation of {@link AbstractGenericWebContextLoader} that loads
+ * bean definitions from XML resources.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+public class GenericXmlWebContextLoader extends AbstractGenericWebContextLoader {
+
+ /**
+ * Loads bean definitions using an {@link XmlBeanDefinitionReader}.
+ * @see AbstractGenericWebContextLoader#loadBeanDefinitions
+ */
+ @Override
+ protected void loadBeanDefinitions(GenericWebApplicationContext context,
+ WebMergedContextConfiguration webMergedConfig) {
+ new XmlBeanDefinitionReader(context).loadBeanDefinitions(webMergedConfig.getLocations());
+ }
+
+ /**
+ * Returns &quot;{@code -context.xml}&quot;.
+ */
+ protected String getResourceSuffix() {
+ return "-context.xml";
+ }
+
+}
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
new file mode 100644
index 00000000..5166f8ce
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java
@@ -0,0 +1,194 @@
+/*
+ * 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.context.web;
+
+import javax.servlet.ServletContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+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.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletContext;
+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.RequestContextHolder;
+import org.springframework.web.context.request.ServletWebRequest;
+
+/**
+ * {@code TestExecutionListener} which provides mock Servlet API support to
+ * {@link WebApplicationContext WebApplicationContexts} loaded by the <em>Spring
+ * TestContext Framework</em>.
+ *
+ * <p>Specifically, {@code ServletTestExecutionListener} sets up thread-local
+ * state via Spring Web's {@link RequestContextHolder} during {@linkplain
+ * #prepareTestInstance(TestContext) test instance preparation} and {@linkplain
+ * #beforeTestMethod(TestContext) before each test method} and creates a {@link
+ * MockHttpServletRequest}, {@link MockHttpServletResponse}, and
+ * {@link ServletWebRequest} based on the {@link MockServletContext} present in
+ * the {@code WebApplicationContext}. This listener also ensures that the
+ * {@code MockHttpServletResponse} and {@code ServletWebRequest} can be injected
+ * into the test instance, and once the test is complete this listener {@linkplain
+ * #afterTestMethod(TestContext) cleans up} thread-local state.
+ *
+ * <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.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+public class ServletTestExecutionListener extends AbstractTestExecutionListener {
+
+ /**
+ * Attribute name for a {@link TestContext} attribute which indicates
+ * 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");
+
+ /**
+ * 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");
+
+ private static final Log logger = LogFactory.getLog(ServletTestExecutionListener.class);
+
+
+ /**
+ * Sets up thread-local state during the <em>test instance preparation</em>
+ * 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)
+ */
+ @SuppressWarnings("javadoc")
+ public void prepareTestInstance(TestContext testContext) throws Exception {
+ setUpRequestContextIfNecessary(testContext);
+ }
+
+ /**
+ * Sets up thread-local state before each test method via Spring Web's
+ * {@link RequestContextHolder}, but only if the
+ * {@linkplain TestContext#getTestClass() test class} is annotated with
+ * {@link WebAppConfiguration @WebAppConfiguration}.
+ *
+ * @see TestExecutionListener#beforeTestMethod(TestContext)
+ * @see #setUpRequestContextIfNecessary(TestContext)
+ */
+ @SuppressWarnings("javadoc")
+ public void beforeTestMethod(TestContext testContext) throws Exception {
+ setUpRequestContextIfNecessary(testContext);
+ }
+
+ /**
+ * If the {@link #RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE} in the supplied
+ * {@code TestContext} has a value of {@link Boolean#TRUE}, this method will
+ * (1) clean up thread-local state after each test method by {@linkplain
+ * RequestContextHolder#resetRequestAttributes() resetting} Spring Web's
+ * {@code RequestContextHolder} and (2) ensure that new mocks are injected
+ * 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)
+ */
+ public void afterTestMethod(TestContext testContext) throws Exception {
+ if (Boolean.TRUE.equals(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE))) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Resetting RequestContextHolder for test context %s.", testContext));
+ }
+ RequestContextHolder.resetRequestAttributes();
+ testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE,
+ Boolean.TRUE);
+ }
+ testContext.removeAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE);
+ testContext.removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE);
+ }
+
+ private boolean notAnnotatedWithWebAppConfiguration(TestContext testContext) {
+ return AnnotationUtils.findAnnotation(testContext.getTestClass(), WebAppConfiguration.class) == null;
+ }
+
+ private boolean alreadyPopulatedRequestContextHolder(TestContext testContext) {
+ return Boolean.TRUE.equals(testContext.getAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE));
+ }
+
+ private void setUpRequestContextIfNecessary(TestContext testContext) {
+ if (notAnnotatedWithWebAppConfiguration(testContext) || alreadyPopulatedRequestContextHolder(testContext)) {
+ return;
+ }
+
+ ApplicationContext context = testContext.getApplicationContext();
+
+ 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 (logger.isDebugEnabled()) {
+ logger.debug(String.format(
+ "Setting up MockHttpServletRequest, MockHttpServletResponse, ServletWebRequest, and RequestContextHolder for test context %s.",
+ testContext));
+ }
+
+ MockServletContext mockServletContext = (MockServletContext) servletContext;
+ MockHttpServletRequest request = new MockHttpServletRequest(mockServletContext);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ ServletWebRequest servletWebRequest = new ServletWebRequest(request, response);
+
+ RequestContextHolder.setRequestAttributes(servletWebRequest);
+ testContext.setAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
+ testContext.setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
+
+ if (wac instanceof ConfigurableApplicationContext) {
+ @SuppressWarnings("resource")
+ ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) wac;
+ ConfigurableListableBeanFactory bf = configurableApplicationContext.getBeanFactory();
+ bf.registerResolvableDependency(MockHttpServletResponse.class, response);
+ bf.registerResolvableDependency(ServletWebRequest.class, servletWebRequest);
+ }
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..7f24c266
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/WebAppConfiguration.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2012 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 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;
+
+/**
+ * {@code @WebAppConfiguration} is a class-level annotation that is used to
+ * declare that the {@code ApplicationContext} loaded for an integration test
+ * should be a {@link org.springframework.web.context.WebApplicationContext
+ * WebApplicationContext}.
+ *
+ * <p>The mere presence of {@code @WebAppConfiguration} on a test class ensures
+ * that a {@code WebApplicationContext} will be loaded for the test using a default
+ * for the path to the root of the web application. To override the default,
+ * specify an explicit resource path via the {@link #value} attribute.
+ *
+ * <p>Note that {@code @WebAppConfiguration} must be used in conjunction with
+ * {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration},
+ * either within a single test class or within a test class hierarchy.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ * @see org.springframework.web.context.WebApplicationContext
+ * @see org.springframework.test.context.ContextConfiguration
+ * @see ServletTestExecutionListener
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface WebAppConfiguration {
+
+ /**
+ * The resource path to the root directory of the web application.
+ *
+ * <p>A path that does not include a Spring resource prefix (e.g., {@code classpath:},
+ * {@code file:}, etc.) will be interpreted as a file system resource, and a
+ * path should not end with a slash.
+ *
+ * <p>Defaults to {@code "src/main/webapp"} as a file system resource. Note
+ * that this is the standard directory for the root of a web application in
+ * a project that follows the standard Maven project layout for a WAR.
+ */
+ String value() default "src/main/webapp";
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebDelegatingSmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/web/WebDelegatingSmartContextLoader.java
new file mode 100644
index 00000000..fa561c00
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/WebDelegatingSmartContextLoader.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2012 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.springframework.test.context.SmartContextLoader;
+import org.springframework.test.context.support.AbstractDelegatingSmartContextLoader;
+
+/**
+ * {@code WebDelegatingSmartContextLoader} is a concrete implementation of
+ * {@link AbstractDelegatingSmartContextLoader} that delegates to a
+ * {@link GenericXmlWebContextLoader} and an {@link AnnotationConfigWebContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ * @see SmartContextLoader
+ * @see AbstractDelegatingSmartContextLoader
+ * @see GenericXmlWebContextLoader
+ * @see AnnotationConfigWebContextLoader
+ */
+public class WebDelegatingSmartContextLoader extends AbstractDelegatingSmartContextLoader {
+
+ private final SmartContextLoader xmlLoader = new GenericXmlWebContextLoader();
+ private final SmartContextLoader annotationConfigLoader = new AnnotationConfigWebContextLoader();
+
+
+ protected SmartContextLoader getXmlLoader() {
+ return this.xmlLoader;
+ }
+
+ protected SmartContextLoader getAnnotationConfigLoader() {
+ return this.annotationConfigLoader;
+ }
+
+}
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
new file mode 100644
index 00000000..58e8b386
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.web;
+
+import java.util.Set;
+
+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.ContextLoader;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@code WebMergedContextConfiguration} encapsulates the <em>merged</em>
+ * context configuration declared on a test class and all of its superclasses
+ * via {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration},
+ * {@link WebAppConfiguration @WebAppConfiguration}, and
+ * {@link org.springframework.test.context.ActiveProfiles @ActiveProfiles}.
+ *
+ * <p>{@code WebMergedContextConfiguration} extends the contract of
+ * {@link MergedContextConfiguration} by adding support for the {@link
+ * #getResourceBasePath() resource base path} configured via {@code @WebAppConfiguration}.
+ * This allows the {@link org.springframework.test.context.TestContext TestContext}
+ * to properly cache the corresponding {@link
+ * org.springframework.web.context.WebApplicationContext WebApplicationContext}
+ * that was loaded using properties of this {@code WebMergedContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ * @see WebAppConfiguration
+ * @see MergedContextConfiguration
+ * @see org.springframework.test.context.ContextConfiguration
+ * @see org.springframework.test.context.ActiveProfiles
+ * @see org.springframework.test.context.ContextConfigurationAttributes
+ * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration)
+ */
+public class WebMergedContextConfiguration extends MergedContextConfiguration {
+
+ private static final long serialVersionUID = 7323361588604247458L;
+
+ private final String resourceBasePath;
+
+
+ /**
+ * Create a new {@code WebMergedContextConfiguration} instance for the
+ * supplied test class, resource locations, annotated classes, context
+ * initializers, active profiles, resource base path, and {@code ContextLoader}.
+ *
+ * <p>If a {@code null} value is supplied for {@code locations},
+ * {@code classes}, or {@code activeProfiles} an empty array will
+ * be stored instead. If a {@code null} value is supplied for the
+ * {@code contextInitializerClasses} 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 resource locations
+ * @param classes the merged annotated classes
+ * @param contextInitializerClasses the merged context initializer classes
+ * @param activeProfiles the merged active bean definition profiles
+ * @param resourceBasePath the resource path to the root directory of the web application
+ * @param contextLoader the resolved {@code ContextLoader}
+ * @see #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)
+ * @deprecated as of Spring 3.2.2, use
+ * {@link #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)} instead.
+ */
+ @Deprecated
+ public WebMergedContextConfiguration(
+ Class<?> testClass,
+ String[] locations,
+ Class<?>[] classes,
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
+ String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader) {
+
+ this(testClass, locations, classes, contextInitializerClasses, activeProfiles, resourceBasePath, contextLoader,
+ null, null);
+ }
+
+ /**
+ * Create a new {@code WebMergedContextConfiguration} instance for the
+ * supplied test class, resource locations, annotated classes, context
+ * initializers, active profiles, resource base path, and {@code ContextLoader}.
+ *
+ * <p>If a {@code null} value is supplied for {@code locations},
+ * {@code classes}, or {@code activeProfiles} an empty array will
+ * be stored instead. If a {@code null} value is supplied for the
+ * {@code contextInitializerClasses} 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 resource locations
+ * @param classes the merged annotated classes
+ * @param contextInitializerClasses the merged context initializer classes
+ * @param activeProfiles the merged active bean definition profiles
+ * @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 3.2.2
+ */
+ public WebMergedContextConfiguration(
+ Class<?> testClass,
+ String[] locations,
+ Class<?>[] classes,
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
+ String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader,
+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
+
+ super(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader,
+ cacheAwareContextLoaderDelegate, parent);
+
+ 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}.
+ * @see WebAppConfiguration
+ */
+ public String getResourceBasePath() {
+ return this.resourceBasePath;
+ }
+
+ /**
+ * Generate a unique hash code for all properties of this
+ * {@code WebMergedContextConfiguration} excluding the
+ * {@linkplain #getTestClass() test class}.
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + resourceBasePath.hashCode();
+ return result;
+ }
+
+ /**
+ * Determine if the supplied object is equal to this {@code WebMergedContextConfiguration}
+ * instance by comparing both object's {@linkplain #getLocations() locations},
+ * {@linkplain #getClasses() annotated classes},
+ * {@linkplain #getContextInitializerClasses() context initializer classes},
+ * {@linkplain #getActiveProfiles() active profiles},
+ * {@linkplain #getResourceBasePath() resource base path},
+ * {@linkplain #getParent() parents}, and the fully qualified names of their
+ * {@link #getContextLoader() ContextLoaders}.
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof WebMergedContextConfiguration)) {
+ return false;
+ }
+
+ final WebMergedContextConfiguration that = (WebMergedContextConfiguration) obj;
+
+ return super.equals(that) && this.getResourceBasePath().equals(that.getResourceBasePath());
+ }
+
+ /**
+ * Provide a String representation of the {@linkplain #getTestClass() test class},
+ * {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes},
+ * {@linkplain #getContextInitializerClasses() context initializer classes},
+ * {@linkplain #getActiveProfiles() active profiles},
+ * {@linkplain #getResourceBasePath() resource base path}, the name of the
+ * {@link #getContextLoader() ContextLoader}, and the
+ * {@linkplain #getParent() parent configuration}.
+ */
+ @Override
+ public String toString() {
+ return new ToStringCreator(this)//
+ .append("testClass", getTestClass())//
+ .append("locations", ObjectUtils.nullSafeToString(getLocations()))//
+ .append("classes", ObjectUtils.nullSafeToString(getClasses()))//
+ .append("contextInitializerClasses", ObjectUtils.nullSafeToString(getContextInitializerClasses()))//
+ .append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles()))//
+ .append("resourceBasePath", getResourceBasePath())//
+ .append("contextLoader", nullSafeToString(getContextLoader()))//
+ .append("parent", getParent())//
+ .toString();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/package-info.java b/spring-test/src/main/java/org/springframework/test/context/web/package-info.java
new file mode 100644
index 00000000..ebf00203
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/web/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Web support classes for the <em>Spring TestContext Framework</em>.
+ */
+
+package org.springframework.test.context.web;
+
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
new file mode 100644
index 00000000..383b20df
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/jdbc/JdbcTestUtils.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.jdbc;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.SqlParameterValue;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@code JdbcTestUtils} is a collection of JDBC related utility functions
+ * intended to simplify standard database testing scenarios.
+ *
+ * <p>As of Spring 3.1.3, {@code JdbcTestUtils} supersedes {@link SimpleJdbcTestUtils}.
+ *
+ * @author Thomas Risberg
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @author Phillip Webb
+ * @since 2.5.4
+ */
+public class JdbcTestUtils {
+
+ private static final Log logger = LogFactory.getLog(JdbcTestUtils.class);
+
+ private static final String DEFAULT_COMMENT_PREFIX = "--";
+
+ private static final char DEFAULT_STATEMENT_SEPARATOR = ';';
+
+
+ /**
+ * Count the rows in the given table.
+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
+ * @param tableName name of the table to count rows in
+ * @return the number of rows in the table
+ */
+ public static int countRowsInTable(JdbcTemplate jdbcTemplate, String tableName) {
+ return jdbcTemplate.queryForObject("SELECT COUNT(0) FROM " + tableName, Integer.class);
+ }
+
+ /**
+ * Count the rows in the given table, using the provided {@code WHERE} clause.
+ * <p>If the provided {@code WHERE} clause contains text, it will be prefixed
+ * with {@code " WHERE "} and then appended to the generated {@code SELECT}
+ * statement. For example, if the provided table name is {@code "person"} and
+ * the provided where clause is {@code "name = 'Bob' and age > 25"}, the
+ * resulting SQL statement to execute will be
+ * {@code "SELECT COUNT(0) FROM person WHERE name = 'Bob' and age > 25"}.
+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
+ * @param tableName the name of the table to count rows in
+ * @param whereClause the {@code WHERE} clause to append to the query
+ * @return the number of rows in the table that match the provided
+ * {@code WHERE} clause
+ */
+ public static int countRowsInTableWhere(JdbcTemplate jdbcTemplate, String tableName, String whereClause) {
+ String sql = "SELECT COUNT(0) FROM " + tableName;
+ if (StringUtils.hasText(whereClause)) {
+ sql += " WHERE " + whereClause;
+ }
+ return jdbcTemplate.queryForObject(sql, Integer.class);
+ }
+
+ /**
+ * Delete all rows from the specified tables.
+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
+ * @param tableNames the names of the tables to delete from
+ * @return the total number of rows deleted from all specified tables
+ */
+ public static int deleteFromTables(JdbcTemplate jdbcTemplate, String... tableNames) {
+ int totalRowCount = 0;
+ for (String tableName : tableNames) {
+ int rowCount = jdbcTemplate.update("DELETE FROM " + tableName);
+ totalRowCount += rowCount;
+ if (logger.isInfoEnabled()) {
+ logger.info("Deleted " + rowCount + " rows from table " + tableName);
+ }
+ }
+ return totalRowCount;
+ }
+
+ /**
+ * Delete rows from the given table, using the provided {@code WHERE} clause.
+ * <p>If the provided {@code WHERE} clause contains text, it will be prefixed
+ * with {@code " WHERE "} and then appended to the generated {@code DELETE}
+ * statement. For example, if the provided table name is {@code "person"} and
+ * the provided where clause is {@code "name = 'Bob' and age > 25"}, the
+ * resulting SQL statement to execute will be
+ * {@code "DELETE FROM person WHERE name = 'Bob' and age > 25"}.
+ * <p>As an alternative to hard-coded values, the {@code "?"} placeholder can
+ * be used within the {@code WHERE} clause, binding to the given arguments.
+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
+ * @param tableName the name of the table to delete rows from
+ * @param whereClause the {@code WHERE} clause to append to the query
+ * @param args arguments to bind to the query (leaving it to the PreparedStatement
+ * to guess the corresponding SQL type); may also contain {@link SqlParameterValue}
+ * objects which indicate not only the argument value but also the SQL type and
+ * optionally the scale.
+ * @return the number of rows deleted from the table
+ */
+ public static int deleteFromTableWhere(JdbcTemplate jdbcTemplate, String tableName,
+ String whereClause, Object... args) {
+ String sql = "DELETE FROM " + tableName;
+ if (StringUtils.hasText(whereClause)) {
+ sql += " WHERE " + whereClause;
+ }
+ int rowCount = (args != null && args.length > 0 ? jdbcTemplate.update(sql, args)
+ : jdbcTemplate.update(sql));
+ if (logger.isInfoEnabled()) {
+ logger.info("Deleted " + rowCount + " rows from table " + tableName);
+ }
+ return rowCount;
+ }
+
+ /**
+ * Drop the specified tables.
+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
+ * @param tableNames the names of the tables to drop
+ */
+ public static void dropTables(JdbcTemplate jdbcTemplate, String... tableNames) {
+ for (String tableName : tableNames) {
+ jdbcTemplate.execute("DROP TABLE " + tableName);
+ if (logger.isInfoEnabled()) {
+ logger.info("Dropped table " + tableName);
+ }
+ }
+ }
+
+ /**
+ * Execute the given SQL script.
+ * <p>The script will typically be loaded from the classpath. There should
+ * be one statement per line. Any semicolons and line comments will be removed.
+ * <p><b>Do not use this method to execute DDL if you expect rollback.</b>
+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
+ * @param resourceLoader the resource loader with which to load the SQL script
+ * @param sqlResourcePath the Spring resource path for the SQL script
+ * @param continueOnError whether or not to continue without throwing an
+ * exception in the event of an error
+ * @throws DataAccessException if there is an error executing a statement
+ * and {@code continueOnError} is {@code false}
+ * @see ResourceDatabasePopulator
+ * @see #executeSqlScript(JdbcTemplate, Resource, boolean)
+ */
+ public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader resourceLoader,
+ String sqlResourcePath, boolean continueOnError) throws DataAccessException {
+ Resource resource = resourceLoader.getResource(sqlResourcePath);
+ executeSqlScript(jdbcTemplate, resource, continueOnError);
+ }
+
+ /**
+ * Execute the given SQL script.
+ * <p>The script will typically be loaded from the classpath. Statements
+ * should be delimited with a semicolon. If statements are not delimited with
+ * a semicolon then there should be one statement per line. Statements are
+ * allowed to span lines only if they are delimited with a semicolon. Any
+ * line comments will be removed.
+ * <p><b>Do not use this method to execute DDL if you expect rollback.</b>
+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
+ * @param resource the resource to load the SQL script from
+ * @param continueOnError whether or not to continue without throwing an
+ * exception in the event of an error
+ * @throws DataAccessException if there is an error executing a statement
+ * and {@code continueOnError} is {@code false}
+ * @see ResourceDatabasePopulator
+ * @see #executeSqlScript(JdbcTemplate, EncodedResource, boolean)
+ */
+ public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource, boolean continueOnError)
+ throws DataAccessException {
+ executeSqlScript(jdbcTemplate, new EncodedResource(resource), continueOnError);
+ }
+
+ /**
+ * Execute the given SQL script.
+ * <p>The script will typically be loaded from the classpath. There should
+ * be one statement per line. Any semicolons and line comments will be removed.
+ * <p><b>Do not use this method to execute DDL if you expect rollback.</b>
+ * @param jdbcTemplate the JdbcTemplate with which to perform JDBC operations
+ * @param resource the resource (potentially associated with a specific encoding)
+ * to load the SQL script from
+ * @param continueOnError whether or not to continue without throwing an
+ * exception in the event of an error
+ * @throws DataAccessException if there is an error executing a statement
+ * and {@code continueOnError} is {@code false}
+ * @see ResourceDatabasePopulator
+ */
+ public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource resource, boolean continueOnError)
+ throws DataAccessException {
+
+ if (logger.isInfoEnabled()) {
+ logger.info("Executing SQL script from " + resource);
+ }
+ long startTime = System.currentTimeMillis();
+ List<String> statements = new LinkedList<String>();
+ LineNumberReader reader = null;
+ try {
+ reader = new LineNumberReader(resource.getReader());
+ String script = readScript(reader);
+ char delimiter = DEFAULT_STATEMENT_SEPARATOR;
+ if (!containsSqlScriptDelimiters(script, delimiter)) {
+ delimiter = '\n';
+ }
+ splitSqlScript(script, delimiter, statements);
+ int lineNumber = 0;
+ for (String statement : statements) {
+ lineNumber++;
+ try {
+ int rowsAffected = jdbcTemplate.update(statement);
+ if (logger.isDebugEnabled()) {
+ logger.debug(rowsAffected + " rows affected by SQL: " + statement);
+ }
+ }
+ catch (DataAccessException ex) {
+ if (continueOnError) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Failed to execute SQL script statement at line " + lineNumber
+ + " of resource " + resource + ": " + statement, ex);
+ }
+ }
+ else {
+ throw ex;
+ }
+ }
+ }
+ long elapsedTime = System.currentTimeMillis() - startTime;
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format("Executed SQL script from %s in %s ms.", resource, elapsedTime));
+ }
+ }
+ catch (IOException ex) {
+ throw new DataAccessResourceFailureException("Failed to open SQL script from " + resource, ex);
+ }
+ finally {
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+ catch (IOException ex) {
+ // ignore
+ }
+ }
+ }
+
+ /**
+ * Read a script from the provided {@code LineNumberReader}, using
+ * "{@code --}" as the comment prefix, and build a {@code String} containing
+ * the lines.
+ * @param lineNumberReader the {@code LineNumberReader} containing the script
+ * to be processed
+ * @return a {@code String} containing the script lines
+ * @see #readScript(LineNumberReader, String)
+ */
+ public static String readScript(LineNumberReader lineNumberReader) throws IOException {
+ return readScript(lineNumberReader, DEFAULT_COMMENT_PREFIX);
+ }
+
+ /**
+ * Read a script from the provided {@code LineNumberReader}, using the supplied
+ * comment prefix, and build a {@code String} containing the lines.
+ * <p>Lines <em>beginning</em> with the comment prefix are excluded from the
+ * results; however, line comments anywhere else &mdash; for example, within
+ * a statement &mdash; will be included in the results.
+ * @param lineNumberReader the {@code LineNumberReader} containing the script
+ * to be processed
+ * @param commentPrefix the prefix that identifies comments in the SQL script &mdash; typically "--"
+ * @return a {@code String} containing the script lines
+ */
+ public static String readScript(LineNumberReader lineNumberReader, String commentPrefix) throws IOException {
+ String currentStatement = lineNumberReader.readLine();
+ StringBuilder scriptBuilder = new StringBuilder();
+ while (currentStatement != null) {
+ if (StringUtils.hasText(currentStatement)
+ && (commentPrefix != null && !currentStatement.startsWith(commentPrefix))) {
+ if (scriptBuilder.length() > 0) {
+ scriptBuilder.append('\n');
+ }
+ scriptBuilder.append(currentStatement);
+ }
+ currentStatement = lineNumberReader.readLine();
+ }
+ return scriptBuilder.toString();
+ }
+
+ /**
+ * Determine if the provided SQL script contains the specified delimiter.
+ * @param script the SQL script
+ * @param delim character delimiting each statement &mdash; typically a ';' character
+ * @return {@code true} if the script contains the delimiter; {@code false} otherwise
+ */
+ public static boolean containsSqlScriptDelimiters(String script, char delim) {
+ boolean inLiteral = false;
+ char[] content = script.toCharArray();
+ for (int i = 0; i < script.length(); i++) {
+ if (content[i] == '\'') {
+ inLiteral = !inLiteral;
+ }
+ if (content[i] == delim && !inLiteral) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Split an SQL script into separate statements delimited by the provided
+ * delimiter character. Each individual statement will be added to the
+ * provided {@code List}.
+ * <p>Within a statement, "{@code --}" will be used as the comment prefix;
+ * any text beginning with the comment prefix and extending to the end of
+ * the line will be omitted from the statement. In addition, multiple adjacent
+ * whitespace characters will be collapsed into a single space.
+ * @param script the SQL script
+ * @param delim character delimiting each statement &mdash; typically a ';' character
+ * @param statements the list that will contain the individual statements
+ */
+ public static void splitSqlScript(String script, char delim, List<String> statements) {
+ splitSqlScript(script, "" + delim, DEFAULT_COMMENT_PREFIX, statements);
+ }
+
+ /**
+ * Split an SQL script into separate statements delimited by the provided
+ * delimiter string. Each individual statement will be added to the provided
+ * {@code List}.
+ * <p>Within a statement, the provided {@code commentPrefix} will be honored;
+ * any text beginning with the comment prefix and extending to the end of the
+ * line will be omitted from the statement. In addition, multiple adjacent
+ * whitespace characters will be collapsed into a single space.
+ * @param script the SQL script
+ * @param delim character delimiting each statement &mdash; typically a ';' character
+ * @param commentPrefix the prefix that identifies line comments in the SQL script &mdash; typically "--"
+ * @param statements the List that will contain the individual statements
+ */
+ private static void splitSqlScript(String script, String delim, String commentPrefix, List<String> statements) {
+ StringBuilder sb = new StringBuilder();
+ boolean inLiteral = false;
+ boolean inEscape = false;
+ char[] content = script.toCharArray();
+ for (int i = 0; i < script.length(); i++) {
+ char c = content[i];
+ if (inEscape) {
+ inEscape = false;
+ sb.append(c);
+ continue;
+ }
+ // MySQL style escapes
+ if (c == '\\') {
+ inEscape = true;
+ sb.append(c);
+ continue;
+ }
+ if (c == '\'') {
+ inLiteral = !inLiteral;
+ }
+ if (!inLiteral) {
+ if (script.startsWith(delim, i)) {
+ // we've reached the end of the current statement
+ if (sb.length() > 0) {
+ statements.add(sb.toString());
+ sb = new StringBuilder();
+ }
+ i += delim.length() - 1;
+ continue;
+ }
+ else if (script.startsWith(commentPrefix, i)) {
+ // skip over any content from the start of the comment to the EOL
+ int indexOfNextNewline = script.indexOf("\n", i);
+ if (indexOfNextNewline > i) {
+ i = indexOfNextNewline;
+ continue;
+ }
+ else {
+ // if there's no newline after the comment, we must be at the end
+ // of the script, so stop here.
+ break;
+ }
+ }
+ else if (c == ' ' || c == '\n' || c == '\t') {
+ // avoid multiple adjacent whitespace characters
+ if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
+ c = ' ';
+ }
+ else {
+ continue;
+ }
+ }
+ }
+ sb.append(c);
+ }
+ if (StringUtils.hasText(sb)) {
+ statements.add(sb.toString());
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/jdbc/SimpleJdbcTestUtils.java b/spring-test/src/main/java/org/springframework/test/jdbc/SimpleJdbcTestUtils.java
new file mode 100644
index 00000000..24cee9ad
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/jdbc/SimpleJdbcTestUtils.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2002-2012 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.jdbc;
+
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+
+/**
+ * A Java-5-based collection of JDBC related utility functions intended to
+ * simplify standard database testing scenarios.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @author Thomas Risberg
+ * @since 2.5
+ * @deprecated as of Spring 3.1.3; use {@link JdbcTestUtils} instead.
+ */
+@Deprecated
+public abstract class SimpleJdbcTestUtils {
+
+ private static final Log logger = LogFactory.getLog(SimpleJdbcTestUtils.class);
+
+
+ /**
+ * Count the rows in the given table.
+ * @param simpleJdbcTemplate the SimpleJdbcTemplate with which to perform JDBC operations
+ * @param tableName table name to count rows in
+ * @return the number of rows in the table
+ */
+ public static int countRowsInTable(SimpleJdbcTemplate simpleJdbcTemplate, String tableName) {
+ return simpleJdbcTemplate.queryForInt("SELECT COUNT(0) FROM " + tableName);
+ }
+
+ /**
+ * Delete all rows from the specified tables.
+ * @param simpleJdbcTemplate the SimpleJdbcTemplate with which to perform JDBC operations
+ * @param tableNames the names of the tables from which to delete
+ * @return the total number of rows deleted from all specified tables
+ */
+ public static int deleteFromTables(SimpleJdbcTemplate simpleJdbcTemplate, String... tableNames) {
+ int totalRowCount = 0;
+ for (String tableName : tableNames) {
+ int rowCount = simpleJdbcTemplate.update("DELETE FROM " + tableName);
+ totalRowCount += rowCount;
+ if (logger.isInfoEnabled()) {
+ logger.info("Deleted " + rowCount + " rows from table " + tableName);
+ }
+ }
+ return totalRowCount;
+ }
+
+ /**
+ * Execute the given SQL script.
+ * <p>The script will normally be loaded by classpath. There should be one statement
+ * per line. Any semicolons will be removed. <b>Do not use this method to execute
+ * DDL if you expect rollback.</b>
+ * @param simpleJdbcTemplate the SimpleJdbcTemplate with which to perform JDBC operations
+ * @param resourceLoader the resource loader (with which to load the SQL script
+ * @param sqlResourcePath the Spring resource path for the SQL script
+ * @param continueOnError whether or not to continue without throwing an
+ * exception in the event of an error
+ * @throws DataAccessException if there is an error executing a statement
+ * and continueOnError was {@code false}
+ */
+ public static void executeSqlScript(SimpleJdbcTemplate simpleJdbcTemplate, ResourceLoader resourceLoader,
+ String sqlResourcePath, boolean continueOnError) throws DataAccessException {
+
+ Resource resource = resourceLoader.getResource(sqlResourcePath);
+ executeSqlScript(simpleJdbcTemplate, resource, continueOnError);
+ }
+
+ /**
+ * Execute the given SQL script. The script will normally be loaded by classpath.
+ * <p>Statements should be delimited with a semicolon. If statements are not delimited with
+ * a semicolon then there should be one statement per line. Statements are allowed to span
+ * lines only if they are delimited with a semicolon.
+ * <p><b>Do not use this method to execute DDL if you expect rollback.</b>
+ * @param simpleJdbcTemplate the SimpleJdbcTemplate with which to perform JDBC operations
+ * @param resource the resource to load the SQL script from.
+ * @param continueOnError whether or not to continue without throwing an
+ * exception in the event of an error.
+ * @throws DataAccessException if there is an error executing a statement
+ * and continueOnError was {@code false}
+ */
+ public static void executeSqlScript(SimpleJdbcTemplate simpleJdbcTemplate, Resource resource,
+ boolean continueOnError) throws DataAccessException {
+
+ executeSqlScript(simpleJdbcTemplate, new EncodedResource(resource), continueOnError);
+ }
+
+ /**
+ * Execute the given SQL script.
+ * <p>The script will normally be loaded by classpath. There should be one statement
+ * per line. Any semicolons will be removed. <b>Do not use this method to execute
+ * DDL if you expect rollback.</b>
+ * @param simpleJdbcTemplate the SimpleJdbcTemplate with which to perform JDBC operations
+ * @param resource the resource (potentially associated with a specific encoding)
+ * to load the SQL script from.
+ * @param continueOnError whether or not to continue without throwing an
+ * exception in the event of an error.
+ * @throws DataAccessException if there is an error executing a statement
+ * and continueOnError was {@code false}
+ */
+ public static void executeSqlScript(SimpleJdbcTemplate simpleJdbcTemplate, EncodedResource resource,
+ boolean continueOnError) throws DataAccessException {
+
+ if (logger.isInfoEnabled()) {
+ logger.info("Executing SQL script from " + resource);
+ }
+
+ long startTime = System.currentTimeMillis();
+ List<String> statements = new LinkedList<String>();
+ LineNumberReader reader = null;
+ try {
+ reader = new LineNumberReader(resource.getReader());
+ String script = JdbcTestUtils.readScript(reader);
+ char delimiter = ';';
+ if (!JdbcTestUtils.containsSqlScriptDelimiters(script, delimiter)) {
+ delimiter = '\n';
+ }
+ JdbcTestUtils.splitSqlScript(script, delimiter, statements);
+ for (String statement : statements) {
+ try {
+ int rowsAffected = simpleJdbcTemplate.update(statement);
+ if (logger.isDebugEnabled()) {
+ logger.debug(rowsAffected + " rows affected by SQL: " + statement);
+ }
+ }
+ catch (DataAccessException ex) {
+ if (continueOnError) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("SQL: " + statement + " failed", ex);
+ }
+ }
+ else {
+ throw ex;
+ }
+ }
+ }
+ long elapsedTime = System.currentTimeMillis() - startTime;
+ if (logger.isInfoEnabled()) {
+ logger.info("Done executing SQL scriptBuilder from " + resource + " in " + elapsedTime + " ms.");
+ }
+ }
+ catch (IOException ex) {
+ throw new DataAccessResourceFailureException("Failed to open SQL script from " + resource, ex);
+ }
+ finally {
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+ catch (IOException ex) {
+ // ignore
+ }
+
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/jdbc/package-info.java b/spring-test/src/main/java/org/springframework/test/jdbc/package-info.java
new file mode 100644
index 00000000..4587aa46
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/jdbc/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Support classes for tests based on JDBC.
+ */
+
+package org.springframework.test.jdbc;
+
diff --git a/spring-test/src/main/java/org/springframework/test/jpa/AbstractAspectjJpaTests.java b/spring-test/src/main/java/org/springframework/test/jpa/AbstractAspectjJpaTests.java
new file mode 100644
index 00000000..dd55d0e7
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/jpa/AbstractAspectjJpaTests.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2012 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.jpa;
+
+import org.aspectj.weaver.loadtime.ClassPreProcessorAgentAdapter;
+
+import org.springframework.instrument.classloading.ResourceOverridingShadowingClassLoader;
+
+/**
+ * Subclass of {@link AbstractJpaTests} that activates AspectJ load-time weaving and
+ * allows for specifying a custom location for AspectJ's {@code aop.xml} file.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
+ * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests})
+ */
+@Deprecated
+public abstract class AbstractAspectjJpaTests extends AbstractJpaTests {
+
+ /**
+ * Default location of the {@code aop.xml} file in the class path:
+ * "META-INF/aop.xml"
+ */
+ public static final String DEFAULT_AOP_XML_LOCATION = "META-INF/aop.xml";
+
+
+ @Override
+ protected void customizeResourceOverridingShadowingClassLoader(ClassLoader shadowingClassLoader) {
+ ResourceOverridingShadowingClassLoader orxl = (ResourceOverridingShadowingClassLoader) shadowingClassLoader;
+ orxl.override(DEFAULT_AOP_XML_LOCATION, getActualAopXmlLocation());
+ orxl.addTransformer(new ClassPreProcessorAgentAdapter());
+ }
+
+ /**
+ * Return the actual location of the {@code aop.xml} file
+ * in the class path. The default is "META-INF/aop.xml".
+ * <p>Override this method to point to a specific {@code aop.xml}
+ * file within your test suite, allowing for different config files
+ * to co-exist within the same class path.
+ */
+ protected String getActualAopXmlLocation() {
+ return DEFAULT_AOP_XML_LOCATION;
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/jpa/AbstractJpaTests.java b/spring-test/src/main/java/org/springframework/test/jpa/AbstractJpaTests.java
new file mode 100644
index 00000000..005c19e3
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/jpa/AbstractJpaTests.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.jpa;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+
+import junit.framework.TestCase;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver;
+import org.springframework.instrument.classloading.LoadTimeWeaver;
+import org.springframework.instrument.classloading.ResourceOverridingShadowingClassLoader;
+import org.springframework.instrument.classloading.ShadowingClassLoader;
+import org.springframework.orm.jpa.ExtendedEntityManagerCreator;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.SharedEntityManagerCreator;
+import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
+import org.springframework.test.annotation.AbstractAnnotationAwareTransactionalTests;
+import org.springframework.util.StringUtils;
+
+/**
+ * Convenient support class for JPA-related tests. Offers the same contract as
+ * AbstractTransactionalDataSourceSpringContextTests and equally good performance,
+ * even when performing the instrumentation required by the JPA specification.
+ *
+ * <p>Exposes an EntityManagerFactory and a shared EntityManager.
+ * Requires an EntityManagerFactory to be injected, plus the DataSource and
+ * JpaTransactionManager through the superclass.
+ *
+ * <p>When using Xerces, make sure a post 2.0.2 version is available on the classpath
+ * to avoid a critical
+ * <a href="http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16014"/>bug</a>
+ * that leads to StackOverflow. Maven users are likely to encounter this problem since
+ * 2.0.2 is used by default.
+ *
+ * <p>A workaround is to explicitly specify the Xerces version inside the Maven POM:
+ * <pre>
+ * &lt;dependency&gt;
+ * &lt;groupId&gt;xerces&lt;/groupId&gt;
+ * &lt;artifactId&gt;xercesImpl&lt;/artifactId&gt;
+ * &lt;version&gt;2.8.1&lt;/version&gt;
+ * &lt;/dependency&gt;
+ * </pre>
+ *
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
+ * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests})
+ */
+@Deprecated
+public abstract class AbstractJpaTests extends AbstractAnnotationAwareTransactionalTests {
+
+ private static final String DEFAULT_ORM_XML_LOCATION = "META-INF/orm.xml";
+
+ /**
+ * Map from String defining unique combination of config locations, to ApplicationContext.
+ * Values are intentionally not strongly typed, to avoid potential class cast exceptions
+ * through use between different class loaders.
+ */
+ private static Map<String, Object> contextCache = new HashMap<String, Object>();
+
+ private static Map<String, ClassLoader> classLoaderCache = new HashMap<String, ClassLoader>();
+
+ protected EntityManagerFactory entityManagerFactory;
+
+ /**
+ * If this instance is in a shadow loader, this variable
+ * will contain the parent instance of the subclass.
+ * The class will not be the same as the class of the
+ * shadow instance, as it was loaded by a different class loader,
+ * but it can be invoked reflectively. The shadowParent
+ * and the shadow loader can communicate reflectively
+ * but not through direct invocation.
+ */
+ private Object shadowParent;
+
+ /**
+ * Subclasses can use this in test cases.
+ * It will participate in any current transaction.
+ */
+ protected EntityManager sharedEntityManager;
+
+
+ public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
+ this.entityManagerFactory = entityManagerFactory;
+ this.sharedEntityManager = SharedEntityManagerCreator.createSharedEntityManager(this.entityManagerFactory);
+ }
+
+ /**
+ * Create an EntityManager that will always automatically enlist itself in current
+ * transactions, in contrast to an EntityManager returned by
+ * {@code EntityManagerFactory.createEntityManager()}
+ * (which requires an explicit {@code joinTransaction()} call).
+ */
+ protected EntityManager createContainerManagedEntityManager() {
+ return ExtendedEntityManagerCreator.createContainerManagedEntityManager(this.entityManagerFactory);
+ }
+
+ /**
+ * Subclasses should override this method if they wish to disable shadow class loading.
+ * <p>The default implementation deactivates shadow class loading if Spring's
+ * InstrumentationSavingAgent has been configured on VM startup.
+ */
+ protected boolean shouldUseShadowLoader() {
+ return !InstrumentationLoadTimeWeaver.isInstrumentationAvailable();
+ }
+
+ @Override
+ public void setDirty() {
+ super.setDirty();
+ contextCache.remove(cacheKeys());
+ classLoaderCache.remove(cacheKeys());
+
+ // If we are a shadow loader, we need to invoke
+ // the shadow parent to set it dirty, as
+ // it is the shadow parent that maintains the cache state,
+ // not the child
+ if (this.shadowParent != null) {
+ try {
+ Method m = shadowParent.getClass().getMethod("setDirty", (Class[]) null);
+ m.invoke(shadowParent, (Object[]) null);
+ }
+ catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+
+
+ @Override
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public void runBare() throws Throwable {
+
+ // getName will return the name of the method being run.
+ if (isDisabledInThisEnvironment(getName())) {
+ // Let superclass log that we didn't run the test.
+ super.runBare();
+ return;
+ }
+
+ final Method testMethod = getTestMethod();
+
+ if (isDisabledInThisEnvironment(testMethod)) {
+ recordDisabled();
+ this.logger.info("**** " + getClass().getName() + "." + getName() + " is disabled in this environment: "
+ + "Total disabled tests=" + getDisabledTestCount());
+ return;
+ }
+
+ if (!shouldUseShadowLoader()) {
+ super.runBare();
+ return;
+ }
+
+ String combinationOfContextLocationsForThisTestClass = cacheKeys();
+ ClassLoader classLoaderForThisTestClass = getClass().getClassLoader();
+ // save the TCCL
+ ClassLoader initialClassLoader = Thread.currentThread().getContextClassLoader();
+
+ if (this.shadowParent != null) {
+ Thread.currentThread().setContextClassLoader(classLoaderForThisTestClass);
+ super.runBare();
+ }
+
+ else {
+ ShadowingClassLoader shadowingClassLoader = (ShadowingClassLoader) classLoaderCache.get(combinationOfContextLocationsForThisTestClass);
+
+ if (shadowingClassLoader == null) {
+ shadowingClassLoader = (ShadowingClassLoader) createShadowingClassLoader(classLoaderForThisTestClass);
+ classLoaderCache.put(combinationOfContextLocationsForThisTestClass, shadowingClassLoader);
+ }
+ try {
+ Thread.currentThread().setContextClassLoader(shadowingClassLoader);
+ String[] configLocations = getConfigLocations();
+
+ // Do not strongly type, to avoid ClassCastException.
+ Object cachedContext = contextCache.get(combinationOfContextLocationsForThisTestClass);
+
+ if (cachedContext == null) {
+
+ // Create the LoadTimeWeaver.
+ Class shadowingLoadTimeWeaverClass = shadowingClassLoader.loadClass(ShadowingLoadTimeWeaver.class.getName());
+ Constructor constructor = shadowingLoadTimeWeaverClass.getConstructor(ClassLoader.class);
+ constructor.setAccessible(true);
+ Object ltw = constructor.newInstance(shadowingClassLoader);
+
+ // Create the BeanFactory.
+ Class beanFactoryClass = shadowingClassLoader.loadClass(DefaultListableBeanFactory.class.getName());
+ Object beanFactory = BeanUtils.instantiateClass(beanFactoryClass);
+
+ // Create the BeanDefinitionReader.
+ Class beanDefinitionReaderClass = shadowingClassLoader.loadClass(XmlBeanDefinitionReader.class.getName());
+ Class beanDefinitionRegistryClass = shadowingClassLoader.loadClass(BeanDefinitionRegistry.class.getName());
+ Object reader = beanDefinitionReaderClass.getConstructor(beanDefinitionRegistryClass).newInstance(beanFactory);
+
+ // Load the bean definitions into the BeanFactory.
+ Method loadBeanDefinitions = beanDefinitionReaderClass.getMethod("loadBeanDefinitions", String[].class);
+ loadBeanDefinitions.invoke(reader, new Object[] {configLocations});
+
+ // Create LoadTimeWeaver-injecting BeanPostProcessor.
+ Class loadTimeWeaverInjectingBeanPostProcessorClass = shadowingClassLoader.loadClass(LoadTimeWeaverInjectingBeanPostProcessor.class.getName());
+ Class loadTimeWeaverClass = shadowingClassLoader.loadClass(LoadTimeWeaver.class.getName());
+ Constructor bppConstructor = loadTimeWeaverInjectingBeanPostProcessorClass.getConstructor(loadTimeWeaverClass);
+ bppConstructor.setAccessible(true);
+ Object beanPostProcessor = bppConstructor.newInstance(ltw);
+
+ // Add LoadTimeWeaver-injecting BeanPostProcessor.
+ Class beanPostProcessorClass = shadowingClassLoader.loadClass(BeanPostProcessor.class.getName());
+ Method addBeanPostProcessor = beanFactoryClass.getMethod("addBeanPostProcessor", beanPostProcessorClass);
+ addBeanPostProcessor.invoke(beanFactory, beanPostProcessor);
+
+ // Create the GenericApplicationContext.
+ Class genericApplicationContextClass = shadowingClassLoader.loadClass(GenericApplicationContext.class.getName());
+ Class defaultListableBeanFactoryClass = shadowingClassLoader.loadClass(DefaultListableBeanFactory.class.getName());
+ cachedContext = genericApplicationContextClass.getConstructor(defaultListableBeanFactoryClass).newInstance(beanFactory);
+
+ // Invoke the context's "refresh" method.
+ genericApplicationContextClass.getMethod("refresh").invoke(cachedContext);
+
+ // Store the context reference in the cache.
+ contextCache.put(combinationOfContextLocationsForThisTestClass, cachedContext);
+ }
+ // create the shadowed test
+ Class shadowedTestClass = shadowingClassLoader.loadClass(getClass().getName());
+
+ // So long as JUnit is excluded from shadowing we
+ // can minimize reflective invocation here
+ TestCase shadowedTestCase = (TestCase) BeanUtils.instantiateClass(shadowedTestClass);
+
+ /* shadowParent = this */
+ Class thisShadowedClass = shadowingClassLoader.loadClass(AbstractJpaTests.class.getName());
+ Field shadowed = thisShadowedClass.getDeclaredField("shadowParent");
+ shadowed.setAccessible(true);
+ shadowed.set(shadowedTestCase, this);
+
+ /* AbstractSpringContextTests.addContext(Object, ApplicationContext) */
+ Class applicationContextClass = shadowingClassLoader.loadClass(ConfigurableApplicationContext.class.getName());
+ Method addContextMethod = shadowedTestClass.getMethod("addContext", Object.class, applicationContextClass);
+ addContextMethod.invoke(shadowedTestCase, configLocations, cachedContext);
+
+ // Invoke tests on shadowed test case
+ shadowedTestCase.setName(getName());
+ shadowedTestCase.runBare();
+ }
+ catch (InvocationTargetException ex) {
+ // Unwrap this for better exception reporting
+ // when running tests
+ throw ex.getTargetException();
+ }
+ finally {
+ Thread.currentThread().setContextClassLoader(initialClassLoader);
+ }
+ }
+ }
+
+ protected String cacheKeys() {
+ return StringUtils.arrayToCommaDelimitedString(getConfigLocations());
+ }
+
+ /**
+ * NB: This method must <b>not</b> have a return type of ShadowingClassLoader as that would cause that
+ * class to be loaded eagerly when this test case loads, creating verify errors at runtime.
+ */
+ protected ClassLoader createShadowingClassLoader(ClassLoader classLoader) {
+ OrmXmlOverridingShadowingClassLoader orxl = new OrmXmlOverridingShadowingClassLoader(classLoader,
+ getActualOrmXmlLocation());
+ customizeResourceOverridingShadowingClassLoader(orxl);
+ return orxl;
+ }
+
+ /**
+ * Customize the shadowing class loader.
+ * @param shadowingClassLoader this parameter is actually of type
+ * ResourceOverridingShadowingClassLoader, and can safely to be cast to
+ * that type. However, the signature must not be of that type as that
+ * would cause the present class loader to load that type.
+ */
+ protected void customizeResourceOverridingShadowingClassLoader(ClassLoader shadowingClassLoader) {
+ // empty
+ }
+
+ /**
+ * Subclasses can override this to return the real location path for
+ * orm.xml or null if they do not wish to find any orm.xml
+ * @return orm.xml path or null to hide any such file
+ */
+ protected String getActualOrmXmlLocation() {
+ return DEFAULT_ORM_XML_LOCATION;
+ }
+
+
+ private static class LoadTimeWeaverInjectingBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
+
+ private final LoadTimeWeaver ltw;
+
+ @SuppressWarnings("unused")
+ public LoadTimeWeaverInjectingBeanPostProcessor(LoadTimeWeaver ltw) {
+ this.ltw = ltw;
+ }
+
+ public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+ if (bean instanceof LocalContainerEntityManagerFactoryBean) {
+ ((LocalContainerEntityManagerFactoryBean) bean).setLoadTimeWeaver(this.ltw);
+ }
+ if (bean instanceof DefaultPersistenceUnitManager) {
+ ((DefaultPersistenceUnitManager) bean).setLoadTimeWeaver(this.ltw);
+ }
+ return bean;
+ }
+ }
+
+
+ private static class ShadowingLoadTimeWeaver implements LoadTimeWeaver {
+
+ private final ClassLoader shadowingClassLoader;
+
+ @SuppressWarnings("unused")
+ public ShadowingLoadTimeWeaver(ClassLoader shadowingClassLoader) {
+ this.shadowingClassLoader = shadowingClassLoader;
+ }
+
+ public void addTransformer(ClassFileTransformer transformer) {
+ try {
+ Method addClassFileTransformer =
+ this.shadowingClassLoader.getClass().getMethod("addTransformer", ClassFileTransformer.class);
+ addClassFileTransformer.setAccessible(true);
+ addClassFileTransformer.invoke(this.shadowingClassLoader, transformer);
+ }
+ catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public ClassLoader getInstrumentableClassLoader() {
+ return this.shadowingClassLoader;
+ }
+
+ public ClassLoader getThrowawayClassLoader() {
+ // Be sure to copy the same resource overrides and same class file transformers:
+ // We want the throwaway class loader to behave like the instrumentable class loader.
+ ResourceOverridingShadowingClassLoader roscl =
+ new ResourceOverridingShadowingClassLoader(getClass().getClassLoader());
+ if (this.shadowingClassLoader instanceof ShadowingClassLoader) {
+ roscl.copyTransformers((ShadowingClassLoader) this.shadowingClassLoader);
+ }
+ if (this.shadowingClassLoader instanceof ResourceOverridingShadowingClassLoader) {
+ roscl.copyOverrides((ResourceOverridingShadowingClassLoader) this.shadowingClassLoader);
+ }
+ return roscl;
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/jpa/OrmXmlOverridingShadowingClassLoader.java b/spring-test/src/main/java/org/springframework/test/jpa/OrmXmlOverridingShadowingClassLoader.java
new file mode 100644
index 00000000..9cf1ded1
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/jpa/OrmXmlOverridingShadowingClassLoader.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2012 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.jpa;
+
+import org.springframework.instrument.classloading.ResourceOverridingShadowingClassLoader;
+
+/**
+ * Subclass of ShadowingClassLoader that overrides attempts to
+ * locate {@code orm.xml}.
+ *
+ * <p>This class must <b>not</b> be an inner class of AbstractJpaTests
+ * to avoid it being loaded until first used.
+ *
+ * @author Rod Johnson
+ * @author Adrian Colyer
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+class OrmXmlOverridingShadowingClassLoader extends ResourceOverridingShadowingClassLoader {
+
+ /**
+ * Default location of the {@code orm.xml} file in the class path:
+ * "META-INF/orm.xml"
+ */
+ public static final String DEFAULT_ORM_XML_LOCATION = "META-INF/orm.xml";
+
+
+ public OrmXmlOverridingShadowingClassLoader(ClassLoader loader, String realOrmXmlLocation) {
+ super(loader);
+
+ // Automatically exclude classes from these well-known persistence providers.
+ // Do NOT exclude Hibernate classes --
+ // this causes class casts due to use of CGLIB by Hibernate.
+ // Same goes for OpenJPA which will not enhance the domain classes.
+ excludePackage("oracle.toplink.essentials");
+ excludePackage("junit");
+
+ override(DEFAULT_ORM_XML_LOCATION, realOrmXmlLocation);
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/jpa/package-info.java b/spring-test/src/main/java/org/springframework/test/jpa/package-info.java
new file mode 100644
index 00000000..58ef717a
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/jpa/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * As of Spring 3.0, this package has been deprecated in favor of using the listener-based
+ * <em>Spring TestContext Framework</em>.
+ *
+ */
+package org.springframework.test.jpa;
+
diff --git a/spring-test/src/main/java/org/springframework/test/package-info.java b/spring-test/src/main/java/org/springframework/test/package-info.java
new file mode 100644
index 00000000..9ec02f96
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * This package contains the legacy JUnit 3.8 class hierarchy, which as of Spring 3.0
+ * has been deprecated in favor of using the listener-based <em>Spring TestContext Framework</em>.
+ *
+ */
+package org.springframework.test;
+
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
new file mode 100644
index 00000000..4f2dbab7
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/util/AssertionErrors.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2002-2012 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;
+
+import org.springframework.util.ObjectUtils;
+
+/**
+ * JUnit independent assertion class.
+ *
+ * @author Lukas Krecan
+ * @author Arjen Poutsma
+ * @since 3.2
+ */
+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) {
+ throw new AssertionError(message);
+ }
+
+ /**
+ * Fails a test with the given message passing along expected and actual
+ * values to be added to the message.
+ *
+ * <p>For example given:
+ * <pre>
+ * assertEquals("Response header [" + name + "]", actual, expected);
+ * </pre>
+ * <p>The resulting message is:
+ * <pre>
+ * Response header [Accept] expected:&lt;application/json&gt; but was:&lt;text/plain&gt;
+ * </pre>
+ *
+ * @param message describes the value that failed the match
+ * @param expected expected value
+ * @param actual actual value
+ */
+ public static void fail(String message, Object expected, Object actual) {
+ throw new AssertionError(message + " expected:<" + expected + "> but was:<" + actual + ">");
+ }
+
+ /**
+ * 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
+ */
+ public static void assertTrue(String message, boolean condition) {
+ if (!condition) {
+ fail(message);
+ }
+ }
+
+ /**
+ * Assert two objects are equal raise an {@link AssertionError} if not.
+ * <p>For example:
+ * <pre>
+ * 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);
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..6b54d547
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.util.Assert;
+import org.springframework.util.MethodInvoker;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@code ReflectionTestUtils} is a collection of reflection-based utility
+ * methods for use in unit and integration testing scenarios.
+ *
+ * <p>There are often times when it would be beneficial to be able to set a
+ * non-{@code public} field, invoke a non-{@code public} setter method, or
+ * invoke a non-{@code public} <em>configuration</em> or <em>lifecycle</em>
+ * callback method when testing code involving, for example:
+ *
+ * <ul>
+ * <li>ORM frameworks such as JPA and Hibernate which condone the usage of
+ * {@code private} or {@code protected} field access as opposed to
+ * {@code public} setter methods for properties in a domain entity.</li>
+ * <li>Spring's support for annotations such as
+ * {@link org.springframework.beans.factory.annotation.Autowired @Autowired} and
+ * {@link javax.annotation.Resource @Resource} which provides dependency
+ * injection for {@code private} or {@code protected} fields, setter methods,
+ * and configuration methods.</li>
+ * <li>Use of annotations such as {@link javax.annotation.PostConstruct @PostConstruct}
+ * and {@link javax.annotation.PreDestroy @PreDestroy} for lifecycle callback
+ * methods.</li>
+ * </ul>
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see ReflectionUtils
+ */
+public class ReflectionTestUtils {
+
+ private static final String SETTER_PREFIX = "set";
+
+ private static final String GETTER_PREFIX = "get";
+
+ private static final Log logger = LogFactory.getLog(ReflectionTestUtils.class);
+
+
+ /**
+ * Set the {@link Field field} with the given {@code name} on the provided
+ * {@link Object target object} to the supplied {@code value}.
+ *
+ * <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},
+ * {@code private}, and <em>package-private</em> fields.
+ *
+ * @param target the target object on which to set the field
+ * @param name the name of the field to set
+ * @param value the value to set
+ * @see ReflectionUtils#findField(Class, String, Class)
+ * @see ReflectionUtils#makeAccessible(Field)
+ * @see ReflectionUtils#setField(Field, Object, Object)
+ */
+ public static void setField(Object target, String name, Object value) {
+ setField(target, name, value, null);
+ }
+
+ /**
+ * Set the {@link Field field} with the given {@code name} on the provided
+ * {@link Object target object} to the supplied {@code value}.
+ *
+ * <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},
+ * {@code private}, and <em>package-private</em> fields.
+ *
+ * @param target the target object on which to set the field
+ * @param name the name of the field to set
+ * @param value the value to set
+ * @param type the type of the field (may be {@code null})
+ * @see ReflectionUtils#findField(Class, String, Class)
+ * @see ReflectionUtils#makeAccessible(Field)
+ * @see ReflectionUtils#setField(Field, Object, Object)
+ */
+ public static void setField(Object target, String name, Object value, Class<?> type) {
+ Assert.notNull(target, "Target object must not be null");
+ Field field = ReflectionUtils.findField(target.getClass(), name, type);
+
+ // SPR-9571: inline Assert.notNull() in order to avoid accidentally invoking
+ // toString() on a non-null target.
+ if (field == null) {
+ throw new IllegalArgumentException(String.format("Could not find field [%s] of type [%s] on target [%s]",
+ name, type, target));
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Setting field [%s] of type [%s] on target [%s] to value [%s]", name, type,
+ target,
+ value));
+ }
+ ReflectionUtils.makeAccessible(field);
+ ReflectionUtils.setField(field, target, value);
+ }
+
+ /**
+ * Get the field with the given {@code name} from the provided target object.
+ *
+ * <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},
+ * {@code private}, and <em>package-private</em> fields.
+ *
+ * @param target the target object on which to set the field
+ * @param name the name of the field to get
+ * @return the field's current value
+ * @see ReflectionUtils#findField(Class, String, Class)
+ * @see ReflectionUtils#makeAccessible(Field)
+ * @see ReflectionUtils#setField(Field, Object, Object)
+ */
+ public static Object getField(Object target, String name) {
+ Assert.notNull(target, "Target object must not be null");
+ Field field = ReflectionUtils.findField(target.getClass(), name);
+ Assert.notNull(field, "Could not find field [" + name + "] on target [" + target + "]");
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Getting field [" + name + "] from target [" + target + "]");
+ }
+ ReflectionUtils.makeAccessible(field);
+ return ReflectionUtils.getField(field, target);
+ }
+
+ /**
+ * Invoke the setter method with the given {@code name} on the supplied
+ * target object with the supplied {@code value}.
+ *
+ * <p>This method traverses the class hierarchy in search of the desired
+ * method. In addition, an attempt will be made to make non-{@code public}
+ * methods <em>accessible</em>, thus allowing one to invoke {@code protected},
+ * {@code private}, and <em>package-private</em> setter methods.
+ *
+ * <p>In addition, this method supports JavaBean-style <em>property</em>
+ * names. For example, if you wish to set the {@code name} property on the
+ * target object, you may pass either &quot;name&quot; or
+ * &quot;setName&quot; as the method name.
+ *
+ * @param target the target object on which to invoke the specified setter
+ * method
+ * @param name the name of the setter method to invoke or the corresponding
+ * property name
+ * @param value the value to provide to the setter method
+ * @see ReflectionUtils#findMethod(Class, String, Class[])
+ * @see ReflectionUtils#makeAccessible(Method)
+ * @see ReflectionUtils#invokeMethod(Method, Object, Object[])
+ */
+ public static void invokeSetterMethod(Object target, String name, Object value) {
+ invokeSetterMethod(target, name, value, null);
+ }
+
+ /**
+ * Invoke the setter method with the given {@code name} on the supplied
+ * target object with the supplied {@code value}.
+ *
+ * <p>This method traverses the class hierarchy in search of the desired
+ * method. In addition, an attempt will be made to make non-{@code public}
+ * methods <em>accessible</em>, thus allowing one to invoke {@code protected},
+ * {@code private}, and <em>package-private</em> setter methods.
+ *
+ * <p>In addition, this method supports JavaBean-style <em>property</em>
+ * names. For example, if you wish to set the {@code name} property on the
+ * target object, you may pass either &quot;name&quot; or
+ * &quot;setName&quot; as the method name.
+ *
+ * @param target the target object on which to invoke the specified setter
+ * method
+ * @param name the name of the setter method to invoke or the corresponding
+ * property name
+ * @param value the value to provide to the setter method
+ * @param type the formal parameter type declared by the setter method
+ * @see ReflectionUtils#findMethod(Class, String, Class[])
+ * @see ReflectionUtils#makeAccessible(Method)
+ * @see ReflectionUtils#invokeMethod(Method, Object, Object[])
+ */
+ public static void invokeSetterMethod(Object target, String name, Object value, Class<?> type) {
+ Assert.notNull(target, "Target object must not be null");
+ Assert.hasText(name, "Method name must not be empty");
+ Class<?>[] paramTypes = (type != null ? new Class<?>[] { type } : null);
+
+ String setterMethodName = name;
+ if (!name.startsWith(SETTER_PREFIX)) {
+ setterMethodName = SETTER_PREFIX + StringUtils.capitalize(name);
+ }
+ Method method = ReflectionUtils.findMethod(target.getClass(), setterMethodName, paramTypes);
+ if (method == null && !setterMethodName.equals(name)) {
+ setterMethodName = name;
+ method = ReflectionUtils.findMethod(target.getClass(), setterMethodName, paramTypes);
+ }
+ Assert.notNull(method, "Could not find setter method [" + setterMethodName + "] on target [" + target
+ + "] with parameter type [" + type + "]");
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Invoking setter method [" + setterMethodName + "] on target [" + target + "]");
+ }
+ ReflectionUtils.makeAccessible(method);
+ ReflectionUtils.invokeMethod(method, target, new Object[] { value });
+ }
+
+ /**
+ * Invoke the getter method with the given {@code name} on the supplied
+ * target object with the supplied {@code value}.
+ *
+ * <p>This method traverses the class hierarchy in search of the desired
+ * method. In addition, an attempt will be made to make non-{@code public}
+ * methods <em>accessible</em>, thus allowing one to invoke {@code protected},
+ * {@code private}, and <em>package-private</em> getter methods.
+ *
+ * <p>In addition, this method supports JavaBean-style <em>property</em>
+ * names. For example, if you wish to get the {@code name} property on the
+ * target object, you may pass either &quot;name&quot; or
+ * &quot;getName&quot; as the method name.
+ *
+ * @param target the target object on which to invoke the specified getter
+ * method
+ * @param name the name of the getter method to invoke or the corresponding
+ * property name
+ * @return the value returned from the invocation
+ * @see ReflectionUtils#findMethod(Class, String, Class[])
+ * @see ReflectionUtils#makeAccessible(Method)
+ * @see ReflectionUtils#invokeMethod(Method, Object, Object[])
+ */
+ public static Object invokeGetterMethod(Object target, String name) {
+ Assert.notNull(target, "Target object must not be null");
+ Assert.hasText(name, "Method name must not be empty");
+
+ String getterMethodName = name;
+ if (!name.startsWith(GETTER_PREFIX)) {
+ getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
+ }
+ Method method = ReflectionUtils.findMethod(target.getClass(), getterMethodName);
+ if (method == null && !getterMethodName.equals(name)) {
+ getterMethodName = name;
+ method = ReflectionUtils.findMethod(target.getClass(), getterMethodName);
+ }
+ Assert.notNull(method, "Could not find getter method [" + getterMethodName + "] on target [" + target + "]");
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Invoking getter method [" + getterMethodName + "] on target [" + target + "]");
+ }
+ ReflectionUtils.makeAccessible(method);
+ return ReflectionUtils.invokeMethod(method, target);
+ }
+
+ /**
+ * Invoke the method with the given {@code name} on the supplied target
+ * object with the supplied arguments.
+ *
+ * <p>This method traverses the class hierarchy in search of the desired
+ * method. In addition, an attempt will be made to make non-{@code public}
+ * methods <em>accessible</em>, thus allowing one to invoke {@code protected},
+ * {@code private}, and <em>package-private</em> methods.
+ *
+ * @param target the target object on which to invoke the specified method
+ * @param name the name of the method to invoke
+ * @param args the arguments to provide to the method
+ * @return the invocation result, if any
+ * @see MethodInvoker
+ * @see ReflectionUtils#makeAccessible(Method)
+ * @see ReflectionUtils#invokeMethod(Method, Object, Object[])
+ * @see ReflectionUtils#handleReflectionException(Exception)
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T invokeMethod(Object target, String name, Object... args) {
+ Assert.notNull(target, "Target object must not be null");
+ Assert.hasText(name, "Method name must not be empty");
+
+ try {
+ MethodInvoker methodInvoker = new MethodInvoker();
+ methodInvoker.setTargetObject(target);
+ methodInvoker.setTargetMethod(name);
+ methodInvoker.setArguments(args);
+ methodInvoker.prepare();
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Invoking method [" + name + "] on target [" + target + "] with arguments ["
+ + ObjectUtils.nullSafeToString(args) + "]");
+ }
+
+ return (T) methodInvoker.invoke();
+ }
+ catch (Exception e) {
+ ReflectionUtils.handleReflectionException(e);
+ }
+
+ throw new IllegalStateException("Should never get here");
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/util/package-info.java b/spring-test/src/main/java/org/springframework/test/util/package-info.java
new file mode 100644
index 00000000..7d9e7496
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/util/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * General utility classes for use in unit and integration tests.
+ */
+
+package org.springframework.test.util; \ No newline at end of file
diff --git a/spring-test/src/main/java/org/springframework/test/web/AbstractModelAndViewTests.java b/spring-test/src/main/java/org/springframework/test/web/AbstractModelAndViewTests.java
new file mode 100644
index 00000000..fefc31e1
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/web/AbstractModelAndViewTests.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import org.springframework.web.servlet.ModelAndView;
+
+/**
+ * Convenient JUnit 3.8 base class for tests dealing with Spring Web MVC
+ * {@link org.springframework.web.servlet.ModelAndView ModelAndView} objects.
+ *
+ * <p>All {@code assert*()} methods throw {@link AssertionFailedError}s.
+ *
+ * <p>Consider the use of {@link ModelAndViewAssert} with JUnit 4 and TestNG.
+ *
+ * @author Alef Arendsen
+ * @author Bram Smeets
+ * @author Sam Brannen
+ * @since 2.0
+ * @see org.springframework.web.servlet.ModelAndView
+ * @see ModelAndViewAssert
+ * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
+ * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests})
+ * or {@link ModelAndViewAssert} with JUnit 4 and TestNG.
+ */
+@Deprecated
+public abstract class AbstractModelAndViewTests extends TestCase {
+
+ /**
+ * Checks whether the model value under the given {@code modelName}
+ * exists and checks it type, based on the {@code expectedType}. If
+ * the model entry exists and the type matches, the model value is returned.
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param modelName name of the object to add to the model (never
+ * {@code null})
+ * @param expectedType expected type of the model value
+ * @return the model value
+ */
+ protected <T> T assertAndReturnModelAttributeOfType(ModelAndView mav, String modelName, Class<T> expectedType) {
+ try {
+ return ModelAndViewAssert.assertAndReturnModelAttributeOfType(mav, modelName, expectedType);
+ }
+ catch (AssertionError e) {
+ throw new AssertionFailedError(e.getMessage());
+ }
+ }
+
+ /**
+ * Compare each individual entry in a list, without first sorting the lists.
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param modelName name of the object to add to the model (never
+ * {@code null})
+ * @param expectedList the expected list
+ */
+ @SuppressWarnings("rawtypes")
+ protected void assertCompareListModelAttribute(ModelAndView mav, String modelName, List expectedList) {
+ try {
+ ModelAndViewAssert.assertCompareListModelAttribute(mav, modelName, expectedList);
+ }
+ catch (AssertionError e) {
+ throw new AssertionFailedError(e.getMessage());
+ }
+ }
+
+ /**
+ * Assert whether or not a model attribute is available.
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param modelName name of the object to add to the model (never
+ * {@code null})
+ */
+ protected void assertModelAttributeAvailable(ModelAndView mav, String modelName) {
+ try {
+ ModelAndViewAssert.assertModelAttributeAvailable(mav, modelName);
+ }
+ catch (AssertionError e) {
+ throw new AssertionFailedError(e.getMessage());
+ }
+ }
+
+ /**
+ * Compare a given {@code expectedValue} to the value from the model
+ * bound under the given {@code modelName}.
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param modelName name of the object to add to the model (never
+ * {@code null})
+ * @param expectedValue the model value
+ */
+ protected void assertModelAttributeValue(ModelAndView mav, String modelName, Object expectedValue) {
+ try {
+ ModelAndViewAssert.assertModelAttributeValue(mav, modelName, expectedValue);
+ }
+ catch (AssertionError e) {
+ throw new AssertionFailedError(e.getMessage());
+ }
+ }
+
+ /**
+ * Inspect the {@code expectedModel} to see if all elements in the
+ * model appear and are equal.
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param expectedModel the expected model
+ */
+ protected void assertModelAttributeValues(ModelAndView mav, Map<String, Object> expectedModel) {
+ try {
+ ModelAndViewAssert.assertModelAttributeValues(mav, expectedModel);
+ }
+ catch (AssertionError e) {
+ throw new AssertionFailedError(e.getMessage());
+ }
+ }
+
+ /**
+ * Compare each individual entry in a list after having sorted both lists
+ * (optionally using a comparator).
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param modelName name of the object to add to the model (never
+ * {@code null})
+ * @param expectedList the expected list
+ * @param comparator the comparator to use (may be {@code null}). If
+ * not specifying the comparator, both lists will be sorted not using
+ * any comparator.
+ */
+ @SuppressWarnings("rawtypes")
+ protected void assertSortAndCompareListModelAttribute(
+ ModelAndView mav, String modelName, List expectedList, Comparator comparator) {
+ try {
+ ModelAndViewAssert.assertSortAndCompareListModelAttribute(mav, modelName, expectedList, comparator);
+ }
+ catch (AssertionError e) {
+ throw new AssertionFailedError(e.getMessage());
+ }
+ }
+
+ /**
+ * Check to see if the view name in the ModelAndView matches the given
+ * {@code expectedName}.
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param expectedName the name of the model value
+ */
+ protected void assertViewName(ModelAndView mav, String expectedName) {
+ try {
+ ModelAndViewAssert.assertViewName(mav, expectedName);
+ }
+ catch (AssertionError e) {
+ throw new AssertionFailedError(e.getMessage());
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/web/ModelAndViewAssert.java b/spring-test/src/main/java/org/springframework/test/web/ModelAndViewAssert.java
new file mode 100644
index 00000000..62677fdb
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/web/ModelAndViewAssert.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2002-2012 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;
+
+import static org.springframework.test.util.AssertionErrors.*;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.servlet.ModelAndView;
+
+/**
+ * A collection of assertions intended to simplify testing scenarios dealing
+ * with Spring Web MVC {@link org.springframework.web.servlet.ModelAndView
+ * ModelAndView} objects.
+ * <p>
+ * Intended for use with JUnit 4 and TestNG. All {@code assert*()} methods
+ * throw {@link AssertionError}s.
+ *
+ * @author Sam Brannen
+ * @author Alef Arendsen
+ * @author Bram Smeets
+ * @since 2.5
+ * @see org.springframework.web.servlet.ModelAndView
+ */
+public abstract class ModelAndViewAssert {
+
+ /**
+ * Checks whether the model value under the given {@code modelName}
+ * exists and checks it type, based on the {@code expectedType}. If the
+ * model entry exists and the type matches, the model value is returned.
+ *
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param modelName name of the object to add to the model (never
+ * {@code null})
+ * @param expectedType expected type of the model value
+ * @return the model value
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T assertAndReturnModelAttributeOfType(ModelAndView mav, String modelName, Class<T> expectedType) {
+ assertTrue("ModelAndView is null", mav != null);
+ assertTrue("Model is null", mav.getModel() != null);
+ Object obj = mav.getModel().get(modelName);
+ assertTrue("Model attribute with name '" + modelName + "' is null", obj != null);
+ assertTrue("Model attribute is not of expected type '" + expectedType.getName() + "' but rather of type '"
+ + obj.getClass().getName() + "'", expectedType.isAssignableFrom(obj.getClass()));
+ return (T) obj;
+ }
+
+ /**
+ * Compare each individual entry in a list, without first sorting the lists.
+ *
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param modelName name of the object to add to the model (never
+ * {@code null})
+ * @param expectedList the expected list
+ */
+ @SuppressWarnings("rawtypes")
+ public static void assertCompareListModelAttribute(ModelAndView mav, String modelName, List expectedList) {
+ assertTrue("ModelAndView is null", mav != null);
+ List modelList = assertAndReturnModelAttributeOfType(mav, modelName, List.class);
+ assertTrue(
+ "Size of model list is '" + modelList.size() + "' while size of expected list is '" + expectedList.size()
+ + "'", expectedList.size() == modelList.size());
+ assertTrue("List in model under name '" + modelName + "' is not equal to the expected list.",
+ expectedList.equals(modelList));
+ }
+
+ /**
+ * Assert whether or not a model attribute is available.
+ *
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param modelName name of the object to add to the model (never
+ * {@code null})
+ */
+ public static void assertModelAttributeAvailable(ModelAndView mav, String modelName) {
+ assertTrue("ModelAndView is null", mav != null);
+ assertTrue("Model is null", mav.getModel() != null);
+ assertTrue("Model attribute with name '" + modelName + "' is not available",
+ mav.getModel().containsKey(modelName));
+ }
+
+ /**
+ * Compare a given {@code expectedValue} to the value from the model
+ * bound under the given {@code modelName}.
+ *
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param modelName name of the object to add to the model (never
+ * {@code null})
+ * @param expectedValue the model value
+ */
+ public static void assertModelAttributeValue(ModelAndView mav, String modelName, Object expectedValue) {
+ assertTrue("ModelAndView is null", mav != null);
+ Object modelValue = assertAndReturnModelAttributeOfType(mav, modelName, Object.class);
+ assertTrue("Model value with name '" + modelName + "' is not the same as the expected value which was '"
+ + expectedValue + "'", modelValue.equals(expectedValue));
+ }
+
+ /**
+ * Inspect the {@code expectedModel} to see if all elements in the
+ * model appear and are equal.
+ *
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param expectedModel the expected model
+ */
+ public static void assertModelAttributeValues(ModelAndView mav, Map<String, Object> expectedModel) {
+ assertTrue("ModelAndView is null", mav != null);
+ assertTrue("Model is null", mav.getModel() != null);
+
+ if (!mav.getModel().keySet().equals(expectedModel.keySet())) {
+ StringBuilder sb = new StringBuilder("Keyset of expected model does not match.\n");
+ appendNonMatchingSetsErrorMessage(expectedModel.keySet(), mav.getModel().keySet(), sb);
+ fail(sb.toString());
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (String modelName : mav.getModel().keySet()) {
+ Object assertionValue = expectedModel.get(modelName);
+ Object mavValue = mav.getModel().get(modelName);
+ if (!assertionValue.equals(mavValue)) {
+ sb.append("Value under name '").append(modelName).append("' differs, should have been '").append(
+ assertionValue).append("' but was '").append(mavValue).append("'\n");
+ }
+ }
+
+ if (sb.length() != 0) {
+ sb.insert(0, "Values of expected model do not match.\n");
+ fail(sb.toString());
+ }
+ }
+
+ /**
+ * Compare each individual entry in a list after having sorted both lists
+ * (optionally using a comparator).
+ *
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param modelName name of the object to add to the model (never
+ * {@code null})
+ * @param expectedList the expected list
+ * @param comparator the comparator to use (may be {@code null}). If
+ * not specifying the comparator, both lists will be sorted not using any
+ * comparator.
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static void assertSortAndCompareListModelAttribute(ModelAndView mav, String modelName, List expectedList,
+ Comparator comparator) {
+
+ assertTrue("ModelAndView is null", mav != null);
+ List modelList = assertAndReturnModelAttributeOfType(mav, modelName, List.class);
+
+ assertTrue(
+ "Size of model list is '" + modelList.size() + "' while size of expected list is '" + expectedList.size()
+ + "'", expectedList.size() == modelList.size());
+
+ if (comparator != null) {
+ Collections.sort(modelList, comparator);
+ Collections.sort(expectedList, comparator);
+ }
+ else {
+ Collections.sort(modelList);
+ Collections.sort(expectedList);
+ }
+
+ assertTrue("List in model under name '" + modelName + "' is not equal to the expected list.",
+ expectedList.equals(modelList));
+ }
+
+ /**
+ * Check to see if the view name in the ModelAndView matches the given
+ * {@code expectedName}.
+ *
+ * @param mav ModelAndView to test against (never {@code null})
+ * @param expectedName the name of the model value
+ */
+ public static void assertViewName(ModelAndView mav, String expectedName) {
+ assertTrue("ModelAndView is null", mav != null);
+ assertTrue("View name is not equal to '" + expectedName + "' but was '" + mav.getViewName() + "'",
+ ObjectUtils.nullSafeEquals(expectedName, mav.getViewName()));
+ }
+
+ private static void appendNonMatchingSetsErrorMessage(Set<String> assertionSet, Set<String> incorrectSet,
+ StringBuilder sb) {
+
+ Set<String> tempSet = new HashSet<String>();
+ tempSet.addAll(incorrectSet);
+ tempSet.removeAll(assertionSet);
+
+ if (tempSet.size() > 0) {
+ sb.append("Set has too many elements:\n");
+ for (Object element : tempSet) {
+ sb.append('-');
+ sb.append(element);
+ sb.append('\n');
+ }
+ }
+
+ tempSet = new HashSet<String>();
+ tempSet.addAll(assertionSet);
+ tempSet.removeAll(incorrectSet);
+
+ if (tempSet.size() > 0) {
+ sb.append("Set is missing elements:\n");
+ for (Object element : tempSet) {
+ sb.append('-');
+ sb.append(element);
+ sb.append('\n');
+ }
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/web/package-info.java b/spring-test/src/main/java/org/springframework/test/web/package-info.java
new file mode 100644
index 00000000..00853208
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/web/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Helper classes for unit tests based on Spring's web support.
+ */
+
+package org.springframework.test.web;
+
diff --git a/spring-test/src/main/java/overview.html b/spring-test/src/main/java/overview.html
new file mode 100644
index 00000000..96bde489
--- /dev/null
+++ b/spring-test/src/main/java/overview.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+<p>
+Spring's TestContext framework. Also includes common Servlet and Portlet API mocks.
+</p>
+</body>
+</html>
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
new file mode 100644
index 00000000..61240cb4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockFilterChainTests.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.springframework.mock.web;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.mockito.BDDMockito.*;
+
+/**
+ * Test fixture for {@link MockFilterChain}.
+ *
+ * @author Rob Winch
+ */
+public class MockFilterChainTests {
+
+ private ServletRequest request;
+
+ private ServletResponse response;
+
+ @Before
+ public void setup() {
+ this.request = new MockHttpServletRequest();
+ this.response = new MockHttpServletResponse();
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void constructorNullServlet() {
+ new MockFilterChain((Servlet) null);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void constructorNullFilter() {
+ new MockFilterChain(mock(Servlet.class), (Filter) null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void doFilterNullRequest() throws Exception {
+ MockFilterChain chain = new MockFilterChain();
+ chain.doFilter(null, this.response);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void doFilterNullResponse() throws Exception {
+ MockFilterChain chain = new MockFilterChain();
+ chain.doFilter(this.request, null);
+ }
+
+ @Test
+ public void doFilterEmptyChain() throws Exception {
+ MockFilterChain chain = new MockFilterChain();
+ chain.doFilter(this.request, this.response);
+
+ assertThat(chain.getRequest(), is(request));
+ assertThat(chain.getResponse(), is(response));
+
+ try {
+ chain.doFilter(this.request, this.response);
+ fail("Expected Exception");
+ }
+ catch(IllegalStateException ex) {
+ assertEquals("This FilterChain has already been called!", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void doFilterWithServlet() throws Exception {
+ Servlet servlet = mock(Servlet.class);
+ MockFilterChain chain = new MockFilterChain(servlet);
+ chain.doFilter(this.request, this.response);
+ verify(servlet).service(this.request, this.response);
+ try {
+ chain.doFilter(this.request, this.response);
+ fail("Expected Exception");
+ }
+ catch(IllegalStateException ex) {
+ assertEquals("This FilterChain has already been called!", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void doFilterWithServletAndFilters() throws Exception {
+ Servlet servlet = mock(Servlet.class);
+
+ MockFilter filter2 = new MockFilter(servlet);
+ MockFilter filter1 = new MockFilter(null);
+ MockFilterChain chain = new MockFilterChain(servlet, filter1, filter2);
+
+ chain.doFilter(this.request, this.response);
+
+ assertTrue(filter1.invoked);
+ assertTrue(filter2.invoked);
+
+ verify(servlet).service(this.request, this.response);
+
+ try {
+ chain.doFilter(this.request, this.response);
+ fail("Expected Exception");
+ }
+ catch(IllegalStateException ex) {
+ assertEquals("This FilterChain has already been called!", ex.getMessage());
+ }
+ }
+
+
+ private static class MockFilter implements Filter {
+
+ private final Servlet servlet;
+
+ private boolean invoked;
+
+ public MockFilter(Servlet servlet) {
+ this.servlet = servlet;
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+
+ this.invoked = true;
+
+ if (this.servlet != null) {
+ this.servlet.service(request, response);
+ }
+ else {
+ chain.doFilter(request, response);
+ }
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void destroy() {
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java
new file mode 100644
index 00000000..a237bb00
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.mock.web;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for {@link MockHttpServletRequest}.
+ *
+ * @author Rick Evans
+ * @author Mark Fisher
+ * @author Rossen Stoyanchev
+ * @author Sam Brannen
+ */
+public class MockHttpServletRequestTests {
+
+ private MockHttpServletRequest request = new MockHttpServletRequest();
+
+
+ @Test
+ public void setContentType() {
+ String contentType = "test/plain";
+ request.setContentType(contentType);
+ assertEquals(contentType, request.getContentType());
+ assertEquals(contentType, request.getHeader("Content-Type"));
+ assertNull(request.getCharacterEncoding());
+ }
+
+ @Test
+ public void setContentTypeUTF8() {
+ String contentType = "test/plain;charset=UTF-8";
+ request.setContentType(contentType);
+ assertEquals(contentType, request.getContentType());
+ assertEquals(contentType, request.getHeader("Content-Type"));
+ assertEquals("UTF-8", request.getCharacterEncoding());
+ }
+
+ @Test
+ public void contentTypeHeader() {
+ String contentType = "test/plain";
+ request.addHeader("Content-Type", contentType);
+ assertEquals(contentType, request.getContentType());
+ assertEquals(contentType, request.getHeader("Content-Type"));
+ assertNull(request.getCharacterEncoding());
+ }
+
+ @Test
+ public void contentTypeHeaderUTF8() {
+ String contentType = "test/plain;charset=UTF-8";
+ request.addHeader("Content-Type", contentType);
+ assertEquals(contentType, request.getContentType());
+ assertEquals(contentType, request.getHeader("Content-Type"));
+ assertEquals("UTF-8", request.getCharacterEncoding());
+ }
+
+ @Test
+ public void setContentTypeThenCharacterEncoding() {
+ request.setContentType("test/plain");
+ request.setCharacterEncoding("UTF-8");
+ assertEquals("test/plain", request.getContentType());
+ assertEquals("test/plain;charset=UTF-8", request.getHeader("Content-Type"));
+ assertEquals("UTF-8", request.getCharacterEncoding());
+ }
+
+ @Test
+ public void setCharacterEncodingThenContentType() {
+ request.setCharacterEncoding("UTF-8");
+ request.setContentType("test/plain");
+ assertEquals("test/plain", request.getContentType());
+ assertEquals("test/plain;charset=UTF-8", request.getHeader("Content-Type"));
+ assertEquals("UTF-8", request.getCharacterEncoding());
+ }
+
+ @Test
+ public void httpHeaderNameCasingIsPreserved() throws Exception {
+ String headerName = "Header1";
+ request.addHeader(headerName, "value1");
+ Enumeration<String> requestHeaders = request.getHeaderNames();
+ assertNotNull(requestHeaders);
+ assertEquals("HTTP header casing not being preserved", headerName, requestHeaders.nextElement());
+ }
+
+ @Test
+ public void nullParameterName() {
+ assertNull(request.getParameter(null));
+ assertNull(request.getParameterValues(null));
+ }
+
+ @Test
+ public void setMultipleParameters() {
+ request.setParameter("key1", "value1");
+ request.setParameter("key2", "value2");
+ Map<String, Object> params = new HashMap<String, Object>(2);
+ params.put("key1", "newValue1");
+ params.put("key3", new String[] { "value3A", "value3B" });
+ request.setParameters(params);
+ String[] values1 = request.getParameterValues("key1");
+ assertEquals(1, values1.length);
+ assertEquals("newValue1", request.getParameter("key1"));
+ assertEquals("value2", request.getParameter("key2"));
+ String[] values3 = request.getParameterValues("key3");
+ assertEquals(2, values3.length);
+ assertEquals("value3A", values3[0]);
+ assertEquals("value3B", values3[1]);
+ }
+
+ @Test
+ public void addMultipleParameters() {
+ request.setParameter("key1", "value1");
+ request.setParameter("key2", "value2");
+ Map<String, Object> params = new HashMap<String, Object>(2);
+ params.put("key1", "newValue1");
+ params.put("key3", new String[] { "value3A", "value3B" });
+ request.addParameters(params);
+ String[] values1 = request.getParameterValues("key1");
+ assertEquals(2, values1.length);
+ assertEquals("value1", values1[0]);
+ assertEquals("newValue1", values1[1]);
+ assertEquals("value2", request.getParameter("key2"));
+ String[] values3 = request.getParameterValues("key3");
+ assertEquals(2, values3.length);
+ assertEquals("value3A", values3[0]);
+ assertEquals("value3B", values3[1]);
+ }
+
+ @Test
+ public void removeAllParameters() {
+ request.setParameter("key1", "value1");
+ Map<String, Object> params = new HashMap<String, Object>(2);
+ params.put("key2", "value2");
+ params.put("key3", new String[] { "value3A", "value3B" });
+ request.addParameters(params);
+ assertEquals(3, request.getParameterMap().size());
+ request.removeAllParameters();
+ assertEquals(0, request.getParameterMap().size());
+ }
+
+ @Test
+ public void defaultLocale() {
+ Locale originalDefaultLocale = Locale.getDefault();
+ try {
+ Locale newDefaultLocale = originalDefaultLocale.equals(Locale.GERMANY) ? Locale.FRANCE : Locale.GERMANY;
+ Locale.setDefault(newDefaultLocale);
+ // Create the request after changing the default locale.
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ assertFalse(newDefaultLocale.equals(request.getLocale()));
+ assertEquals(Locale.ENGLISH, request.getLocale());
+ }
+ finally {
+ Locale.setDefault(originalDefaultLocale);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void setPreferredLocalesWithNullList() {
+ request.setPreferredLocales(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void setPreferredLocalesWithEmptyList() {
+ request.setPreferredLocales(new ArrayList<Locale>());
+ }
+
+ @Test
+ public void setPreferredLocales() {
+ List<Locale> preferredLocales = Arrays.asList(Locale.ITALY, Locale.CHINA);
+ request.setPreferredLocales(preferredLocales);
+ assertEqualEnumerations(Collections.enumeration(preferredLocales), request.getLocales());
+ }
+
+ private void assertEqualEnumerations(Enumeration<?> enum1, Enumeration<?> enum2) {
+ assertNotNull(enum1);
+ assertNotNull(enum2);
+ int count = 0;
+ while (enum1.hasMoreElements()) {
+ assertTrue("enumerations must be equal in length", enum2.hasMoreElements());
+ assertEquals("enumeration element #" + ++count, enum1.nextElement(), enum2.nextElement());
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java
new file mode 100644
index 00000000..867b86ad
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.mock.web;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+
+import org.springframework.web.util.WebUtils;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for {@link MockHttpServletResponse}.
+ *
+ * @author Juergen Hoeller
+ * @author Rick Evans
+ * @author Rossen Stoyanchev
+ * @author Rob Winch
+ * @author Sam Brannen
+ * @since 19.02.2006
+ */
+public class MockHttpServletResponseTests {
+
+ private MockHttpServletResponse response = new MockHttpServletResponse();
+
+
+ @Test
+ public void setContentType() {
+ String contentType = "test/plain";
+ response.setContentType(contentType);
+ assertEquals(contentType, response.getContentType());
+ assertEquals(contentType, response.getHeader("Content-Type"));
+ assertEquals(WebUtils.DEFAULT_CHARACTER_ENCODING, response.getCharacterEncoding());
+ }
+
+ @Test
+ public void setContentTypeUTF8() {
+ String contentType = "test/plain;charset=UTF-8";
+ response.setContentType(contentType);
+ assertEquals("UTF-8", response.getCharacterEncoding());
+ assertEquals(contentType, response.getContentType());
+ assertEquals(contentType, response.getHeader("Content-Type"));
+ }
+
+ @Test
+ public void contentTypeHeader() {
+ String contentType = "test/plain";
+ response.addHeader("Content-Type", contentType);
+ assertEquals(contentType, response.getContentType());
+ assertEquals(contentType, response.getHeader("Content-Type"));
+ assertEquals(WebUtils.DEFAULT_CHARACTER_ENCODING, response.getCharacterEncoding());
+
+ response = new MockHttpServletResponse();
+ response.setHeader("Content-Type", contentType);
+ assertEquals(contentType, response.getContentType());
+ assertEquals(contentType, response.getHeader("Content-Type"));
+ assertEquals(WebUtils.DEFAULT_CHARACTER_ENCODING, response.getCharacterEncoding());
+ }
+
+ @Test
+ public void contentTypeHeaderUTF8() {
+ String contentType = "test/plain;charset=UTF-8";
+ response.setHeader("Content-Type", contentType);
+ assertEquals(contentType, response.getContentType());
+ assertEquals(contentType, response.getHeader("Content-Type"));
+ assertEquals("UTF-8", response.getCharacterEncoding());
+
+ response = new MockHttpServletResponse();
+ response.addHeader("Content-Type", contentType);
+ assertEquals(contentType, response.getContentType());
+ assertEquals(contentType, response.getHeader("Content-Type"));
+ assertEquals("UTF-8", response.getCharacterEncoding());
+ }
+
+ @Test
+ public void setContentTypeThenCharacterEncoding() {
+ response.setContentType("test/plain");
+ response.setCharacterEncoding("UTF-8");
+ assertEquals("test/plain", response.getContentType());
+ assertEquals("test/plain;charset=UTF-8", response.getHeader("Content-Type"));
+ assertEquals("UTF-8", response.getCharacterEncoding());
+ }
+
+ @Test
+ public void setCharacterEncodingThenContentType() {
+ response.setCharacterEncoding("UTF-8");
+ response.setContentType("test/plain");
+ assertEquals("test/plain", response.getContentType());
+ assertEquals("test/plain;charset=UTF-8", response.getHeader("Content-Type"));
+ assertEquals("UTF-8", response.getCharacterEncoding());
+ }
+
+ @Test
+ public void contentLength() {
+ response.setContentLength(66);
+ assertEquals(66, response.getContentLength());
+ assertEquals("66", response.getHeader("Content-Length"));
+ }
+
+ @Test
+ public void contentLengthHeader() {
+ response.addHeader("Content-Length", "66");
+ assertEquals(66, response.getContentLength());
+ assertEquals("66", response.getHeader("Content-Length"));
+ }
+
+ @Test
+ public void httpHeaderNameCasingIsPreserved() throws Exception {
+ final String headerName = "Header1";
+ response.addHeader(headerName, "value1");
+ Collection<String> responseHeaders = response.getHeaderNames();
+ assertNotNull(responseHeaders);
+ assertEquals(1, responseHeaders.size());
+ assertEquals("HTTP header casing not being preserved", headerName, responseHeaders.iterator().next());
+ }
+
+ @Test
+ public void servletOutputStreamCommittedWhenBufferSizeExceeded() throws IOException {
+ assertFalse(response.isCommitted());
+ response.getOutputStream().write('X');
+ assertFalse(response.isCommitted());
+ int size = response.getBufferSize();
+ response.getOutputStream().write(new byte[size]);
+ assertTrue(response.isCommitted());
+ assertEquals(size + 1, response.getContentAsByteArray().length);
+ }
+
+ @Test
+ public void servletOutputStreamCommittedOnFlushBuffer() throws IOException {
+ assertFalse(response.isCommitted());
+ response.getOutputStream().write('X');
+ assertFalse(response.isCommitted());
+ response.flushBuffer();
+ assertTrue(response.isCommitted());
+ assertEquals(1, response.getContentAsByteArray().length);
+ }
+
+ @Test
+ public void servletWriterCommittedWhenBufferSizeExceeded() throws IOException {
+ assertFalse(response.isCommitted());
+ response.getWriter().write("X");
+ assertFalse(response.isCommitted());
+ int size = response.getBufferSize();
+ char[] data = new char[size];
+ Arrays.fill(data, 'p');
+ response.getWriter().write(data);
+ assertTrue(response.isCommitted());
+ assertEquals(size + 1, response.getContentAsByteArray().length);
+ }
+
+ @Test
+ public void servletOutputStreamCommittedOnOutputStreamFlush() throws IOException {
+ assertFalse(response.isCommitted());
+ response.getOutputStream().write('X');
+ assertFalse(response.isCommitted());
+ response.getOutputStream().flush();
+ assertTrue(response.isCommitted());
+ assertEquals(1, response.getContentAsByteArray().length);
+ }
+
+ @Test
+ public void servletWriterCommittedOnWriterFlush() throws IOException {
+ assertFalse(response.isCommitted());
+ response.getWriter().write("X");
+ assertFalse(response.isCommitted());
+ response.getWriter().flush();
+ assertTrue(response.isCommitted());
+ assertEquals(1, response.getContentAsByteArray().length);
+ }
+
+ @Test
+ public void servletWriterAutoFlushedForString() throws IOException {
+ response.getWriter().write("X");
+ assertEquals("X", response.getContentAsString());
+ }
+
+ @Test
+ public void servletWriterAutoFlushedForChar() throws IOException {
+ response.getWriter().write('X');
+ assertEquals("X", response.getContentAsString());
+ }
+
+ @Test
+ public void servletWriterAutoFlushedForCharArray() throws IOException {
+ response.getWriter().write("XY".toCharArray());
+ assertEquals("XY", response.getContentAsString());
+ }
+
+ @Test
+ public void sendRedirect() throws IOException {
+ String redirectUrl = "/redirect";
+ response.sendRedirect(redirectUrl);
+ assertEquals(HttpServletResponse.SC_MOVED_TEMPORARILY, response.getStatus());
+ assertEquals(redirectUrl, response.getHeader("Location"));
+ assertEquals(redirectUrl, response.getRedirectedUrl());
+ assertTrue(response.isCommitted());
+ }
+
+ @Test
+ public void locationHeaderUpdatesGetRedirectedUrl() {
+ String redirectUrl = "/redirect";
+ response.setHeader("Location", redirectUrl);
+ assertEquals(redirectUrl, response.getRedirectedUrl());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpSessionTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpSessionTests.java
new file mode 100644
index 00000000..0379ea78
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpSessionTests.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2012 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.mock.web;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link MockHttpSession}.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+public class MockHttpSessionTests {
+
+ private MockHttpSession session = new MockHttpSession();
+
+
+ @Test
+ public void invalidateOnce() {
+ assertFalse(session.isInvalid());
+ session.invalidate();
+ assertTrue(session.isInvalid());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void invalidateTwice() {
+ session.invalidate();
+ session.invalidate();
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockPageContextTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockPageContextTests.java
new file mode 100644
index 00000000..493cdc75
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockPageContextTests.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2012 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.mock.web;
+
+import junit.framework.TestCase;
+
+import javax.servlet.jsp.PageContext;
+
+/**
+ * Unit tests for the {@code MockPageContext} class.
+ *
+ * @author Rick Evans
+ */
+public final class MockPageContextTests extends TestCase {
+
+ public void testSetAttributeWithNoScopeUsesPageScope() throws Exception {
+ String key = "foo";
+ String value = "bar";
+
+ MockPageContext ctx = new MockPageContext();
+ ctx.setAttribute(key, value);
+ assertEquals(value, ctx.getAttribute(key, PageContext.PAGE_SCOPE));
+ assertNull(ctx.getAttribute(key, PageContext.APPLICATION_SCOPE));
+ assertNull(ctx.getAttribute(key, PageContext.REQUEST_SCOPE));
+ assertNull(ctx.getAttribute(key, PageContext.SESSION_SCOPE));
+ }
+
+ public void testRemoveAttributeWithNoScopeSpecifiedRemovesValueFromAllScopes() throws Exception {
+ String key = "foo";
+ String value = "bar";
+
+ MockPageContext ctx = new MockPageContext();
+ ctx.setAttribute(key, value, PageContext.APPLICATION_SCOPE);
+ ctx.removeAttribute(key);
+
+ assertNull(ctx.getAttribute(key, PageContext.PAGE_SCOPE));
+ assertNull(ctx.getAttribute(key, PageContext.APPLICATION_SCOPE));
+ assertNull(ctx.getAttribute(key, PageContext.REQUEST_SCOPE));
+ assertNull(ctx.getAttribute(key, PageContext.SESSION_SCOPE));
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java
new file mode 100644
index 00000000..f647ec7e
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockServletContextTests.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2002-2012 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.mock.web;
+
+import static org.junit.Assert.*;
+
+import java.util.Set;
+
+import javax.servlet.RequestDispatcher;
+
+import org.junit.Test;
+
+/**
+ * @author Juergen Hoeller
+ * @author Chris Beams
+ * @author Sam Brannen
+ * @since 19.02.2006
+ */
+public class MockServletContextTests {
+
+ private final MockServletContext sc = new MockServletContext("org/springframework/mock");
+
+
+ @Test
+ public void listFiles() {
+ Set<String> paths = sc.getResourcePaths("/web");
+ assertNotNull(paths);
+ assertTrue(paths.contains("/web/MockServletContextTests.class"));
+ }
+
+ @Test
+ public void listSubdirectories() {
+ Set<String> paths = sc.getResourcePaths("/");
+ assertNotNull(paths);
+ assertTrue(paths.contains("/web/"));
+ }
+
+ @Test
+ public void listNonDirectory() {
+ Set<String> paths = sc.getResourcePaths("/web/MockServletContextTests.class");
+ assertNull(paths);
+ }
+
+ @Test
+ public void listInvalidPath() {
+ Set<String> paths = sc.getResourcePaths("/web/invalid");
+ assertNull(paths);
+ }
+
+ @Test
+ public void registerContextAndGetContext() {
+ MockServletContext sc2 = new MockServletContext();
+ sc.setContextPath("/");
+ sc.registerContext("/second", sc2);
+ assertSame(sc, sc.getContext("/"));
+ assertSame(sc2, sc.getContext("/second"));
+ }
+
+ @Test
+ public void getMimeType() {
+ assertEquals("text/html", sc.getMimeType("test.html"));
+ assertEquals("image/gif", sc.getMimeType("test.gif"));
+ }
+
+ @Test
+ public void minorVersion() {
+ assertEquals(5, sc.getMinorVersion());
+ sc.setMinorVersion(4);
+ assertEquals(4, sc.getMinorVersion());
+ }
+
+ @Test
+ public void registerAndUnregisterNamedDispatcher() throws Exception {
+ final String name = "test-servlet";
+ final String url = "/test";
+
+ assertNull(sc.getNamedDispatcher(name));
+
+ sc.registerNamedDispatcher(name, new MockRequestDispatcher(url));
+ RequestDispatcher namedDispatcher = sc.getNamedDispatcher(name);
+ assertNotNull(namedDispatcher);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ namedDispatcher.forward(new MockHttpServletRequest(sc), response);
+ assertEquals(url, response.getForwardedUrl());
+
+ sc.unregisterNamedDispatcher(name);
+ assertNull(sc.getNamedDispatcher(name));
+ }
+
+ @Test
+ public void getNamedDispatcherForDefaultServlet() throws Exception {
+ final String name = "default";
+ RequestDispatcher namedDispatcher = sc.getNamedDispatcher(name);
+ assertNotNull(namedDispatcher);
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ namedDispatcher.forward(new MockHttpServletRequest(sc), response);
+ assertEquals(name, response.getForwardedUrl());
+ }
+
+ @Test
+ public void setDefaultServletName() throws Exception {
+ final String originalDefault = "default";
+ final String newDefault = "test";
+ assertNotNull(sc.getNamedDispatcher(originalDefault));
+
+ sc.setDefaultServletName(newDefault);
+ assertEquals(newDefault, sc.getDefaultServletName());
+ assertNull(sc.getNamedDispatcher(originalDefault));
+
+ RequestDispatcher namedDispatcher = sc.getNamedDispatcher(newDefault);
+ assertNotNull(namedDispatcher);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ namedDispatcher.forward(new MockHttpServletRequest(sc), response);
+ assertEquals(newDefault, response.getForwardedUrl());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/AbstractSpr3350SingleSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/AbstractSpr3350SingleSpringContextTests.java
new file mode 100644
index 00000000..ba8c81c2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/AbstractSpr3350SingleSpringContextTests.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test;
+
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * Abstract JUnit 3.8 based unit test which verifies new functionality requested
+ * in <a href="http://opensource.atlassian.com/projects/spring/browse/SPR-3550"
+ * target="_blank">SPR-3350</a>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@SuppressWarnings("deprecation")
+public abstract class AbstractSpr3350SingleSpringContextTests extends AbstractDependencyInjectionSpringContextTests {
+
+ private Pet cat;
+
+
+ public AbstractSpr3350SingleSpringContextTests() {
+ super();
+ }
+
+ public AbstractSpr3350SingleSpringContextTests(String name) {
+ super(name);
+ }
+
+ public final void setCat(final Pet cat) {
+ this.cat = cat;
+ }
+
+ /**
+ * Forcing concrete subclasses to provide a config path appropriate to the
+ * configured
+ * {@link #createBeanDefinitionReader(org.springframework.context.support.GenericApplicationContext)
+ * BeanDefinitionReader}.
+ *
+ * @see org.springframework.test.AbstractSingleSpringContextTests#getConfigPath()
+ */
+ @Override
+ protected abstract String getConfigPath();
+
+ /**
+ * <p>
+ * Test which addresses the following issue raised in SPR-3350:
+ * </p>
+ * <p>
+ * {@link AbstractSingleSpringContextTests} always uses an
+ * {@link XmlBeanDefinitionReader} internally when creating the
+ * {@link ApplicationContext} inside
+ * {@link #createApplicationContext(String[])}. It would be nice to have the
+ * bean definition reader creation in a separate method so that subclasses
+ * can choose that individually without having to copy-n-paste code from
+ * createApplicationContext() to do the context creation and refresh.
+ * Consider JavaConfig where an Annotation based reader can be plugged in.
+ * </p>
+ */
+ public final void testApplicationContextNotAutoCreated() {
+ assertNotNull("The cat field should have been autowired.", this.cat);
+ assertEquals("Garfield", this.cat.getName());
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests-context.properties b/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests-context.properties
new file mode 100644
index 00000000..7b96503a
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests-context.properties
@@ -0,0 +1,2 @@
+cat.(class)=org.springframework.tests.sample.beans.Pet
+cat.$0=Garfield
diff --git a/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests.java
new file mode 100644
index 00000000..07b0cf4b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/PropertiesBasedSpr3350SingleSpringContextTests.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2012 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;
+
+import org.springframework.beans.factory.support.BeanDefinitionReader;
+import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.context.support.GenericApplicationContext;
+
+/**
+ * Concrete implementation of {@link AbstractSpr3350SingleSpringContextTests}
+ * which configures a {@link PropertiesBeanDefinitionReader} instead of the
+ * default {@link XmlBeanDefinitionReader}.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class PropertiesBasedSpr3350SingleSpringContextTests extends AbstractSpr3350SingleSpringContextTests {
+
+ public PropertiesBasedSpr3350SingleSpringContextTests() {
+ super();
+ }
+
+ public PropertiesBasedSpr3350SingleSpringContextTests(String name) {
+ super(name);
+ }
+
+ /**
+ * Creates a new {@link PropertiesBeanDefinitionReader}.
+ *
+ * @see org.springframework.test.AbstractSingleSpringContextTests#createBeanDefinitionReader(org.springframework.context.support.GenericApplicationContext)
+ */
+ @Override
+ protected final BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) {
+ return new PropertiesBeanDefinitionReader(context);
+ }
+
+ /**
+ * Returns
+ * &quot;PropertiesBasedSpr3350SingleSpringContextTests-context.properties&quot;.
+ */
+ @Override
+ protected final String getConfigPath() {
+ return "PropertiesBasedSpr3350SingleSpringContextTests-context.properties";
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/Spr3264DependencyInjectionSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/Spr3264DependencyInjectionSpringContextTests.java
new file mode 100644
index 00000000..08523c79
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/Spr3264DependencyInjectionSpringContextTests.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * JUnit 3.8 based unit test which verifies new functionality requested in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3264"
+ * target="_blank">SPR-3264</a>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see Spr3264SingleSpringContextTests
+ */
+@SuppressWarnings("deprecation")
+public class Spr3264DependencyInjectionSpringContextTests extends AbstractDependencyInjectionSpringContextTests {
+
+ public Spr3264DependencyInjectionSpringContextTests() {
+ super();
+ }
+
+ public Spr3264DependencyInjectionSpringContextTests(String name) {
+ super(name);
+ }
+
+ /**
+ * <p>
+ * Test which addresses the following issue raised in SPR-3264:
+ * </p>
+ * <p>
+ * AbstractDependencyInjectionSpringContextTests will try to apply
+ * auto-injection; this can be disabled but it has to be done manually
+ * inside the onSetUp...
+ * </p>
+ */
+ public void testInjectDependenciesThrowsIllegalStateException() {
+
+ // Re-assert issues covered by Spr3264SingleSpringContextTests as a
+ // safety net.
+ assertNull("The ApplicationContext should NOT be automatically created if no 'locations' are defined.",
+ this.applicationContext);
+ assertEquals("Verifying the ApplicationContext load count.", 0, super.getLoadCount());
+
+ // Assert changes to AbstractDependencyInjectionSpringContextTests:
+ new AssertThrows(IllegalStateException.class) {
+
+ @Override
+ public void test() throws Exception {
+ Spr3264DependencyInjectionSpringContextTests.super.injectDependencies();
+ }
+ }.runTest();
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/Spr3264SingleSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/Spr3264SingleSpringContextTests.java
new file mode 100644
index 00000000..4b3223f4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/Spr3264SingleSpringContextTests.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * JUnit 3.8 based unit test which verifies new functionality requested in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3264"
+ * target="_blank">SPR-3264</a>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see Spr3264DependencyInjectionSpringContextTests
+ */
+@SuppressWarnings("deprecation")
+public class Spr3264SingleSpringContextTests extends AbstractSingleSpringContextTests {
+
+ public Spr3264SingleSpringContextTests() {
+ super();
+ }
+
+ public Spr3264SingleSpringContextTests(String name) {
+ super(name);
+ }
+
+ /**
+ * <p>
+ * Test which addresses the following issue raised in SPR-3264:
+ * </p>
+ * <p>
+ * AbstractSingleSpringContextTests always expects an application context to
+ * be created even if no files/locations are specified which can lead to NPE
+ * problems or force an appCtx to be instantiated even if not needed.
+ * </p>
+ */
+ public void testApplicationContextNotAutoCreated() {
+ assertNull("The ApplicationContext should NOT be automatically created if no 'locations' are defined.",
+ super.applicationContext);
+ assertEquals("Verifying the ApplicationContext load count.", 0, super.getLoadCount());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests-context.xml b/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests-context.xml
new file mode 100644
index 00000000..b1939de1
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests-context.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
+
+ <bean id="cat" class="org.springframework.tests.sample.beans.Pet">
+ <constructor-arg value="Garfield" />
+ </bean>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests.java
new file mode 100644
index 00000000..192d005f
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/XmlBasedSpr3350SingleSpringContextTests.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2012 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;
+
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+
+/**
+ * Concrete implementation of {@link AbstractSpr3350SingleSpringContextTests}
+ * which is based on the default {@link XmlBeanDefinitionReader}.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class XmlBasedSpr3350SingleSpringContextTests extends AbstractSpr3350SingleSpringContextTests {
+
+ public XmlBasedSpr3350SingleSpringContextTests() {
+ super();
+ }
+
+ public XmlBasedSpr3350SingleSpringContextTests(String name) {
+ super(name);
+ }
+
+ /**
+ * Returns &quot;XmlBasedSpr3350SingleSpringContextTests-context.xml&quot;.
+ */
+ @Override
+ protected final String getConfigPath() {
+ return "XmlBasedSpr3350SingleSpringContextTests-context.xml";
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueAnnotationAwareTransactionalTests-context.xml b/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueAnnotationAwareTransactionalTests-context.xml
new file mode 100644
index 00000000..3c4fc976
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueAnnotationAwareTransactionalTests-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
+ p:driverClassName="org.hsqldb.jdbcDriver" p:url="jdbc:hsqldb:mem:transactional_tests" p:username="sa" p:password="" />
+
+ <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:data-source-ref="dataSource" />
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueAnnotationAwareTransactionalTests.java b/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueAnnotationAwareTransactionalTests.java
new file mode 100644
index 00000000..1568b6fb
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueAnnotationAwareTransactionalTests.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+
+/**
+ * Verifies proper handling of {@link IfProfileValue @IfProfileValue} and
+ * {@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration} in
+ * conjunction with {@link AbstractAnnotationAwareTransactionalTests}.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class ProfileValueAnnotationAwareTransactionalTests extends TestCase {
+
+ private static final String NAME = "ProfileValueAnnotationAwareTransactionalTests.profile_value.name";
+
+ private static final String VALUE = "enigma";
+
+
+ public ProfileValueAnnotationAwareTransactionalTests() {
+ System.setProperty(NAME, VALUE);
+ }
+
+ private void runTestAndAssertCounters(Class<? extends DefaultProfileValueSourceTestCase> testCaseType,
+ String testName, int expectedInvocationCount, int expectedErrorCount, int expectedFailureCount)
+ throws Exception {
+
+ DefaultProfileValueSourceTestCase testCase = testCaseType.newInstance();
+ testCase.setName(testName);
+ TestResult testResult = testCase.run();
+ assertEquals("Verifying number of invocations for test method [" + testName + "].", expectedInvocationCount,
+ testCase.invocationCount);
+ assertEquals("Verifying number of errors for test method [" + testName + "].", expectedErrorCount,
+ testResult.errorCount());
+ assertEquals("Verifying number of failures for test method [" + testName + "].", expectedFailureCount,
+ testResult.failureCount());
+ }
+
+ private void runTests(Class<? extends DefaultProfileValueSourceTestCase> testCaseType) throws Exception {
+ runTestAndAssertCounters(testCaseType, "testIfProfileValueEmpty", 0, 0, 0);
+ runTestAndAssertCounters(testCaseType, "testIfProfileValueDisabledViaWrongName", 0, 0, 0);
+ runTestAndAssertCounters(testCaseType, "testIfProfileValueDisabledViaWrongValue", 0, 0, 0);
+ runTestAndAssertCounters(testCaseType, "testIfProfileValueEnabledViaSingleValue", 1, 0, 0);
+ runTestAndAssertCounters(testCaseType, "testIfProfileValueEnabledViaMultipleValues", 1, 0, 0);
+ runTestAndAssertCounters(testCaseType, "testIfProfileValueNotConfigured", 1, 0, 0);
+ }
+
+ public void testDefaultProfileValueSource() throws Exception {
+ assertEquals("Verifying the type of the configured ProfileValueSource.", SystemProfileValueSource.class,
+ new DefaultProfileValueSourceTestCase().getProfileValueSource().getClass());
+ runTests(DefaultProfileValueSourceTestCase.class);
+ }
+
+ public void testHardCodedProfileValueSource() throws Exception {
+ assertEquals("Verifying the type of the configured ProfileValueSource.", HardCodedProfileValueSource.class,
+ new HardCodedProfileValueSourceTestCase().getProfileValueSource().getClass());
+ runTests(HardCodedProfileValueSourceTestCase.class);
+ }
+
+
+ @SuppressWarnings("deprecation")
+ public static class DefaultProfileValueSourceTestCase extends AbstractAnnotationAwareTransactionalTests {
+
+ int invocationCount = 0;
+
+
+ public DefaultProfileValueSourceTestCase() {
+ }
+
+ public ProfileValueSource getProfileValueSource() {
+ return super.profileValueSource;
+ }
+
+ @Override
+ protected String getConfigPath() {
+ return "ProfileValueAnnotationAwareTransactionalTests-context.xml";
+ }
+
+ @NotTransactional
+ @IfProfileValue(name = NAME)
+ public void testIfProfileValueEmpty() {
+ this.invocationCount++;
+ fail("The body of a disabled test should never be executed!");
+ }
+
+ @NotTransactional
+ @IfProfileValue(name = NAME + "X", value = VALUE)
+ public void testIfProfileValueDisabledViaWrongName() {
+ this.invocationCount++;
+ fail("The body of a disabled test should never be executed!");
+ }
+
+ @NotTransactional
+ @IfProfileValue(name = NAME, value = VALUE + "X")
+ public void testIfProfileValueDisabledViaWrongValue() {
+ this.invocationCount++;
+ fail("The body of a disabled test should never be executed!");
+ }
+
+ @NotTransactional
+ @IfProfileValue(name = NAME, value = VALUE)
+ public void testIfProfileValueEnabledViaSingleValue() {
+ this.invocationCount++;
+ }
+
+ @NotTransactional
+ @IfProfileValue(name = NAME, values = { "foo", VALUE, "bar" })
+ public void testIfProfileValueEnabledViaMultipleValues() {
+ this.invocationCount++;
+ }
+
+ @NotTransactional
+ public void testIfProfileValueNotConfigured() {
+ this.invocationCount++;
+ }
+ }
+
+ @ProfileValueSourceConfiguration(HardCodedProfileValueSource.class)
+ public static class HardCodedProfileValueSourceTestCase extends DefaultProfileValueSourceTestCase {
+ }
+
+ public static class HardCodedProfileValueSource implements ProfileValueSource {
+
+ @Override
+ public String get(String key) {
+ return (key.equals(NAME) ? VALUE : null);
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..bb715753
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/annotation/ProfileValueUtilsTests.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Method;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link ProfileValueUtils}.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+public class ProfileValueUtilsTests {
+
+ private static final String NON_ANNOTATED_METHOD = "nonAnnotatedMethod";
+ private static final String ENABLED_ANNOTATED_METHOD = "enabledAnnotatedMethod";
+ private static final String DISABLED_ANNOTATED_METHOD = "disabledAnnotatedMethod";
+
+ private static final String NAME = "ProfileValueUtilsTests.profile_value.name";
+ private static final String VALUE = "enigma";
+
+
+ @BeforeClass
+ public static void setProfileValue() {
+ System.setProperty(NAME, VALUE);
+ }
+
+ private void assertClassIsEnabled(Class<?> testClass) throws Exception {
+ assertTrue("Test class [" + testClass + "] should be enabled.",
+ ProfileValueUtils.isTestEnabledInThisEnvironment(testClass));
+ }
+
+ private void assertClassIsDisabled(Class<?> testClass) throws Exception {
+ assertFalse("Test class [" + testClass + "] should be disbled.",
+ ProfileValueUtils.isTestEnabledInThisEnvironment(testClass));
+ }
+
+ private void assertMethodIsEnabled(String methodName, Class<?> testClass) throws Exception {
+ Method testMethod = testClass.getMethod(methodName);
+ assertTrue("Test method [" + testMethod + "] should be enabled.",
+ ProfileValueUtils.isTestEnabledInThisEnvironment(testMethod, testClass));
+ }
+
+ private void assertMethodIsDisabled(String methodName, Class<?> testClass) throws Exception {
+ Method testMethod = testClass.getMethod(methodName);
+ assertFalse("Test method [" + testMethod + "] should be disabled.",
+ ProfileValueUtils.isTestEnabledInThisEnvironment(testMethod, testClass));
+ }
+
+ private void assertMethodIsEnabled(ProfileValueSource profileValueSource, String methodName, Class<?> testClass)
+ throws Exception {
+ Method testMethod = testClass.getMethod(methodName);
+ assertTrue("Test method [" + testMethod + "] should be enabled for ProfileValueSource [" + profileValueSource
+ + "].", ProfileValueUtils.isTestEnabledInThisEnvironment(profileValueSource, testMethod, testClass));
+ }
+
+ private void assertMethodIsDisabled(ProfileValueSource profileValueSource, String methodName, Class<?> testClass)
+ throws Exception {
+ Method testMethod = testClass.getMethod(methodName);
+ assertFalse("Test method [" + testMethod + "] should be disabled for ProfileValueSource [" + profileValueSource
+ + "].", ProfileValueUtils.isTestEnabledInThisEnvironment(profileValueSource, testMethod, testClass));
+ }
+
+ // -------------------------------------------------------------------
+
+ @Test
+ public void isTestEnabledInThisEnvironmentForProvidedClass() throws Exception {
+ assertClassIsEnabled(NonAnnotated.class);
+ assertClassIsEnabled(EnabledAnnotatedSingleValue.class);
+ assertClassIsEnabled(EnabledAnnotatedMultiValue.class);
+ assertClassIsDisabled(DisabledAnnotatedSingleValue.class);
+ assertClassIsDisabled(DisabledAnnotatedMultiValue.class);
+ }
+
+ @Test
+ public void isTestEnabledInThisEnvironmentForProvidedMethodAndClass() throws Exception {
+ assertMethodIsEnabled(NON_ANNOTATED_METHOD, NonAnnotated.class);
+
+ assertMethodIsEnabled(NON_ANNOTATED_METHOD, EnabledAnnotatedSingleValue.class);
+ assertMethodIsEnabled(ENABLED_ANNOTATED_METHOD, EnabledAnnotatedSingleValue.class);
+ assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, EnabledAnnotatedSingleValue.class);
+
+ assertMethodIsEnabled(NON_ANNOTATED_METHOD, EnabledAnnotatedMultiValue.class);
+ assertMethodIsEnabled(ENABLED_ANNOTATED_METHOD, EnabledAnnotatedMultiValue.class);
+ assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, EnabledAnnotatedMultiValue.class);
+
+ assertMethodIsDisabled(NON_ANNOTATED_METHOD, DisabledAnnotatedSingleValue.class);
+ assertMethodIsDisabled(ENABLED_ANNOTATED_METHOD, DisabledAnnotatedSingleValue.class);
+ assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, DisabledAnnotatedSingleValue.class);
+
+ assertMethodIsDisabled(NON_ANNOTATED_METHOD, DisabledAnnotatedMultiValue.class);
+ assertMethodIsDisabled(ENABLED_ANNOTATED_METHOD, DisabledAnnotatedMultiValue.class);
+ assertMethodIsDisabled(DISABLED_ANNOTATED_METHOD, DisabledAnnotatedMultiValue.class);
+ }
+
+ @Test
+ public void isTestEnabledInThisEnvironmentForProvidedProfileValueSourceMethodAndClass() throws Exception {
+
+ ProfileValueSource profileValueSource = SystemProfileValueSource.getInstance();
+
+ assertMethodIsEnabled(profileValueSource, NON_ANNOTATED_METHOD, NonAnnotated.class);
+
+ assertMethodIsEnabled(profileValueSource, NON_ANNOTATED_METHOD, EnabledAnnotatedSingleValue.class);
+ assertMethodIsEnabled(profileValueSource, ENABLED_ANNOTATED_METHOD, EnabledAnnotatedSingleValue.class);
+ assertMethodIsDisabled(profileValueSource, DISABLED_ANNOTATED_METHOD, EnabledAnnotatedSingleValue.class);
+
+ assertMethodIsEnabled(profileValueSource, NON_ANNOTATED_METHOD, EnabledAnnotatedMultiValue.class);
+ assertMethodIsEnabled(profileValueSource, ENABLED_ANNOTATED_METHOD, EnabledAnnotatedMultiValue.class);
+ assertMethodIsDisabled(profileValueSource, DISABLED_ANNOTATED_METHOD, EnabledAnnotatedMultiValue.class);
+
+ assertMethodIsDisabled(profileValueSource, NON_ANNOTATED_METHOD, DisabledAnnotatedSingleValue.class);
+ assertMethodIsDisabled(profileValueSource, ENABLED_ANNOTATED_METHOD, DisabledAnnotatedSingleValue.class);
+ assertMethodIsDisabled(profileValueSource, DISABLED_ANNOTATED_METHOD, DisabledAnnotatedSingleValue.class);
+
+ assertMethodIsDisabled(profileValueSource, NON_ANNOTATED_METHOD, DisabledAnnotatedMultiValue.class);
+ assertMethodIsDisabled(profileValueSource, ENABLED_ANNOTATED_METHOD, DisabledAnnotatedMultiValue.class);
+ assertMethodIsDisabled(profileValueSource, DISABLED_ANNOTATED_METHOD, DisabledAnnotatedMultiValue.class);
+ }
+
+
+ // -------------------------------------------------------------------
+
+ @SuppressWarnings("unused")
+ private static class NonAnnotated {
+
+ public void nonAnnotatedMethod() {
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @IfProfileValue(name = NAME, value = VALUE)
+ private static class EnabledAnnotatedSingleValue {
+
+ public void nonAnnotatedMethod() {
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE)
+ public void enabledAnnotatedMethod() {
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE + "X")
+ public void disabledAnnotatedMethod() {
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @IfProfileValue(name = NAME, values = { "foo", VALUE, "bar" })
+ private static class EnabledAnnotatedMultiValue {
+
+ public void nonAnnotatedMethod() {
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE)
+ public void enabledAnnotatedMethod() {
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE + "X")
+ public void disabledAnnotatedMethod() {
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @IfProfileValue(name = NAME, value = VALUE + "X")
+ private static class DisabledAnnotatedSingleValue {
+
+ public void nonAnnotatedMethod() {
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE)
+ public void enabledAnnotatedMethod() {
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE + "X")
+ public void disabledAnnotatedMethod() {
+ }
+ }
+
+ @SuppressWarnings("unused")
+ @IfProfileValue(name = NAME, values = { "foo", "bar" })
+ private static class DisabledAnnotatedMultiValue {
+
+ public void nonAnnotatedMethod() {
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE)
+ public void enabledAnnotatedMethod() {
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE + "X")
+ public void disabledAnnotatedMethod() {
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java b/spring-test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java
new file mode 100644
index 00000000..0be28a87
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.JUnitCore;
+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.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit4.TrackingRunListener;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
+
+/**
+ * JUnit 4 based integration test which verifies correct {@linkplain ContextCache
+ * application context caching} in conjunction with the
+ * {@link SpringJUnit4ClassRunner} and the {@link DirtiesContext
+ * &#064;DirtiesContext} annotation at the class level.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@RunWith(JUnit4.class)
+public class ClassLevelDirtiesContextTests {
+
+ private static final AtomicInteger cacheHits = new AtomicInteger(0);
+ private static final AtomicInteger cacheMisses = new AtomicInteger(0);
+
+
+ /**
+ * Asserts the statistics of the supplied context cache.
+ *
+ * @param usageScenario the scenario in which the statistics are used
+ * @param expectedSize the expected number of contexts in the cache
+ * @param expectedHitCount the expected hit count
+ * @param expectedMissCount the expected miss count
+ */
+ private static final void assertCacheStats(String usageScenario, int expectedSize, int expectedHitCount,
+ int expectedMissCount) {
+
+ ContextCache contextCache = TestContextManager.contextCache;
+ assertEquals("Verifying number of contexts in cache (" + usageScenario + ").", expectedSize,
+ contextCache.size());
+ assertEquals("Verifying number of cache hits (" + usageScenario + ").", expectedHitCount,
+ contextCache.getHitCount());
+ assertEquals("Verifying number of cache misses (" + usageScenario + ").", expectedMissCount,
+ contextCache.getMissCount());
+ }
+
+ private static final void runTestClassAndAssertStats(Class<?> testClass, int expectedTestCount) {
+ final int expectedTestFailureCount = 0;
+ final int expectedTestStartedCount = expectedTestCount;
+ final int expectedTestFinishedCount = expectedTestCount;
+
+ TrackingRunListener listener = new TrackingRunListener();
+ JUnitCore jUnitCore = new JUnitCore();
+ jUnitCore.addListener(listener);
+ jUnitCore.run(testClass);
+
+ assertEquals("Verifying number of failures for test class [" + testClass + "].", expectedTestFailureCount,
+ listener.getTestFailureCount());
+ assertEquals("Verifying number of tests started for test class [" + testClass + "].", expectedTestStartedCount,
+ listener.getTestStartedCount());
+ assertEquals("Verifying number of tests finished for test class [" + testClass + "].",
+ expectedTestFinishedCount, listener.getTestFinishedCount());
+ }
+
+ @BeforeClass
+ public static void verifyInitialCacheState() {
+ ContextCache contextCache = TestContextManager.contextCache;
+ contextCache.clear();
+ contextCache.clearStatistics();
+ cacheHits.set(0);
+ cacheMisses.set(0);
+ assertCacheStats("BeforeClass", 0, cacheHits.get(), cacheMisses.get());
+ }
+
+ @Test
+ public void verifyDirtiesContextBehavior() throws Exception {
+
+ assertBehaviorForCleanTestCase();
+
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with clean test method and default class mode", 0,
+ cacheHits.incrementAndGet(), cacheMisses.get());
+ assertBehaviorForCleanTestCase();
+
+ runTestClassAndAssertStats(InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase.class, 1);
+ assertCacheStats("after inherited class-level @DirtiesContext with clean test method and default class mode",
+ 0, cacheHits.incrementAndGet(), cacheMisses.get());
+ assertBehaviorForCleanTestCase();
+
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with clean test method and AFTER_CLASS mode", 0,
+ cacheHits.incrementAndGet(), cacheMisses.get());
+ assertBehaviorForCleanTestCase();
+
+ runTestClassAndAssertStats(InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase.class, 1);
+ assertCacheStats("after inherited class-level @DirtiesContext with clean test method and AFTER_CLASS mode", 0,
+ cacheHits.incrementAndGet(), cacheMisses.get());
+ assertBehaviorForCleanTestCase();
+
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase.class, 3);
+ assertCacheStats("after class-level @DirtiesContext with clean test method and AFTER_EACH_TEST_METHOD mode", 0,
+ cacheHits.incrementAndGet(), cacheMisses.addAndGet(2));
+ assertBehaviorForCleanTestCase();
+
+ runTestClassAndAssertStats(InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase.class, 3);
+ assertCacheStats(
+ "after inherited class-level @DirtiesContext with clean test method and AFTER_EACH_TEST_METHOD mode", 0,
+ cacheHits.incrementAndGet(), cacheMisses.addAndGet(2));
+ assertBehaviorForCleanTestCase();
+
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithDirtyMethodsTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with dirty test method", 0, cacheHits.incrementAndGet(),
+ cacheMisses.get());
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithDirtyMethodsTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with dirty test method", 0, cacheHits.get(),
+ cacheMisses.incrementAndGet());
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithDirtyMethodsTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with dirty test method", 0, cacheHits.get(),
+ cacheMisses.incrementAndGet());
+ assertBehaviorForCleanTestCase();
+
+ runTestClassAndAssertStats(InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase.class, 1);
+ assertCacheStats("after inherited class-level @DirtiesContext with dirty test method", 0,
+ cacheHits.incrementAndGet(), cacheMisses.get());
+ runTestClassAndAssertStats(InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase.class, 1);
+ assertCacheStats("after inherited class-level @DirtiesContext with dirty test method", 0, cacheHits.get(),
+ cacheMisses.incrementAndGet());
+ runTestClassAndAssertStats(InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase.class, 1);
+ assertCacheStats("after inherited class-level @DirtiesContext with dirty test method", 0, cacheHits.get(),
+ cacheMisses.incrementAndGet());
+ assertBehaviorForCleanTestCase();
+
+ runTestClassAndAssertStats(ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase.class, 1);
+ assertCacheStats("after class-level @DirtiesContext with clean test method and AFTER_CLASS mode", 0,
+ cacheHits.incrementAndGet(), cacheMisses.get());
+ }
+
+ private void assertBehaviorForCleanTestCase() {
+ runTestClassAndAssertStats(CleanTestCase.class, 1);
+ assertCacheStats("after clean test class", 1, cacheHits.get(), cacheMisses.incrementAndGet());
+ }
+
+ @AfterClass
+ public static void verifyFinalCacheState() {
+ assertCacheStats("AfterClass", 0, cacheHits.get(), cacheMisses.get());
+ }
+
+
+ // -------------------------------------------------------------------
+
+ @RunWith(SpringJUnit4ClassRunner.class)
+ @TestExecutionListeners( { DependencyInjectionTestExecutionListener.class,
+ DirtiesContextTestExecutionListener.class })
+ @ContextConfiguration
+ public static abstract class BaseTestCase {
+
+ @Configuration
+ static class Config {
+ /* no beans */
+ }
+
+ @Autowired
+ protected ApplicationContext applicationContext;
+
+
+ protected void assertApplicationContextWasAutowired() {
+ assertNotNull("The application context should have been autowired.", this.applicationContext);
+ }
+ }
+
+ public static final class CleanTestCase extends BaseTestCase {
+
+ @Test
+ public void verifyContextWasAutowired() {
+ assertApplicationContextWasAutowired();
+ }
+
+ }
+
+ @DirtiesContext
+ public static class ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends BaseTestCase {
+
+ @Test
+ public void verifyContextWasAutowired() {
+ assertApplicationContextWasAutowired();
+ }
+ }
+
+ public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase extends
+ ClassLevelDirtiesContextWithCleanMethodsAndDefaultModeTestCase {
+ }
+
+ @DirtiesContext(classMode = ClassMode.AFTER_CLASS)
+ public static class ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends BaseTestCase {
+
+ @Test
+ public void verifyContextWasAutowired() {
+ assertApplicationContextWasAutowired();
+ }
+ }
+
+ public static class InheritedClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase extends
+ ClassLevelDirtiesContextWithCleanMethodsAndAfterClassModeTestCase {
+ }
+
+ @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
+ public static class ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends BaseTestCase {
+
+ @Test
+ public void verifyContextWasAutowired1() {
+ assertApplicationContextWasAutowired();
+ }
+
+ @Test
+ public void verifyContextWasAutowired2() {
+ assertApplicationContextWasAutowired();
+ }
+
+ @Test
+ public void verifyContextWasAutowired3() {
+ assertApplicationContextWasAutowired();
+ }
+ }
+
+ public static class InheritedClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase extends
+ ClassLevelDirtiesContextWithAfterEachTestMethodModeTestCase {
+ }
+
+ @DirtiesContext
+ public static class ClassLevelDirtiesContextWithDirtyMethodsTestCase extends BaseTestCase {
+
+ @Test
+ @DirtiesContext
+ public void dirtyContext() {
+ assertApplicationContextWasAutowired();
+ }
+ }
+
+ public static class InheritedClassLevelDirtiesContextWithDirtyMethodsTestCase extends
+ ClassLevelDirtiesContextWithDirtyMethodsTestCase {
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java
new file mode 100644
index 00000000..eb1c39a9
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/ContextCacheTests.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.Assert.*;
+import static org.springframework.test.context.SpringRunnerContextCacheTests.*;
+
+/**
+ * Integration tests for verifying proper behavior of the {@link ContextCache} in
+ * conjunction with cache keys used in {@link TestContext}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see SpringRunnerContextCacheTests
+ */
+public class ContextCacheTests {
+
+ private ContextCache contextCache = new ContextCache();
+
+
+ @Before
+ public void initialCacheState() {
+ assertContextCacheStatistics(contextCache, "initial state", 0, 0, 0);
+ assertParentContextCount(0);
+ }
+
+ private void assertParentContextCount(int expected) {
+ assertEquals("parent context count", expected, contextCache.getParentContextCount());
+ }
+
+ private MergedContextConfiguration getMergedContextConfiguration(TestContext testContext) {
+ return (MergedContextConfiguration) ReflectionTestUtils.getField(testContext, "mergedContextConfiguration");
+ }
+
+ private ApplicationContext loadContext(Class<?> testClass) {
+ TestContext testContext = new TestContext(testClass, contextCache);
+ return testContext.getApplicationContext();
+ }
+
+ private void loadCtxAndAssertStats(Class<?> testClass, int expectedSize, int expectedHitCount, int expectedMissCount) {
+ assertNotNull(loadContext(testClass));
+ assertContextCacheStatistics(contextCache, testClass.getName(), expectedSize, expectedHitCount,
+ expectedMissCount);
+ }
+
+ @Test
+ public void verifyCacheKeyIsBasedOnContextLoader() {
+ loadCtxAndAssertStats(AnnotationConfigContextLoaderTestCase.class, 1, 0, 1);
+ loadCtxAndAssertStats(AnnotationConfigContextLoaderTestCase.class, 1, 1, 1);
+ loadCtxAndAssertStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 1, 2);
+ loadCtxAndAssertStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 2, 2);
+ loadCtxAndAssertStats(AnnotationConfigContextLoaderTestCase.class, 2, 3, 2);
+ loadCtxAndAssertStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 4, 2);
+ }
+
+ @Test
+ public void verifyCacheKeyIsBasedOnActiveProfiles() {
+ loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 0, 1);
+ loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 1, 1);
+ // Profiles {foo, bar} should hash to the same as {bar,foo}
+ loadCtxAndAssertStats(BarFooProfilesTestCase.class, 1, 2, 1);
+ loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 3, 1);
+ loadCtxAndAssertStats(FooBarProfilesTestCase.class, 1, 4, 1);
+ loadCtxAndAssertStats(BarFooProfilesTestCase.class, 1, 5, 1);
+ }
+
+ @Test
+ public void verifyCacheBehaviorForContextHierarchies() {
+ int size = 0;
+ int hits = 0;
+ int misses = 0;
+
+ // Level 1
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel1TestCase.class, ++size, hits, ++misses);
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel1TestCase.class, size, ++hits, misses);
+
+ // Level 2
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel2TestCase.class, ++size /* L2 */, ++hits /* L1 */,
+ ++misses /* L2 */);
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel2TestCase.class, size, ++hits /* L2 */, misses);
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel2TestCase.class, size, ++hits /* L2 */, misses);
+
+ // Level 3-A
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel3aTestCase.class, ++size /* L3A */, ++hits /* L2 */,
+ ++misses /* L3A */);
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel3aTestCase.class, size, ++hits /* L3A */, misses);
+
+ // Level 3-B
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel3bTestCase.class, ++size /* L3B */, ++hits /* L2 */,
+ ++misses /* L3B */);
+ loadCtxAndAssertStats(ClassHierarchyContextHierarchyLevel3bTestCase.class, size, ++hits /* L3B */, misses);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel1() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 1
+ // Should also remove Levels 2, 3-A, and 3-B, leaving nothing.
+ contextCache.remove(getMergedContextConfiguration(testContext3a).getParent().getParent(),
+ HierarchyMode.CURRENT_LEVEL);
+ assertContextCacheStatistics(contextCache, "removed level 1", 0, 1, 4);
+ assertParentContextCount(0);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel1WithExhaustiveMode() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 1
+ // Should also remove Levels 2, 3-A, and 3-B, leaving nothing.
+ contextCache.remove(getMergedContextConfiguration(testContext3a).getParent().getParent(),
+ HierarchyMode.EXHAUSTIVE);
+ assertContextCacheStatistics(contextCache, "removed level 1", 0, 1, 4);
+ assertParentContextCount(0);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel2() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 2
+ // Should also remove Levels 3-A and 3-B, leaving only Level 1 as a context in the
+ // cache but also removing the Level 1 hierarchy since all children have been
+ // removed.
+ contextCache.remove(getMergedContextConfiguration(testContext3a).getParent(), HierarchyMode.CURRENT_LEVEL);
+ assertContextCacheStatistics(contextCache, "removed level 2", 1, 1, 4);
+ assertParentContextCount(0);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel2WithExhaustiveMode() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 2
+ // Should wipe the cache
+ contextCache.remove(getMergedContextConfiguration(testContext3a).getParent(), HierarchyMode.EXHAUSTIVE);
+ assertContextCacheStatistics(contextCache, "removed level 2", 0, 1, 4);
+ assertParentContextCount(0);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel3Then2() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 3-A
+ contextCache.remove(getMergedContextConfiguration(testContext3a), HierarchyMode.CURRENT_LEVEL);
+ assertContextCacheStatistics(contextCache, "removed level 3-A", 3, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 2
+ // Should also remove Level 3-B, leaving only Level 1.
+ contextCache.remove(getMergedContextConfiguration(testContext3b).getParent(), HierarchyMode.CURRENT_LEVEL);
+ assertContextCacheStatistics(contextCache, "removed level 2", 1, 1, 4);
+ assertParentContextCount(0);
+ }
+
+ @Test
+ public void removeContextHierarchyCacheLevel3Then2WithExhaustiveMode() {
+
+ // Load Level 3-A
+ TestContext testContext3a = new TestContext(ClassHierarchyContextHierarchyLevel3aTestCase.class, contextCache);
+ testContext3a.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A", 3, 0, 3);
+ assertParentContextCount(2);
+
+ // Load Level 3-B
+ TestContext testContext3b = new TestContext(ClassHierarchyContextHierarchyLevel3bTestCase.class, contextCache);
+ testContext3b.getApplicationContext();
+ assertContextCacheStatistics(contextCache, "level 3, A and B", 4, 1, 4);
+ assertParentContextCount(2);
+
+ // Remove Level 3-A
+ // Should wipe the cache.
+ contextCache.remove(getMergedContextConfiguration(testContext3a), HierarchyMode.EXHAUSTIVE);
+ assertContextCacheStatistics(contextCache, "removed level 3-A", 0, 1, 4);
+ assertParentContextCount(0);
+
+ // Remove Level 2
+ // Should not actually do anything since the cache was cleared in the
+ // previous step. So the stats should remain the same.
+ contextCache.remove(getMergedContextConfiguration(testContext3b).getParent(), HierarchyMode.EXHAUSTIVE);
+ assertContextCacheStatistics(contextCache, "removed level 2", 0, 1, 4);
+ assertParentContextCount(0);
+ }
+
+
+ @Configuration
+ static class Config {
+ }
+
+ @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class)
+ private static class AnnotationConfigContextLoaderTestCase {
+ }
+
+ @ContextConfiguration(classes = Config.class, loader = CustomAnnotationConfigContextLoader.class)
+ private static class CustomAnnotationConfigContextLoaderTestCase {
+ }
+
+ private static class CustomAnnotationConfigContextLoader extends AnnotationConfigContextLoader {
+ }
+
+ @ActiveProfiles({ "foo", "bar" })
+ @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class)
+ private static class FooBarProfilesTestCase {
+ }
+
+ @ActiveProfiles({ "bar", "foo" })
+ @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class)
+ private static class BarFooProfilesTestCase {
+ }
+
+ @ContextHierarchy({ @ContextConfiguration })
+ private static class ClassHierarchyContextHierarchyLevel1TestCase {
+
+ @Configuration
+ static class Level1Config {
+
+ }
+ }
+
+ @ContextHierarchy({ @ContextConfiguration })
+ private static class ClassHierarchyContextHierarchyLevel2TestCase extends
+ ClassHierarchyContextHierarchyLevel1TestCase {
+
+ @Configuration
+ static class Level2Config {
+
+ }
+ }
+
+ @ContextHierarchy({ @ContextConfiguration })
+ private static class ClassHierarchyContextHierarchyLevel3aTestCase extends
+ ClassHierarchyContextHierarchyLevel2TestCase {
+
+ @Configuration
+ static class Level3aConfig {
+
+ }
+ }
+
+ @ContextHierarchy({ @ContextConfiguration })
+ private static class ClassHierarchyContextHierarchyLevel3bTestCase extends
+ ClassHierarchyContextHierarchyLevel2TestCase {
+
+ @Configuration
+ static class Level3bConfig {
+
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextHierarchyDirtiesContextTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextHierarchyDirtiesContextTests.java
new file mode 100644
index 00000000..3026e3a0
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/ContextHierarchyDirtiesContextTests.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import org.junit.After;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Result;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * Integration tests that verify proper behavior of {@link DirtiesContext @DirtiesContext}
+ * in conjunction with context hierarchies configured via {@link ContextHierarchy @ContextHierarchy}.
+ *
+ * @author Sam Brannen
+ * @author Tadaya Tsuyukubo
+ * @since 3.2.2
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class ContextHierarchyDirtiesContextTests {
+
+ private static ApplicationContext context;
+
+
+ @After
+ public void cleanUp() {
+ ContextHierarchyDirtiesContextTests.context = null;
+ }
+
+ @Test
+ public void classLevelDirtiesContextWithCurrentLevelHierarchyMode() {
+ runTestAndVerifyHierarchies(ClassLevelDirtiesContextWithCurrentLevelModeTestCase.class, true, true, false);
+ }
+
+ @Test
+ public void classLevelDirtiesContextWithExhaustiveHierarchyMode() {
+ runTestAndVerifyHierarchies(ClassLevelDirtiesContextWithExhaustiveModeTestCase.class, false, false, false);
+ }
+
+ @Test
+ public void methodLevelDirtiesContextWithCurrentLevelHierarchyMode() {
+ runTestAndVerifyHierarchies(MethodLevelDirtiesContextWithCurrentLevelModeTestCase.class, true, true, false);
+ }
+
+ @Test
+ public void methodLevelDirtiesContextWithExhaustiveHierarchyMode() {
+ runTestAndVerifyHierarchies(MethodLevelDirtiesContextWithExhaustiveModeTestCase.class, false, false, false);
+ }
+
+ private void runTestAndVerifyHierarchies(Class<? extends FooTestCase> testClass, boolean isFooContextActive,
+ boolean isBarContextActive, boolean isBazContextActive) {
+
+ JUnitCore jUnitCore = new JUnitCore();
+ Result result = jUnitCore.run(testClass);
+ assertTrue("all tests passed", result.wasSuccessful());
+
+ assertThat(ContextHierarchyDirtiesContextTests.context, notNullValue());
+
+ ConfigurableApplicationContext bazContext = (ConfigurableApplicationContext) ContextHierarchyDirtiesContextTests.context;
+ assertEquals("baz", bazContext.getBean("bean", String.class));
+ assertThat("bazContext#isActive()", bazContext.isActive(), is(isBazContextActive));
+
+ ConfigurableApplicationContext barContext = (ConfigurableApplicationContext) bazContext.getParent();
+ assertThat(barContext, notNullValue());
+ assertEquals("bar", barContext.getBean("bean", String.class));
+ assertThat("barContext#isActive()", barContext.isActive(), is(isBarContextActive));
+
+ ConfigurableApplicationContext fooContext = (ConfigurableApplicationContext) barContext.getParent();
+ assertThat(fooContext, notNullValue());
+ assertEquals("foo", fooContext.getBean("bean", String.class));
+ assertThat("fooContext#isActive()", fooContext.isActive(), is(isFooContextActive));
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ @RunWith(SpringJUnit4ClassRunner.class)
+ @ContextHierarchy(@ContextConfiguration(name = "foo"))
+ static abstract class FooTestCase implements ApplicationContextAware {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String bean() {
+ return "foo";
+ }
+ }
+
+
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ ContextHierarchyDirtiesContextTests.context = applicationContext;
+ }
+ }
+
+ @ContextHierarchy(@ContextConfiguration(name = "bar"))
+ static abstract class BarTestCase extends FooTestCase {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String bean() {
+ return "bar";
+ }
+ }
+ }
+
+ @ContextHierarchy(@ContextConfiguration(name = "baz"))
+ static abstract class BazTestCase extends BarTestCase {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String bean() {
+ return "baz";
+ }
+ }
+ }
+
+ // -------------------------------------------------------------------------
+
+ /**
+ * {@link DirtiesContext} is declared at the class level, without specifying
+ * the {@link DirtiesContext.HierarchyMode}.
+ * <p>After running this test class, the context cache should be <em>exhaustively</em>
+ * cleared beginning from the current context hierarchy, upwards to the highest
+ * parent context, and then back down through all subhierarchies of the parent
+ * context.
+ */
+ @DirtiesContext
+ public static class ClassLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
+
+ @Test
+ public void test() {
+ }
+ }
+
+ /**
+ * {@link DirtiesContext} is declared at the class level, specifying the
+ * {@link DirtiesContext.HierarchyMode#CURRENT_LEVEL CURRENT_LEVEL} hierarchy mode.
+ * <p>After running this test class, the context cache should be cleared
+ * beginning from the current context hierarchy and down through all subhierarchies.
+ */
+ @DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL)
+ public static class ClassLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
+
+ @Test
+ public void test() {
+ }
+ }
+
+ /**
+ * {@link DirtiesContext} is declared at the method level, without specifying
+ * the {@link DirtiesContext.HierarchyMode}.
+ * <p>After running this test class, the context cache should be <em>exhaustively</em>
+ * cleared beginning from the current context hierarchy, upwards to the highest
+ * parent context, and then back down through all subhierarchies of the parent
+ * context.
+ */
+ public static class MethodLevelDirtiesContextWithExhaustiveModeTestCase extends BazTestCase {
+
+ @Test
+ @DirtiesContext
+ public void test() {
+ }
+ }
+
+ /**
+ * {@link DirtiesContext} is declared at the method level, specifying the
+ * {@link DirtiesContext.HierarchyMode#CURRENT_LEVEL CURRENT_LEVEL} hierarchy mode.
+ * <p>After running this test class, the context cache should be cleared
+ * beginning from the current context hierarchy and down through all subhierarchies.
+ */
+ public static class MethodLevelDirtiesContextWithCurrentLevelModeTestCase extends BazTestCase {
+
+ @Test
+ @DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL)
+ public void test() {
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests$BareAnnotations-context.xml b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests$BareAnnotations-context.xml
new file mode 100644
index 00000000..44d11048
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests$BareAnnotations-context.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+ <!-- intentionally empty: only needed so that the ContextLoader can find this file -->
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java
new file mode 100644
index 00000000..9553efc8
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/ContextLoaderUtilsTests.java
@@ -0,0 +1,911 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import static org.springframework.test.context.ContextLoaderUtils.*;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Test;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+import org.springframework.test.context.support.DelegatingSmartContextLoader;
+import org.springframework.test.context.support.GenericPropertiesContextLoader;
+import org.springframework.web.context.support.GenericWebApplicationContext;
+
+/**
+ * Unit tests for {@link ContextLoaderUtils}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+public class ContextLoaderUtilsTests {
+
+ private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private static final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> EMPTY_INITIALIZER_CLASSES = //
+ Collections.<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> emptySet();
+
+
+ private void assertAttributes(ContextConfigurationAttributes attributes, Class<?> expectedDeclaringClass,
+ String[] expectedLocations, Class<?>[] expectedClasses,
+ Class<? extends ContextLoader> expectedContextLoaderClass, boolean expectedInheritLocations) {
+ assertEquals(expectedDeclaringClass, attributes.getDeclaringClass());
+ assertArrayEquals(expectedLocations, attributes.getLocations());
+ assertArrayEquals(expectedClasses, attributes.getClasses());
+ assertEquals(expectedInheritLocations, attributes.isInheritLocations());
+ assertEquals(expectedContextLoaderClass, attributes.getContextLoaderClass());
+ }
+
+ private void assertLocationsFooAttributes(ContextConfigurationAttributes attributes) {
+ assertAttributes(attributes, LocationsFoo.class, new String[] { "/foo.xml" }, EMPTY_CLASS_ARRAY,
+ ContextLoader.class, false);
+ }
+
+ private void assertClassesFooAttributes(ContextConfigurationAttributes attributes) {
+ assertAttributes(attributes, ClassesFoo.class, EMPTY_STRING_ARRAY, new Class<?>[] { FooConfig.class },
+ ContextLoader.class, false);
+ }
+
+ private void assertLocationsBarAttributes(ContextConfigurationAttributes attributes) {
+ assertAttributes(attributes, LocationsBar.class, new String[] { "/bar.xml" }, EMPTY_CLASS_ARRAY,
+ AnnotationConfigContextLoader.class, true);
+ }
+
+ private void assertClassesBarAttributes(ContextConfigurationAttributes attributes) {
+ assertAttributes(attributes, ClassesBar.class, EMPTY_STRING_ARRAY, new Class<?>[] { BarConfig.class },
+ AnnotationConfigContextLoader.class, true);
+ }
+
+ private void assertMergedConfig(MergedContextConfiguration mergedConfig, Class<?> expectedTestClass,
+ String[] expectedLocations, Class<?>[] expectedClasses,
+ Class<? extends ContextLoader> expectedContextLoaderClass) {
+ assertMergedConfig(mergedConfig, expectedTestClass, expectedLocations, expectedClasses,
+ EMPTY_INITIALIZER_CLASSES, expectedContextLoaderClass);
+ }
+
+ private void assertMergedConfig(
+ MergedContextConfiguration mergedConfig,
+ Class<?> expectedTestClass,
+ String[] expectedLocations,
+ Class<?>[] expectedClasses,
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> expectedInitializerClasses,
+ Class<? extends ContextLoader> expectedContextLoaderClass) {
+ assertNotNull(mergedConfig);
+ assertEquals(expectedTestClass, mergedConfig.getTestClass());
+ assertNotNull(mergedConfig.getLocations());
+ assertArrayEquals(expectedLocations, mergedConfig.getLocations());
+ assertNotNull(mergedConfig.getClasses());
+ assertArrayEquals(expectedClasses, mergedConfig.getClasses());
+ assertNotNull(mergedConfig.getActiveProfiles());
+ assertEquals(expectedContextLoaderClass, mergedConfig.getContextLoader().getClass());
+ assertNotNull(mergedConfig.getContextInitializerClasses());
+ assertEquals(expectedInitializerClasses, mergedConfig.getContextInitializerClasses());
+ }
+
+ private void debugConfigAttributes(List<ContextConfigurationAttributes> configAttributesList) {
+ // for (ContextConfigurationAttributes configAttributes : configAttributesList) {
+ // System.err.println(configAttributes);
+ // }
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void resolveContextHierarchyAttributesForSingleTestClassWithContextConfigurationAndContextHierarchy() {
+ resolveContextHierarchyAttributes(SingleTestClassWithContextConfigurationAndContextHierarchy.class);
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForSingleTestClassWithImplicitSingleLevelContextHierarchy() {
+ List<List<ContextConfigurationAttributes>> hierarchyAttributes = resolveContextHierarchyAttributes(BareAnnotations.class);
+ assertEquals(1, hierarchyAttributes.size());
+ List<ContextConfigurationAttributes> configAttributesList = hierarchyAttributes.get(0);
+ assertEquals(1, configAttributesList.size());
+ debugConfigAttributes(configAttributesList);
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForSingleTestClassWithSingleLevelContextHierarchy() {
+ List<List<ContextConfigurationAttributes>> hierarchyAttributes = resolveContextHierarchyAttributes(SingleTestClassWithSingleLevelContextHierarchy.class);
+ assertEquals(1, hierarchyAttributes.size());
+ List<ContextConfigurationAttributes> configAttributesList = hierarchyAttributes.get(0);
+ assertEquals(1, configAttributesList.size());
+ debugConfigAttributes(configAttributesList);
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForSingleTestClassWithTripleLevelContextHierarchy() {
+ List<List<ContextConfigurationAttributes>> hierarchyAttributes = resolveContextHierarchyAttributes(SingleTestClassWithTripleLevelContextHierarchy.class);
+ assertEquals(1, hierarchyAttributes.size());
+ List<ContextConfigurationAttributes> configAttributesList = hierarchyAttributes.get(0);
+ assertEquals(3, configAttributesList.size());
+ debugConfigAttributes(configAttributesList);
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForTestClassHierarchyWithSingleLevelContextHierarchies() {
+ List<List<ContextConfigurationAttributes>> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass3WithSingleLevelContextHierarchy.class);
+ assertEquals(3, hierarchyAttributes.size());
+
+ List<ContextConfigurationAttributes> configAttributesListClassLevel1 = hierarchyAttributes.get(0);
+ debugConfigAttributes(configAttributesListClassLevel1);
+ assertEquals(1, configAttributesListClassLevel1.size());
+ assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml"));
+
+ List<ContextConfigurationAttributes> configAttributesListClassLevel2 = hierarchyAttributes.get(1);
+ debugConfigAttributes(configAttributesListClassLevel2);
+ assertEquals(1, configAttributesListClassLevel2.size());
+ assertArrayEquals(new String[] { "two-A.xml", "two-B.xml" },
+ configAttributesListClassLevel2.get(0).getLocations());
+
+ List<ContextConfigurationAttributes> configAttributesListClassLevel3 = hierarchyAttributes.get(2);
+ debugConfigAttributes(configAttributesListClassLevel3);
+ assertEquals(1, configAttributesListClassLevel3.size());
+ assertThat(configAttributesListClassLevel3.get(0).getLocations()[0], equalTo("three.xml"));
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareContextConfigurationInSubclass() {
+ List<List<ContextConfigurationAttributes>> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSubclass.class);
+ assertEquals(2, hierarchyAttributes.size());
+
+ List<ContextConfigurationAttributes> configAttributesListClassLevel1 = hierarchyAttributes.get(0);
+ debugConfigAttributes(configAttributesListClassLevel1);
+ assertEquals(1, configAttributesListClassLevel1.size());
+ assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml"));
+
+ List<ContextConfigurationAttributes> configAttributesListClassLevel2 = hierarchyAttributes.get(1);
+ debugConfigAttributes(configAttributesListClassLevel2);
+ assertEquals(1, configAttributesListClassLevel2.size());
+ assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("two.xml"));
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForTestClassHierarchyWithBareContextConfigurationInSuperclass() {
+ List<List<ContextConfigurationAttributes>> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass2WithBareContextConfigurationInSuperclass.class);
+ assertEquals(2, hierarchyAttributes.size());
+
+ List<ContextConfigurationAttributes> configAttributesListClassLevel1 = hierarchyAttributes.get(0);
+ debugConfigAttributes(configAttributesListClassLevel1);
+ assertEquals(1, configAttributesListClassLevel1.size());
+ assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("one.xml"));
+
+ List<ContextConfigurationAttributes> configAttributesListClassLevel2 = hierarchyAttributes.get(1);
+ debugConfigAttributes(configAttributesListClassLevel2);
+ assertEquals(1, configAttributesListClassLevel2.size());
+ assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("two.xml"));
+ }
+
+ @Test
+ public void resolveContextHierarchyAttributesForTestClassHierarchyWithMultiLevelContextHierarchies() {
+ List<List<ContextConfigurationAttributes>> hierarchyAttributes = resolveContextHierarchyAttributes(TestClass3WithMultiLevelContextHierarchy.class);
+ assertEquals(3, hierarchyAttributes.size());
+
+ List<ContextConfigurationAttributes> configAttributesListClassLevel1 = hierarchyAttributes.get(0);
+ debugConfigAttributes(configAttributesListClassLevel1);
+ assertEquals(2, configAttributesListClassLevel1.size());
+ assertThat(configAttributesListClassLevel1.get(0).getLocations()[0], equalTo("1-A.xml"));
+ assertThat(configAttributesListClassLevel1.get(1).getLocations()[0], equalTo("1-B.xml"));
+
+ List<ContextConfigurationAttributes> configAttributesListClassLevel2 = hierarchyAttributes.get(1);
+ debugConfigAttributes(configAttributesListClassLevel2);
+ assertEquals(2, configAttributesListClassLevel2.size());
+ assertThat(configAttributesListClassLevel2.get(0).getLocations()[0], equalTo("2-A.xml"));
+ assertThat(configAttributesListClassLevel2.get(1).getLocations()[0], equalTo("2-B.xml"));
+
+ List<ContextConfigurationAttributes> configAttributesListClassLevel3 = hierarchyAttributes.get(2);
+ debugConfigAttributes(configAttributesListClassLevel3);
+ assertEquals(3, configAttributesListClassLevel3.size());
+ assertThat(configAttributesListClassLevel3.get(0).getLocations()[0], equalTo("3-A.xml"));
+ assertThat(configAttributesListClassLevel3.get(1).getLocations()[0], equalTo("3-B.xml"));
+ assertThat(configAttributesListClassLevel3.get(2).getLocations()[0], equalTo("3-C.xml"));
+ }
+
+ @Test
+ public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchies() {
+ Map<String, List<ContextConfigurationAttributes>> map = buildContextHierarchyMap(TestClass3WithMultiLevelContextHierarchy.class);
+
+ assertThat(map.size(), is(3));
+ assertThat(map.keySet(), hasItems("alpha", "beta", "gamma"));
+
+ List<ContextConfigurationAttributes> alphaConfig = map.get("alpha");
+ assertThat(alphaConfig.size(), is(3));
+ assertThat(alphaConfig.get(0).getLocations()[0], is("1-A.xml"));
+ assertThat(alphaConfig.get(1).getLocations()[0], is("2-A.xml"));
+ assertThat(alphaConfig.get(2).getLocations()[0], is("3-A.xml"));
+
+ List<ContextConfigurationAttributes> betaConfig = map.get("beta");
+ assertThat(betaConfig.size(), is(3));
+ assertThat(betaConfig.get(0).getLocations()[0], is("1-B.xml"));
+ assertThat(betaConfig.get(1).getLocations()[0], is("2-B.xml"));
+ assertThat(betaConfig.get(2).getLocations()[0], is("3-B.xml"));
+
+ List<ContextConfigurationAttributes> gammaConfig = map.get("gamma");
+ assertThat(gammaConfig.size(), is(1));
+ assertThat(gammaConfig.get(0).getLocations()[0], is("3-C.xml"));
+ }
+
+ @Test
+ public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchiesAndUnnamedConfig() {
+ Map<String, List<ContextConfigurationAttributes>> map = buildContextHierarchyMap(TestClass3WithMultiLevelContextHierarchyAndUnnamedConfig.class);
+
+ String level1 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 1;
+ String level2 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 2;
+ String level3 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 3;
+ String level4 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 4;
+ String level5 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 5;
+ String level6 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 6;
+ String level7 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 7;
+
+ assertThat(map.size(), is(7));
+ assertThat(map.keySet(), hasItems(level1, level2, level3, level4, level5, level6, level7));
+
+ List<ContextConfigurationAttributes> level1Config = map.get(level1);
+ assertThat(level1Config.size(), is(1));
+ assertThat(level1Config.get(0).getLocations()[0], is("1-A.xml"));
+
+ List<ContextConfigurationAttributes> level2Config = map.get(level2);
+ assertThat(level2Config.size(), is(1));
+ assertThat(level2Config.get(0).getLocations()[0], is("1-B.xml"));
+
+ List<ContextConfigurationAttributes> level3Config = map.get(level3);
+ assertThat(level3Config.size(), is(1));
+ assertThat(level3Config.get(0).getLocations()[0], is("2-A.xml"));
+
+ // ...
+
+ List<ContextConfigurationAttributes> level7Config = map.get(level7);
+ assertThat(level7Config.size(), is(1));
+ assertThat(level7Config.get(0).getLocations()[0], is("3-C.xml"));
+ }
+
+ @Test
+ public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchiesAndPartiallyNamedConfig() {
+ Map<String, List<ContextConfigurationAttributes>> map = buildContextHierarchyMap(TestClass2WithMultiLevelContextHierarchyAndPartiallyNamedConfig.class);
+
+ String level1 = "parent";
+ String level2 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 2;
+ String level3 = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + 3;
+
+ assertThat(map.size(), is(3));
+ assertThat(map.keySet(), hasItems(level1, level2, level3));
+ Iterator<String> levels = map.keySet().iterator();
+ assertThat(levels.next(), is(level1));
+ assertThat(levels.next(), is(level2));
+ assertThat(levels.next(), is(level3));
+
+ List<ContextConfigurationAttributes> level1Config = map.get(level1);
+ assertThat(level1Config.size(), is(2));
+ assertThat(level1Config.get(0).getLocations()[0], is("1-A.xml"));
+ assertThat(level1Config.get(1).getLocations()[0], is("2-A.xml"));
+
+ List<ContextConfigurationAttributes> level2Config = map.get(level2);
+ assertThat(level2Config.size(), is(1));
+ assertThat(level2Config.get(0).getLocations()[0], is("1-B.xml"));
+
+ List<ContextConfigurationAttributes> level3Config = map.get(level3);
+ assertThat(level3Config.size(), is(1));
+ assertThat(level3Config.get(0).getLocations()[0], is("2-C.xml"));
+ }
+
+ private void assertContextConfigEntriesAreNotUnique(Class<?> testClass) {
+ try {
+ buildContextHierarchyMap(testClass);
+ fail("Should throw an IllegalStateException");
+ }
+ catch (IllegalStateException e) {
+ String msg = String.format(
+ "The @ContextConfiguration elements configured via @ContextHierarchy in test class [%s] and its superclasses must define unique contexts per hierarchy level.",
+ testClass.getName());
+ assertEquals(msg, e.getMessage());
+ }
+ }
+
+ @Test
+ public void buildContextHierarchyMapForSingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig() {
+ assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig.class);
+ }
+
+ @Test
+ public void buildContextHierarchyMapForSingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig() {
+ assertContextConfigEntriesAreNotUnique(SingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig.class);
+ }
+
+ /**
+ * Used to reproduce bug reported in https://jira.springsource.org/browse/SPR-10997
+ */
+ @Test
+ public void buildContextHierarchyMapForTestClassHierarchyWithMultiLevelContextHierarchiesAndOverriddenInitializers() {
+ Map<String, List<ContextConfigurationAttributes>> map = buildContextHierarchyMap(TestClass2WithMultiLevelContextHierarchyWithOverriddenInitializers.class);
+
+ assertThat(map.size(), is(2));
+ assertThat(map.keySet(), hasItems("alpha", "beta"));
+
+ List<ContextConfigurationAttributes> alphaConfig = map.get("alpha");
+ assertThat(alphaConfig.size(), is(2));
+ assertThat(alphaConfig.get(0).getLocations().length, is(1));
+ assertThat(alphaConfig.get(0).getLocations()[0], is("1-A.xml"));
+ assertThat(alphaConfig.get(0).getInitializers().length, is(0));
+ assertThat(alphaConfig.get(1).getLocations().length, is(0));
+ assertThat(alphaConfig.get(1).getInitializers().length, is(1));
+ assertEquals(DummyApplicationContextInitializer.class, alphaConfig.get(1).getInitializers()[0]);
+
+ List<ContextConfigurationAttributes> betaConfig = map.get("beta");
+ assertThat(betaConfig.size(), is(2));
+ assertThat(betaConfig.get(0).getLocations().length, is(1));
+ assertThat(betaConfig.get(0).getLocations()[0], is("1-B.xml"));
+ assertThat(betaConfig.get(0).getInitializers().length, is(0));
+ assertThat(betaConfig.get(1).getLocations().length, is(0));
+ assertThat(betaConfig.get(1).getInitializers().length, is(1));
+ assertEquals(DummyApplicationContextInitializer.class, betaConfig.get(1).getInitializers()[0]);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void resolveConfigAttributesWithConflictingLocations() {
+ resolveContextConfigurationAttributes(ConflictingLocations.class);
+ }
+
+ @Test
+ public void resolveConfigAttributesWithBareAnnotations() {
+ List<ContextConfigurationAttributes> attributesList = resolveContextConfigurationAttributes(BareAnnotations.class);
+ assertNotNull(attributesList);
+ assertEquals(1, attributesList.size());
+ assertAttributes(attributesList.get(0), BareAnnotations.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY,
+ ContextLoader.class, true);
+ }
+
+ @Test
+ public void resolveConfigAttributesWithLocalAnnotationAndLocations() {
+ List<ContextConfigurationAttributes> attributesList = resolveContextConfigurationAttributes(LocationsFoo.class);
+ assertNotNull(attributesList);
+ assertEquals(1, attributesList.size());
+ assertLocationsFooAttributes(attributesList.get(0));
+ }
+
+ @Test
+ public void resolveConfigAttributesWithLocalAnnotationAndClasses() {
+ List<ContextConfigurationAttributes> attributesList = resolveContextConfigurationAttributes(ClassesFoo.class);
+ assertNotNull(attributesList);
+ assertEquals(1, attributesList.size());
+ assertClassesFooAttributes(attributesList.get(0));
+ }
+
+ @Test
+ public void resolveConfigAttributesWithLocalAndInheritedAnnotationsAndLocations() {
+ List<ContextConfigurationAttributes> attributesList = resolveContextConfigurationAttributes(LocationsBar.class);
+ assertNotNull(attributesList);
+ assertEquals(2, attributesList.size());
+ assertLocationsBarAttributes(attributesList.get(0));
+ assertLocationsFooAttributes(attributesList.get(1));
+ }
+
+ @Test
+ public void resolveConfigAttributesWithLocalAndInheritedAnnotationsAndClasses() {
+ List<ContextConfigurationAttributes> attributesList = resolveContextConfigurationAttributes(ClassesBar.class);
+ assertNotNull(attributesList);
+ assertEquals(2, attributesList.size());
+ assertClassesBarAttributes(attributesList.get(0));
+ assertClassesFooAttributes(attributesList.get(1));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void buildMergedConfigWithoutAnnotation() {
+ buildMergedContextConfiguration(Enigma.class, null, null);
+ }
+
+ @Test
+ public void buildMergedConfigWithBareAnnotations() {
+ Class<BareAnnotations> testClass = BareAnnotations.class;
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+
+ assertMergedConfig(
+ mergedConfig,
+ testClass,
+ new String[] { "classpath:/org/springframework/test/context/ContextLoaderUtilsTests$BareAnnotations-context.xml" },
+ EMPTY_CLASS_ARRAY, DelegatingSmartContextLoader.class);
+ }
+
+ @Test
+ public void buildMergedConfigWithLocalAnnotationAndLocations() {
+ Class<?> testClass = LocationsFoo.class;
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+
+ assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY,
+ DelegatingSmartContextLoader.class);
+ }
+
+ @Test
+ public void buildMergedConfigWithLocalAnnotationAndClasses() {
+ Class<?> testClass = ClassesFoo.class;
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class<?>[] { FooConfig.class },
+ DelegatingSmartContextLoader.class);
+ }
+
+ @Test
+ public void buildMergedConfigWithLocalAnnotationAndOverriddenContextLoaderAndLocations() {
+ Class<?> testClass = LocationsFoo.class;
+ Class<? extends ContextLoader> expectedContextLoaderClass = GenericPropertiesContextLoader.class;
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass,
+ expectedContextLoaderClass.getName(), null);
+
+ assertMergedConfig(mergedConfig, testClass, new String[] { "classpath:/foo.xml" }, EMPTY_CLASS_ARRAY,
+ expectedContextLoaderClass);
+ }
+
+ @Test
+ public void buildMergedConfigWithLocalAnnotationAndOverriddenContextLoaderAndClasses() {
+ Class<?> testClass = ClassesFoo.class;
+ Class<? extends ContextLoader> expectedContextLoaderClass = GenericPropertiesContextLoader.class;
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass,
+ expectedContextLoaderClass.getName(), null);
+
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, new Class<?>[] { FooConfig.class },
+ expectedContextLoaderClass);
+ }
+
+ @Test
+ public void buildMergedConfigWithLocalAndInheritedAnnotationsAndLocations() {
+ Class<?> testClass = LocationsBar.class;
+ String[] expectedLocations = new String[] { "/foo.xml", "/bar.xml" };
+
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY,
+ AnnotationConfigContextLoader.class);
+ }
+
+ @Test
+ public void buildMergedConfigWithLocalAndInheritedAnnotationsAndClasses() {
+ Class<?> testClass = ClassesBar.class;
+ Class<?>[] expectedClasses = new Class<?>[] { FooConfig.class, BarConfig.class };
+
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses,
+ AnnotationConfigContextLoader.class);
+ }
+
+ @Test
+ public void buildMergedConfigWithAnnotationsAndOverriddenLocations() {
+ Class<?> testClass = OverriddenLocationsBar.class;
+ String[] expectedLocations = new String[] { "/bar.xml" };
+
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ assertMergedConfig(mergedConfig, testClass, expectedLocations, EMPTY_CLASS_ARRAY,
+ AnnotationConfigContextLoader.class);
+ }
+
+ @Test
+ public void buildMergedConfigWithAnnotationsAndOverriddenClasses() {
+ Class<?> testClass = OverriddenClassesBar.class;
+ Class<?>[] expectedClasses = new Class<?>[] { BarConfig.class };
+
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses,
+ AnnotationConfigContextLoader.class);
+ }
+
+ @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);
+
+ MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass, null, null);
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
+ 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, null, null);
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
+ 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, null, null);
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
+ 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, null, null);
+ assertMergedConfig(mergedConfig, testClass, EMPTY_STRING_ARRAY, expectedClasses, expectedInitializerClasses,
+ DelegatingSmartContextLoader.class);
+ }
+
+ @Test
+ public void resolveActiveProfilesWithoutAnnotation() {
+ String[] profiles = resolveActiveProfiles(Enigma.class);
+ assertArrayEquals(EMPTY_STRING_ARRAY, profiles);
+ }
+
+ @Test
+ public void resolveActiveProfilesWithNoProfilesDeclared() {
+ String[] profiles = resolveActiveProfiles(BareAnnotations.class);
+ assertArrayEquals(EMPTY_STRING_ARRAY, profiles);
+ }
+
+ @Test
+ public void resolveActiveProfilesWithEmptyProfiles() {
+ String[] profiles = resolveActiveProfiles(EmptyProfiles.class);
+ assertArrayEquals(EMPTY_STRING_ARRAY, profiles);
+ }
+
+ @Test
+ public void resolveActiveProfilesWithDuplicatedProfiles() {
+ String[] profiles = resolveActiveProfiles(DuplicatedProfiles.class);
+ assertNotNull(profiles);
+ assertEquals(3, profiles.length);
+
+ List<String> list = Arrays.asList(profiles);
+ assertTrue(list.contains("foo"));
+ assertTrue(list.contains("bar"));
+ assertTrue(list.contains("baz"));
+ }
+
+ @Test
+ public void resolveActiveProfilesWithLocalAnnotation() {
+ String[] profiles = resolveActiveProfiles(LocationsFoo.class);
+ assertNotNull(profiles);
+ assertArrayEquals(new String[] { "foo" }, profiles);
+ }
+
+ @Test
+ public void resolveActiveProfilesWithInheritedAnnotationAndLocations() {
+ String[] profiles = resolveActiveProfiles(InheritedLocationsFoo.class);
+ assertNotNull(profiles);
+ assertArrayEquals(new String[] { "foo" }, profiles);
+ }
+
+ @Test
+ public void resolveActiveProfilesWithInheritedAnnotationAndClasses() {
+ String[] profiles = resolveActiveProfiles(InheritedClassesFoo.class);
+ assertNotNull(profiles);
+ assertArrayEquals(new String[] { "foo" }, profiles);
+ }
+
+ @Test
+ public void resolveActiveProfilesWithLocalAndInheritedAnnotations() {
+ String[] profiles = resolveActiveProfiles(LocationsBar.class);
+ assertNotNull(profiles);
+ assertEquals(2, profiles.length);
+
+ List<String> list = Arrays.asList(profiles);
+ assertTrue(list.contains("foo"));
+ assertTrue(list.contains("bar"));
+ }
+
+ @Test
+ public void resolveActiveProfilesWithOverriddenAnnotation() {
+ String[] profiles = resolveActiveProfiles(Animals.class);
+ assertNotNull(profiles);
+ assertEquals(2, profiles.length);
+
+ List<String> list = Arrays.asList(profiles);
+ assertTrue(list.contains("dog"));
+ assertTrue(list.contains("cat"));
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ private static class Enigma {
+ }
+
+ @ContextConfiguration(value = "x", locations = "y")
+ private static class ConflictingLocations {
+ }
+
+ @ContextConfiguration
+ @ActiveProfiles
+ private static class BareAnnotations {
+ }
+
+ @ActiveProfiles({ " ", "\t" })
+ private static class EmptyProfiles {
+ }
+
+ @ActiveProfiles({ "foo", "bar", " foo", "bar ", "baz" })
+ private static class DuplicatedProfiles {
+ }
+
+ @Configuration
+ private static class FooConfig {
+ }
+
+ @ContextConfiguration(locations = "/foo.xml", inheritLocations = false)
+ @ActiveProfiles(profiles = "foo")
+ private static class LocationsFoo {
+ }
+
+ @ContextConfiguration(classes = FooConfig.class, inheritLocations = false)
+ @ActiveProfiles(profiles = "foo")
+ private static class ClassesFoo {
+ }
+
+ private static class InheritedLocationsFoo extends LocationsFoo {
+ }
+
+ private static class InheritedClassesFoo extends ClassesFoo {
+ }
+
+ @Configuration
+ private static class BarConfig {
+ }
+
+ @ContextConfiguration(locations = "/bar.xml", inheritLocations = true, loader = AnnotationConfigContextLoader.class)
+ @ActiveProfiles("bar")
+ private static class LocationsBar extends LocationsFoo {
+ }
+
+ @ContextConfiguration(locations = "/bar.xml", inheritLocations = false, loader = AnnotationConfigContextLoader.class)
+ @ActiveProfiles("bar")
+ private static class OverriddenLocationsBar extends LocationsFoo {
+ }
+
+ @ContextConfiguration(classes = BarConfig.class, inheritLocations = true, loader = AnnotationConfigContextLoader.class)
+ @ActiveProfiles("bar")
+ private static class ClassesBar extends ClassesFoo {
+ }
+
+ @ContextConfiguration(classes = BarConfig.class, inheritLocations = false, loader = AnnotationConfigContextLoader.class)
+ @ActiveProfiles("bar")
+ private static class OverriddenClassesBar extends ClassesFoo {
+ }
+
+ @ActiveProfiles(profiles = { "dog", "cat" }, inheritProfiles = false)
+ private static class Animals extends LocationsBar {
+ }
+
+ private static class FooInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
+
+ @Override
+ public void initialize(GenericApplicationContext applicationContext) {
+ }
+ }
+
+ private static class BarInitializer implements ApplicationContextInitializer<GenericWebApplicationContext> {
+
+ @Override
+ public void initialize(GenericWebApplicationContext applicationContext) {
+ }
+ }
+
+ @ContextConfiguration(classes = FooConfig.class, initializers = FooInitializer.class)
+ private static class InitializersFoo {
+ }
+
+ @ContextConfiguration(classes = BarConfig.class, initializers = BarInitializer.class)
+ private static class InitializersBar extends InitializersFoo {
+ }
+
+ @ContextConfiguration(classes = BarConfig.class, initializers = BarInitializer.class, inheritInitializers = false)
+ private static class OverriddenInitializersBar extends InitializersFoo {
+ }
+
+ @ContextConfiguration(classes = BarConfig.class, inheritLocations = false, initializers = BarInitializer.class, inheritInitializers = false)
+ private static class OverriddenInitializersAndClassesBar extends InitializersFoo {
+ }
+
+ @ContextConfiguration("foo.xml")
+ @ContextHierarchy(@ContextConfiguration("bar.xml"))
+ private static class SingleTestClassWithContextConfigurationAndContextHierarchy {
+ }
+
+ @ContextHierarchy(@ContextConfiguration("A.xml"))
+ private static class SingleTestClassWithSingleLevelContextHierarchy {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration("A.xml"),//
+ @ContextConfiguration("B.xml"),//
+ @ContextConfiguration("C.xml") //
+ })
+ private static class SingleTestClassWithTripleLevelContextHierarchy {
+ }
+
+ @ContextHierarchy(@ContextConfiguration("one.xml"))
+ private static class TestClass1WithSingleLevelContextHierarchy {
+ }
+
+ @ContextHierarchy(@ContextConfiguration({ "two-A.xml", "two-B.xml" }))
+ private static class TestClass2WithSingleLevelContextHierarchy extends TestClass1WithSingleLevelContextHierarchy {
+ }
+
+ @ContextHierarchy(@ContextConfiguration("three.xml"))
+ private static class TestClass3WithSingleLevelContextHierarchy extends TestClass2WithSingleLevelContextHierarchy {
+ }
+
+ @ContextConfiguration("one.xml")
+ private static class TestClass1WithBareContextConfigurationInSuperclass {
+ }
+
+ @ContextHierarchy(@ContextConfiguration("two.xml"))
+ private static class TestClass2WithBareContextConfigurationInSuperclass extends
+ TestClass1WithBareContextConfigurationInSuperclass {
+ }
+
+ @ContextHierarchy(@ContextConfiguration("one.xml"))
+ private static class TestClass1WithBareContextConfigurationInSubclass {
+ }
+
+ @ContextConfiguration("two.xml")
+ private static class TestClass2WithBareContextConfigurationInSubclass extends
+ TestClass1WithBareContextConfigurationInSuperclass {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "1-A.xml", name = "alpha"),//
+ @ContextConfiguration(locations = "1-B.xml", name = "beta") //
+ })
+ private static class TestClass1WithMultiLevelContextHierarchy {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "2-A.xml", name = "alpha"),//
+ @ContextConfiguration(locations = "2-B.xml", name = "beta") //
+ })
+ private static class TestClass2WithMultiLevelContextHierarchy extends TestClass1WithMultiLevelContextHierarchy {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "3-A.xml", name = "alpha"),//
+ @ContextConfiguration(locations = "3-B.xml", name = "beta"),//
+ @ContextConfiguration(locations = "3-C.xml", name = "gamma") //
+ })
+ private static class TestClass3WithMultiLevelContextHierarchy extends TestClass2WithMultiLevelContextHierarchy {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "1-A.xml"),//
+ @ContextConfiguration(locations = "1-B.xml") //
+ })
+ private static class TestClass1WithMultiLevelContextHierarchyAndUnnamedConfig {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "2-A.xml"),//
+ @ContextConfiguration(locations = "2-B.xml") //
+ })
+ private static class TestClass2WithMultiLevelContextHierarchyAndUnnamedConfig extends
+ TestClass1WithMultiLevelContextHierarchyAndUnnamedConfig {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "3-A.xml"),//
+ @ContextConfiguration(locations = "3-B.xml"),//
+ @ContextConfiguration(locations = "3-C.xml") //
+ })
+ private static class TestClass3WithMultiLevelContextHierarchyAndUnnamedConfig extends
+ TestClass2WithMultiLevelContextHierarchyAndUnnamedConfig {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "1-A.xml", name = "parent"),//
+ @ContextConfiguration(locations = "1-B.xml") //
+ })
+ private static class TestClass1WithMultiLevelContextHierarchyAndPartiallyNamedConfig {
+ }
+
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(locations = "2-A.xml", name = "parent"),//
+ @ContextConfiguration(locations = "2-C.xml") //
+ })
+ private static class TestClass2WithMultiLevelContextHierarchyAndPartiallyNamedConfig extends
+ TestClass1WithMultiLevelContextHierarchyAndPartiallyNamedConfig {
+ }
+
+ @ContextHierarchy({
+ //
+ @ContextConfiguration,//
+ @ContextConfiguration //
+ })
+ private static class SingleTestClassWithMultiLevelContextHierarchyWithEmptyContextConfig {
+ }
+
+ @ContextHierarchy({
+ //
+ @ContextConfiguration("foo.xml"),//
+ @ContextConfiguration(classes = BarConfig.class),// duplicate!
+ @ContextConfiguration("baz.xml"),//
+ @ContextConfiguration(classes = BarConfig.class),// duplicate!
+ @ContextConfiguration(loader = AnnotationConfigContextLoader.class) //
+ })
+ private static class SingleTestClassWithMultiLevelContextHierarchyWithDuplicatedContextConfig {
+ }
+
+ /**
+ * Used to reproduce bug reported in https://jira.springsource.org/browse/SPR-10997
+ */
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(name = "alpha", locations = "1-A.xml"),//
+ @ContextConfiguration(name = "beta", locations = "1-B.xml") //
+ })
+ private static class TestClass1WithMultiLevelContextHierarchyWithUniqueContextConfig {
+ }
+
+ /**
+ * Used to reproduce bug reported in https://jira.springsource.org/browse/SPR-10997
+ */
+ @ContextHierarchy({//
+ //
+ @ContextConfiguration(name = "alpha", initializers = DummyApplicationContextInitializer.class),//
+ @ContextConfiguration(name = "beta", initializers = DummyApplicationContextInitializer.class) //
+ })
+ private static class TestClass2WithMultiLevelContextHierarchyWithOverriddenInitializers extends
+ TestClass1WithMultiLevelContextHierarchyWithUniqueContextConfig {
+ }
+
+ /**
+ * Used to reproduce bug reported in https://jira.springsource.org/browse/SPR-10997
+ */
+ private static class DummyApplicationContextInitializer implements
+ ApplicationContextInitializer<ConfigurableApplicationContext> {
+
+ @Override
+ public void initialize(ConfigurableApplicationContext applicationContext) {
+ /* no-op */
+ }
+
+ }
+
+}
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
new file mode 100644
index 00000000..863fd9da
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context;
+
+import static org.junit.Assert.*;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+import org.springframework.test.context.support.GenericXmlContextLoader;
+
+/**
+ * Unit tests for {@link MergedContextConfiguration}.
+ *
+ * <p>These tests primarily exist to ensure that {@code MergedContextConfiguration}
+ * can safely be used as the cache key for {@link ContextCache}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+public class MergedContextConfigurationTests {
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
+
+ private final GenericXmlContextLoader loader = new GenericXmlContextLoader();
+
+
+ @Test
+ public void hashCodeWithNulls() {
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(null, null, null, null, null);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(null, null, null, null, null);
+ assertTrue(mergedConfig1.hashCode() > 0);
+ assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithNullArrays() {
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), null, null, null, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), null, null, null, loader);
+ assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithEmptyArrays() {
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithEmptyArraysAndDifferentLoaders() {
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, new AnnotationConfigContextLoader());
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithSameLocations() {
+ String[] locations = new String[] { "foo", "bar}" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), locations,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), locations,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithDifferentLocations() {
+ String[] locations1 = new String[] { "foo", "bar}" };
+ String[] locations2 = new String[] { "baz", "quux}" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), locations1,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), locations2,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithSameConfigClasses() {
+ Class<?>[] classes = new Class<?>[] { String.class, Integer.class };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ classes, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ classes, EMPTY_STRING_ARRAY, loader);
+ assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithDifferentConfigClasses() {
+ Class<?>[] classes1 = new Class<?>[] { String.class, Integer.class };
+ Class<?>[] classes2 = new Class<?>[] { Boolean.class, Number.class };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ classes1, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ classes2, EMPTY_STRING_ARRAY, loader);
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithSameProfiles() {
+ String[] activeProfiles = new String[] { "catbert", "dogbert" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles, loader);
+ assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithSameProfilesReversed() {
+ String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
+ String[] activeProfiles2 = new String[] { "dogbert", "catbert" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles1, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles2, loader);
+ assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithSameDuplicateProfiles() {
+ String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
+ String[] activeProfiles2 = new String[] { "catbert", "dogbert", "catbert", "dogbert", "catbert" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles1, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles2, loader);
+ assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithDifferentProfiles() {
+ String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
+ String[] activeProfiles2 = new String[] { "X", "Y" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles1, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles2, loader);
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithSameInitializers() {
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 = //
+ new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
+ initializerClasses1.add(FooInitializer.class);
+ initializerClasses1.add(BarInitializer.class);
+
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 = //
+ new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
+ initializerClasses2.add(BarInitializer.class);
+ initializerClasses2.add(FooInitializer.class);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
+ assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void hashCodeWithDifferentInitializers() {
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 = //
+ new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
+ initializerClasses1.add(FooInitializer.class);
+
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 = //
+ new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
+ initializerClasses2.add(BarInitializer.class);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ /**
+ * @since 3.2.2
+ */
+ @Test
+ public void hashCodeWithSameParent() {
+ MergedContextConfiguration parent = new MergedContextConfiguration(getClass(), new String[] { "foo", "bar}" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
+ assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ /**
+ * @since 3.2.2
+ */
+ @Test
+ public void hashCodeWithDifferentParents() {
+ MergedContextConfiguration parent1 = new MergedContextConfiguration(getClass(), new String[] { "foo", "bar}" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration parent2 = new MergedContextConfiguration(getClass(), new String[] { "baz", "quux" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent1);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent2);
+ assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
+ }
+
+ @Test
+ public void equalsBasics() {
+ MergedContextConfiguration mergedConfig = new MergedContextConfiguration(null, null, null, null, null);
+ assertEquals(mergedConfig, mergedConfig);
+ assertNotEquals(mergedConfig, null);
+ assertNotEquals(mergedConfig, new Integer(1));
+ }
+
+ @Test
+ public void equalsWithNulls() {
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(null, null, null, null, null);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(null, null, null, null, null);
+ assertEquals(mergedConfig1, mergedConfig2);
+ }
+
+ @Test
+ public void equalsWithNullArrays() {
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), null, null, null, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), null, null, null, loader);
+ assertEquals(mergedConfig1, mergedConfig2);
+ }
+
+ @Test
+ public void equalsWithEmptyArrays() {
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ assertEquals(mergedConfig1, mergedConfig2);
+ }
+
+ @Test
+ public void equalsWithEmptyArraysAndDifferentLoaders() {
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, new AnnotationConfigContextLoader());
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
+ }
+
+ @Test
+ public void equalsWithSameLocations() {
+ String[] locations = new String[] { "foo", "bar}" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), locations,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), locations,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ assertEquals(mergedConfig1, mergedConfig2);
+ }
+
+ @Test
+ public void equalsWithDifferentLocations() {
+ String[] locations1 = new String[] { "foo", "bar}" };
+ String[] locations2 = new String[] { "baz", "quux}" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), locations1,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), locations2,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
+ }
+
+ @Test
+ public void equalsWithSameConfigClasses() {
+ Class<?>[] classes = new Class<?>[] { String.class, Integer.class };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ classes, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ classes, EMPTY_STRING_ARRAY, loader);
+ assertEquals(mergedConfig1, mergedConfig2);
+ }
+
+ @Test
+ public void equalsWithDifferentConfigClasses() {
+ Class<?>[] classes1 = new Class<?>[] { String.class, Integer.class };
+ Class<?>[] classes2 = new Class<?>[] { Boolean.class, Number.class };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ classes1, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ classes2, EMPTY_STRING_ARRAY, loader);
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
+ }
+
+ @Test
+ public void equalsWithSameProfiles() {
+ String[] activeProfiles = new String[] { "catbert", "dogbert" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles, loader);
+ assertEquals(mergedConfig1, mergedConfig2);
+ }
+
+ @Test
+ public void equalsWithSameProfilesReversed() {
+ String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
+ String[] activeProfiles2 = new String[] { "dogbert", "catbert" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles1, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles2, loader);
+ assertEquals(mergedConfig1, mergedConfig2);
+ }
+
+ @Test
+ public void equalsWithSameDuplicateProfiles() {
+ String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
+ String[] activeProfiles2 = new String[] { "catbert", "dogbert", "catbert", "dogbert", "catbert" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles1, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles2, loader);
+ assertEquals(mergedConfig1, mergedConfig2);
+ }
+
+ @Test
+ public void equalsWithDifferentProfiles() {
+ String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
+ String[] activeProfiles2 = new String[] { "X", "Y" };
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles1, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, activeProfiles2, loader);
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
+ }
+
+ @Test
+ public void equalsWithSameInitializers() {
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 = //
+ new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
+ initializerClasses1.add(FooInitializer.class);
+ initializerClasses1.add(BarInitializer.class);
+
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 = //
+ new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
+ initializerClasses2.add(BarInitializer.class);
+ initializerClasses2.add(FooInitializer.class);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
+ assertEquals(mergedConfig1, mergedConfig2);
+ }
+
+ @Test
+ public void equalsWithDifferentInitializers() {
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 = //
+ new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
+ initializerClasses1.add(FooInitializer.class);
+
+ Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 = //
+ new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
+ initializerClasses2.add(BarInitializer.class);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
+ }
+
+ /**
+ * @since 3.2.2
+ */
+ @Test
+ public void equalsWithSameParent() {
+ MergedContextConfiguration parent = new MergedContextConfiguration(getClass(), new String[] { "foo", "bar}" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
+ assertEquals(mergedConfig1, mergedConfig2);
+ assertEquals(mergedConfig2, mergedConfig1);
+ }
+
+ /**
+ * @since 3.2.2
+ */
+ @Test
+ public void equalsWithDifferentParents() {
+ MergedContextConfiguration parent1 = new MergedContextConfiguration(getClass(), new String[] { "foo", "bar}" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ MergedContextConfiguration parent2 = new MergedContextConfiguration(getClass(), new String[] { "baz", "quux" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+
+ MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent1);
+ MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent2);
+ assertNotEquals(mergedConfig1, mergedConfig2);
+ assertNotEquals(mergedConfig2, mergedConfig1);
+ }
+
+
+ private static class FooInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
+
+ @Override
+ public void initialize(GenericApplicationContext applicationContext) {
+ }
+ }
+
+ private static class BarInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
+
+ @Override
+ public void initialize(GenericApplicationContext applicationContext) {
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java
new file mode 100644
index 00000000..07f73831
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+import java.util.Comparator;
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.SpringRunnerContextCacheTests.OrderedMethodsSpringJUnit4ClassRunner;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
+
+/**
+ * JUnit 4 based unit test which verifies correct {@link ContextCache
+ * application context caching} in conjunction with the
+ * {@link SpringJUnit4ClassRunner} and the {@link DirtiesContext
+ * &#064;DirtiesContext} annotation at the method level.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see ContextCacheTests
+ */
+@RunWith(OrderedMethodsSpringJUnit4ClassRunner.class)
+@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
+@ContextConfiguration("junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml")
+public class SpringRunnerContextCacheTests {
+
+ private static ApplicationContext dirtiedApplicationContext;
+
+ @Autowired
+ protected ApplicationContext applicationContext;
+
+
+ /**
+ * Asserts the statistics of the context cache in {@link TestContextManager}.
+ *
+ * @param usageScenario the scenario in which the statistics are used
+ * @param expectedSize the expected number of contexts in the cache
+ * @param expectedHitCount the expected hit count
+ * @param expectedMissCount the expected miss count
+ */
+ private static final void assertContextCacheStatistics(String usageScenario, int expectedSize,
+ int expectedHitCount, int expectedMissCount) {
+ assertContextCacheStatistics(TestContextManager.contextCache, usageScenario, expectedSize, expectedHitCount,
+ expectedMissCount);
+ }
+
+ /**
+ * Asserts the statistics of the supplied context cache.
+ *
+ * @param contextCache the cache to assert against
+ * @param usageScenario the scenario in which the statistics are used
+ * @param expectedSize the expected number of contexts in the cache
+ * @param expectedHitCount the expected hit count
+ * @param expectedMissCount the expected miss count
+ */
+ public static final void assertContextCacheStatistics(ContextCache contextCache, String usageScenario,
+ int expectedSize, int expectedHitCount, int expectedMissCount) {
+
+ assertEquals("Verifying number of contexts in cache (" + usageScenario + ").", expectedSize,
+ contextCache.size());
+ assertEquals("Verifying number of cache hits (" + usageScenario + ").", expectedHitCount,
+ contextCache.getHitCount());
+ assertEquals("Verifying number of cache misses (" + usageScenario + ").", expectedMissCount,
+ contextCache.getMissCount());
+ }
+
+ @BeforeClass
+ public static void verifyInitialCacheState() {
+ dirtiedApplicationContext = null;
+ ContextCache contextCache = TestContextManager.contextCache;
+ contextCache.clear();
+ contextCache.clearStatistics();
+ assertContextCacheStatistics("BeforeClass", 0, 0, 0);
+ }
+
+ @AfterClass
+ public static void verifyFinalCacheState() {
+ assertContextCacheStatistics("AfterClass", 1, 1, 2);
+ }
+
+ @Test
+ @DirtiesContext
+ public void dirtyContext() {
+ assertContextCacheStatistics("dirtyContext()", 1, 0, 1);
+ assertNotNull("The application context should have been autowired.", this.applicationContext);
+ SpringRunnerContextCacheTests.dirtiedApplicationContext = this.applicationContext;
+ }
+
+ @Test
+ public void verifyContextDirty() {
+ assertContextCacheStatistics("verifyContextWasDirtied()", 1, 0, 2);
+ assertNotNull("The application context should have been autowired.", this.applicationContext);
+ assertNotSame("The application context should have been 'dirtied'.",
+ SpringRunnerContextCacheTests.dirtiedApplicationContext, this.applicationContext);
+ SpringRunnerContextCacheTests.dirtiedApplicationContext = this.applicationContext;
+ }
+
+ @Test
+ public void verifyContextNotDirty() {
+ assertContextCacheStatistics("verifyContextWasNotDirtied()", 1, 1, 2);
+ assertNotNull("The application context should have been autowired.", this.applicationContext);
+ assertSame("The application context should NOT have been 'dirtied'.",
+ SpringRunnerContextCacheTests.dirtiedApplicationContext, this.applicationContext);
+ }
+
+
+ /**
+ * @since 3.2
+ */
+ public static class OrderedMethodsSpringJUnit4ClassRunner extends SpringJUnit4ClassRunner {
+
+ public OrderedMethodsSpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
+ super(clazz);
+ }
+
+ @Override
+ protected List<FrameworkMethod> computeTestMethods() {
+ List<FrameworkMethod> testMethods = super.computeTestMethods();
+
+ java.util.Collections.sort(testMethods, new Comparator<FrameworkMethod>() {
+
+ @Override
+ public int compare(FrameworkMethod method1, FrameworkMethod method2) {
+ return method1.getName().compareTo(method2.getName());
+ }
+ });
+
+ return testMethods;
+ }
+
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java
new file mode 100644
index 00000000..97f303b8
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/TestContextManagerTests.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.springframework.core.style.ToStringCreator;
+import org.springframework.test.context.support.AbstractTestExecutionListener;
+
+/**
+ * JUnit 4 based unit test for {@link TestContextManager}, which verifies proper
+ * <em>execution order</em> of registered {@link TestExecutionListener
+ * TestExecutionListeners}.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class TestContextManagerTests {
+
+ private static final String FIRST = "veni";
+ private static final String SECOND = "vidi";
+ private static final String THIRD = "vici";
+
+ private static final List<String> afterTestMethodCalls = new ArrayList<String>();
+ private static final List<String> beforeTestMethodCalls = new ArrayList<String>();
+
+ protected static final Log logger = LogFactory.getLog(TestContextManagerTests.class);
+
+ private final TestContextManager testContextManager = new TestContextManager(ExampleTestCase.class);
+
+
+ private Method getTestMethod() throws NoSuchMethodException {
+ return ExampleTestCase.class.getDeclaredMethod("exampleTestMethod", (Class<?>[]) null);
+ }
+
+ /**
+ * Asserts the <em>execution order</em> of 'before' and 'after' test method
+ * calls on {@link TestExecutionListener listeners} registered for the
+ * configured {@link TestContextManager}.
+ *
+ * @see #beforeTestMethodCalls
+ * @see #afterTestMethodCalls
+ */
+ private static void assertExecutionOrder(List<String> expectedBeforeTestMethodCalls,
+ List<String> expectedAfterTestMethodCalls, final String usageContext) {
+
+ if (expectedBeforeTestMethodCalls == null) {
+ expectedBeforeTestMethodCalls = new ArrayList<String>();
+ }
+ if (expectedAfterTestMethodCalls == null) {
+ expectedAfterTestMethodCalls = new ArrayList<String>();
+ }
+
+ if (logger.isDebugEnabled()) {
+ for (String listenerName : beforeTestMethodCalls) {
+ logger.debug("'before' listener [" + listenerName + "] (" + usageContext + ").");
+ }
+ for (String listenerName : afterTestMethodCalls) {
+ logger.debug("'after' listener [" + listenerName + "] (" + usageContext + ").");
+ }
+ }
+
+ assertTrue("Verifying execution order of 'before' listeners' (" + usageContext + ").",
+ expectedBeforeTestMethodCalls.equals(beforeTestMethodCalls));
+ assertTrue("Verifying execution order of 'after' listeners' (" + usageContext + ").",
+ expectedAfterTestMethodCalls.equals(afterTestMethodCalls));
+ }
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ beforeTestMethodCalls.clear();
+ afterTestMethodCalls.clear();
+ assertExecutionOrder(null, null, "BeforeClass");
+ }
+
+ /**
+ * Verifies the expected {@link TestExecutionListener}
+ * <em>execution order</em> after all test methods have completed.
+ */
+ @AfterClass
+ public static void verifyListenerExecutionOrderAfterClass() throws Exception {
+ assertExecutionOrder(Arrays.<String> asList(FIRST, SECOND, THIRD),
+ Arrays.<String> asList(THIRD, SECOND, FIRST), "AfterClass");
+ }
+
+ @Before
+ public void setUpTestContextManager() throws Throwable {
+ assertEquals("Verifying the number of registered TestExecutionListeners.", 3,
+ this.testContextManager.getTestExecutionListeners().size());
+
+ this.testContextManager.beforeTestMethod(new ExampleTestCase(), getTestMethod());
+ }
+
+ /**
+ * Verifies the expected {@link TestExecutionListener}
+ * <em>execution order</em> within a test method.
+ *
+ * @see #verifyListenerExecutionOrderAfterClass()
+ */
+ @Test
+ public void verifyListenerExecutionOrderWithinTestMethod() {
+ assertExecutionOrder(Arrays.<String> asList(FIRST, SECOND, THIRD), null, "Test");
+ }
+
+ @After
+ public void tearDownTestContextManager() throws Throwable {
+ this.testContextManager.afterTestMethod(new ExampleTestCase(), getTestMethod(), null);
+ }
+
+
+ @TestExecutionListeners({ FirstTel.class, SecondTel.class, ThirdTel.class })
+ private static class ExampleTestCase {
+
+ @SuppressWarnings("unused")
+ public void exampleTestMethod() {
+ assertTrue(true);
+ }
+ }
+
+ private static class NamedTestExecutionListener extends AbstractTestExecutionListener {
+
+ private final String name;
+
+
+ public NamedTestExecutionListener(final String name) {
+ this.name = name;
+ }
+
+ @Override
+ public void beforeTestMethod(final TestContext testContext) {
+ beforeTestMethodCalls.add(this.name);
+ }
+
+ @Override
+ public void afterTestMethod(final TestContext testContext) {
+ afterTestMethodCalls.add(this.name);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringCreator(this).append("name", this.name).toString();
+ }
+ }
+
+ private static class FirstTel extends NamedTestExecutionListener {
+
+ public FirstTel() {
+ super(FIRST);
+ }
+ }
+
+ private static class SecondTel extends NamedTestExecutionListener {
+
+ public SecondTel() {
+ super(SECOND);
+ }
+ }
+
+ private static class ThirdTel extends NamedTestExecutionListener {
+
+ public ThirdTel() {
+ super(THIRD);
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..5596e6ea
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/TestExecutionListenersTests.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.springframework.test.context.support.AbstractTestExecutionListener;
+
+/**
+ * <p>
+ * JUnit 4 based unit test for the {@link TestExecutionListeners
+ * &#064;TestExecutionListeners} annotation, which verifies:
+ * </p>
+ * <ul>
+ * <li>Proper registering of {@link TestExecutionListener listeners} in
+ * conjunction with a {@link TestContextManager}</li>
+ * <li><em>Inherited</em> functionality proposed in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3896"
+ * target="_blank">SPR-3896</a></li>
+ * </ul>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class TestExecutionListenersTests {
+
+ @Test
+ public void verifyNumDefaultListenersRegistered() throws Exception {
+ TestContextManager testContextManager = new TestContextManager(DefaultListenersExampleTestCase.class);
+ assertEquals("Verifying the number of registered TestExecutionListeners for DefaultListenersExampleTest.", 4,
+ testContextManager.getTestExecutionListeners().size());
+ }
+
+ @Test
+ public void verifyNumNonInheritedDefaultListenersRegistered() throws Exception {
+ TestContextManager testContextManager = new TestContextManager(
+ NonInheritedDefaultListenersExampleTestCase.class);
+ assertEquals(
+ "Verifying the number of registered TestExecutionListeners for NonInheritedDefaultListenersExampleTest.",
+ 1, testContextManager.getTestExecutionListeners().size());
+ }
+
+ @Test
+ public void verifyNumInheritedDefaultListenersRegistered() throws Exception {
+ TestContextManager testContextManager = new TestContextManager(InheritedDefaultListenersExampleTestCase.class);
+ assertEquals(
+ "Verifying the number of registered TestExecutionListeners for InheritedDefaultListenersExampleTest.", 1,
+ testContextManager.getTestExecutionListeners().size());
+
+ testContextManager = new TestContextManager(SubInheritedDefaultListenersExampleTestCase.class);
+ assertEquals(
+ "Verifying the number of registered TestExecutionListeners for SubInheritedDefaultListenersExampleTest.",
+ 1, testContextManager.getTestExecutionListeners().size());
+
+ testContextManager = new TestContextManager(SubSubInheritedDefaultListenersExampleTestCase.class);
+ assertEquals(
+ "Verifying the number of registered TestExecutionListeners for SubSubInheritedDefaultListenersExampleTest.",
+ 2, testContextManager.getTestExecutionListeners().size());
+ }
+
+ @Test
+ public void verifyNumListenersRegistered() throws Exception {
+ TestContextManager testContextManager = new TestContextManager(ExampleTestCase.class);
+ assertEquals("Verifying the number of registered TestExecutionListeners for ExampleTest.", 3,
+ testContextManager.getTestExecutionListeners().size());
+ }
+
+ @Test
+ public void verifyNumNonInheritedListenersRegistered() throws Exception {
+ TestContextManager testContextManager = new TestContextManager(NonInheritedListenersExampleTestCase.class);
+ assertEquals("Verifying the number of registered TestExecutionListeners for NonInheritedListenersExampleTest.",
+ 1, testContextManager.getTestExecutionListeners().size());
+ }
+
+ @Test
+ public void verifyNumInheritedListenersRegistered() throws Exception {
+ TestContextManager testContextManager = new TestContextManager(InheritedListenersExampleTestCase.class);
+ assertEquals("Verifying the number of registered TestExecutionListeners for InheritedListenersExampleTest.", 4,
+ testContextManager.getTestExecutionListeners().size());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void verifyDuplicateListenersConfigThrowsException() throws Exception {
+ new TestContextManager(DuplicateListenersConfigExampleTestCase.class);
+ }
+
+
+ static class DefaultListenersExampleTestCase {
+ }
+
+ @TestExecutionListeners(QuuxTestExecutionListener.class)
+ static class InheritedDefaultListenersExampleTestCase extends DefaultListenersExampleTestCase {
+ }
+
+ static class SubInheritedDefaultListenersExampleTestCase extends InheritedDefaultListenersExampleTestCase {
+ }
+
+ @TestExecutionListeners(EnigmaTestExecutionListener.class)
+ static class SubSubInheritedDefaultListenersExampleTestCase extends SubInheritedDefaultListenersExampleTestCase {
+ }
+
+ @TestExecutionListeners(listeners = { QuuxTestExecutionListener.class }, inheritListeners = false)
+ static class NonInheritedDefaultListenersExampleTestCase extends InheritedDefaultListenersExampleTestCase {
+ }
+
+ @TestExecutionListeners( { FooTestExecutionListener.class, BarTestExecutionListener.class,
+ BazTestExecutionListener.class })
+ static class ExampleTestCase {
+ }
+
+ @TestExecutionListeners(QuuxTestExecutionListener.class)
+ static class InheritedListenersExampleTestCase extends ExampleTestCase {
+ }
+
+ @TestExecutionListeners(listeners = QuuxTestExecutionListener.class, inheritListeners = false)
+ static class NonInheritedListenersExampleTestCase extends InheritedListenersExampleTestCase {
+ }
+
+ @TestExecutionListeners(listeners = FooTestExecutionListener.class, value = BarTestExecutionListener.class)
+ static class DuplicateListenersConfigExampleTestCase {
+ }
+
+ static class FooTestExecutionListener extends AbstractTestExecutionListener {
+ }
+
+ static class BarTestExecutionListener extends AbstractTestExecutionListener {
+ }
+
+ static class BazTestExecutionListener extends AbstractTestExecutionListener {
+ }
+
+ static class QuuxTestExecutionListener extends AbstractTestExecutionListener {
+ }
+
+ static class EnigmaTestExecutionListener extends AbstractTestExecutionListener {
+ }
+
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests-context.properties b/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests-context.properties
new file mode 100644
index 00000000..45d36076
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests-context.properties
@@ -0,0 +1,5 @@
+dog.(class)=org.springframework.tests.sample.beans.Pet
+dog.$0=Fido
+
+testString2.(class)=java.lang.String
+testString2.$0=Test String #2
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests.java
new file mode 100644
index 00000000..3a976747
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.test.context.junit4.PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests;
+
+/**
+ * Integration tests which verify that the same custom {@link ContextLoader} can
+ * be used at all levels within a test class hierarchy when the
+ * {@code loader} is <i>inherited</i> (i.e., not explicitly declared) via
+ * {@link ContextConfiguration &#064;ContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ * @see PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests
+ * @see ContextConfigurationWithPropertiesExtendingPropertiesTests
+ */
+@ContextConfiguration
+public class ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests extends
+ PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests {
+
+ @Autowired
+ private Pet dog;
+
+ @Autowired
+ private String testString2;
+
+
+ @Test
+ public void verifyExtendedAnnotationAutowiredFields() {
+ assertNotNull("The dog field should have been autowired.", this.dog);
+ assertEquals("Fido", this.dog.getName());
+
+ assertNotNull("The testString2 field should have been autowired.", this.testString2);
+ assertEquals("Test String #2", this.testString2);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesTests-context.properties b/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesTests-context.properties
new file mode 100644
index 00000000..45d36076
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesTests-context.properties
@@ -0,0 +1,5 @@
+dog.(class)=org.springframework.tests.sample.beans.Pet
+dog.$0=Fido
+
+testString2.(class)=java.lang.String
+testString2.$0=Test String #2
diff --git a/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesTests.java b/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesTests.java
new file mode 100644
index 00000000..77f079ae
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/configuration/ContextConfigurationWithPropertiesExtendingPropertiesTests.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.test.context.junit4.PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests;
+import org.springframework.test.context.support.GenericPropertiesContextLoader;
+
+/**
+ * Integration tests which verify that the same custom {@link ContextLoader} can
+ * be used at all levels within a test class hierarchy when the
+ * {@code loader} is explicitly declared via {@link ContextConfiguration
+ * &#064;ContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ * @see PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests
+ * @see ContextConfigurationWithPropertiesExtendingPropertiesAndInheritedLoaderTests
+ */
+@ContextConfiguration(loader = GenericPropertiesContextLoader.class)
+public class ContextConfigurationWithPropertiesExtendingPropertiesTests extends
+ PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests {
+
+ @Autowired
+ private Pet dog;
+
+ @Autowired
+ private String testString2;
+
+
+ @Test
+ public void verifyExtendedAnnotationAutowiredFields() {
+ assertNotNull("The dog field should have been autowired.", this.dog);
+ assertEquals("Fido", this.dog.getName());
+
+ assertNotNull("The testString2 field should have been autowired.", this.testString2);
+ assertEquals("Test String #2", this.testString2);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests-context.xml
new file mode 100644
index 00000000..38def23c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests-context.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="properties"
+ class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+ <property name="properties">
+ <props>
+ <prop key="user.name">Dave</prop>
+ <prop key="username">Andy</prop>
+ </props>
+ </property>
+ </bean>
+
+ <!-- spr5906 -->
+
+ <bean id="derived"
+ class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+ <property name="properties">
+ <props>
+ <prop key="user.name">#{properties['user.name']}</prop>
+ <prop key="username">#{properties['username']}</prop>
+ <prop key="username.no.quotes">#{properties[username]}</prop>
+ <prop key="username.no.brackets">#{properties.username}</prop>
+ <prop key="#{properties['user.name']}">exists</prop>
+ <prop key="#{properties.username}">exists also</prop>
+ </props>
+ </property>
+ </bean>
+
+ <!-- spr5847 -->
+
+ <bean id="andy"
+ class="org.springframework.test.context.expression.ExpressionUsageTests$Foo">
+ <property name="name" value="#{properties.username}" />
+ </bean>
+
+ <bean id="andy2"
+ class="org.springframework.test.context.expression.ExpressionUsageTests$Foo">
+ <property name="name" value="#{properties.username }" /><!-- space in expression -->
+ </bean>
+
+</beans> \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests.java b/spring-test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests.java
new file mode 100644
index 00000000..94d44e0e
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/expression/ExpressionUsageTests.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2002-2012 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.expression;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Properties;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Andy Clement
+ * @author Dave Syer
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class ExpressionUsageTests {
+
+ @Autowired
+ @Qualifier("derived")
+ private Properties props;
+
+ @Autowired
+ @Qualifier("andy2")
+ private Foo andy2;
+
+ @Autowired
+ @Qualifier("andy")
+ private Foo andy;
+
+
+ @Test
+ public void testSpr5906() throws Exception {
+ // verify the property values have been evaluated as expressions
+ assertEquals("Dave", props.getProperty("user.name"));
+ assertEquals("Andy", props.getProperty("username"));
+
+ // verify the property keys have been evaluated as expressions
+ assertEquals("exists", props.getProperty("Dave"));
+ assertEquals("exists also", props.getProperty("Andy"));
+ }
+
+ @Test
+ public void testSpr5847() throws Exception {
+ assertEquals("Andy", andy2.getName());
+ assertEquals("Andy", andy.getName());
+ }
+
+
+ public static class Foo {
+
+ private String name;
+
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelOneTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelOneTests.java
new file mode 100644
index 00000000..716228c7
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelOneTests.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy({
+//
+ @ContextConfiguration(name = "parent", classes = ClassHierarchyWithMergedConfigLevelOneTests.AppConfig.class),//
+ @ContextConfiguration(name = "child", classes = ClassHierarchyWithMergedConfigLevelOneTests.UserConfig.class) //
+})
+public class ClassHierarchyWithMergedConfigLevelOneTests {
+
+ @Configuration
+ static class AppConfig {
+
+ @Bean
+ public String parent() {
+ return "parent";
+ }
+ }
+
+ @Configuration
+ static class UserConfig {
+
+ @Autowired
+ private AppConfig appConfig;
+
+
+ @Bean
+ public String user() {
+ return appConfig.parent() + " + user";
+ }
+
+ @Bean
+ public String beanFromUserConfig() {
+ return "from UserConfig";
+ }
+ }
+
+
+ @Autowired
+ protected String parent;
+
+ @Autowired
+ protected String user;
+
+ @Autowired(required = false)
+ @Qualifier("beanFromUserConfig")
+ protected String beanFromUserConfig;
+
+ @Autowired
+ protected ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("parent", parent);
+ assertEquals("parent + user", user);
+ assertEquals("from UserConfig", beanFromUserConfig);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelTwoTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelTwoTests.java
new file mode 100644
index 00000000..42a63413
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithMergedConfigLevelTwoTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+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.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration(name = "child", classes = ClassHierarchyWithMergedConfigLevelTwoTests.OrderConfig.class))
+public class ClassHierarchyWithMergedConfigLevelTwoTests extends ClassHierarchyWithMergedConfigLevelOneTests {
+
+ @Configuration
+ static class OrderConfig {
+
+ @Autowired
+ private ClassHierarchyWithMergedConfigLevelOneTests.UserConfig userConfig;
+
+ @Bean
+ public String order() {
+ return userConfig.user() + " + order";
+ }
+ }
+
+
+ @Autowired
+ private String order;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ super.loadContextHierarchy();
+ assertEquals("parent + user + order", order);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithOverriddenConfigLevelTwoTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithOverriddenConfigLevelTwoTests.java
new file mode 100644
index 00000000..1f5f69a0
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/ClassHierarchyWithOverriddenConfigLevelTwoTests.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+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.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration(name = "child", classes = ClassHierarchyWithOverriddenConfigLevelTwoTests.TestUserConfig.class, inheritLocations = false))
+public class ClassHierarchyWithOverriddenConfigLevelTwoTests extends ClassHierarchyWithMergedConfigLevelOneTests {
+
+ @Configuration
+ static class TestUserConfig {
+
+ @Autowired
+ private ClassHierarchyWithMergedConfigLevelOneTests.AppConfig appConfig;
+
+
+ @Bean
+ public String user() {
+ return appConfig.parent() + " + test user";
+ }
+
+ @Bean
+ public String beanFromTestUserConfig() {
+ return "from TestUserConfig";
+ }
+ }
+
+
+ @Autowired
+ private String beanFromTestUserConfig;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("parent", parent);
+ assertEquals("parent + test user", user);
+ assertEquals("from TestUserConfig", beanFromTestUserConfig);
+ assertNull("Bean from UserConfig should not be present.", beanFromUserConfig);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/DirtiesContextWithContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/DirtiesContextWithContextHierarchyTests.java
new file mode 100644
index 00000000..95757b2c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/DirtiesContextWithContextHierarchyTests.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import static org.junit.Assert.*;
+
+/**
+ * Integration tests that verify support for {@link DirtiesContext.HierarchyMode}
+ * in conjunction with context hierarchies configured via {@link ContextHierarchy}.
+ *
+ * <p>Note that correct method execution order is essential, thus the use of
+ * {@link FixMethodOrder}.
+ *
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy({ @ContextConfiguration(classes = DirtiesContextWithContextHierarchyTests.ParentConfig.class),
+ @ContextConfiguration(classes = DirtiesContextWithContextHierarchyTests.ChildConfig.class) })
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class DirtiesContextWithContextHierarchyTests {
+
+ @Configuration
+ static class ParentConfig {
+
+ @Bean
+ public StringBuffer foo() {
+ return new StringBuffer("foo");
+ }
+
+ @Bean
+ public StringBuffer baz() {
+ return new StringBuffer("baz-parent");
+ }
+ }
+
+ @Configuration
+ static class ChildConfig {
+
+ @Bean
+ public StringBuffer baz() {
+ return new StringBuffer("baz-child");
+ }
+ }
+
+
+ @Autowired
+ private StringBuffer foo;
+
+ @Autowired
+ private StringBuffer baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ // -------------------------------------------------------------------------
+
+ private void reverseStringBuffers() {
+ foo.reverse();
+ baz.reverse();
+ }
+
+ private void assertOriginalState() {
+ assertCleanParentContext();
+ assertCleanChildContext();
+ }
+
+ private void assertCleanParentContext() {
+ assertEquals("foo", foo.toString());
+ }
+
+ private void assertCleanChildContext() {
+ assertEquals("baz-child", baz.toString());
+ }
+
+ private void assertDirtyParentContext() {
+ assertEquals("oof", foo.toString());
+ }
+
+ private void assertDirtyChildContext() {
+ assertEquals("dlihc-zab", baz.toString());
+ }
+
+ // -------------------------------------------------------------------------
+
+ @Before
+ public void verifyContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ }
+
+ @Test
+ public void test1_verifyOriginalStateAndDirtyContexts() {
+ assertOriginalState();
+ reverseStringBuffers();
+ }
+
+ @Test
+ @DirtiesContext
+ public void test2_verifyContextsWereDirtiedAndTriggerExhaustiveCacheClearing() {
+ assertDirtyParentContext();
+ assertDirtyChildContext();
+ }
+
+ @Test
+ @DirtiesContext(hierarchyMode = HierarchyMode.CURRENT_LEVEL)
+ public void test3_verifyOriginalStateWasReinstatedAndDirtyContextsAndTriggerCurrentLevelCacheClearing() {
+ assertOriginalState();
+ reverseStringBuffers();
+ }
+
+ @Test
+ public void test4_verifyParentContextIsStillDirtyButChildContextHasBeenReinstated() {
+ assertDirtyParentContext();
+ assertCleanChildContext();
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithSingleLevelContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithSingleLevelContextHierarchyTests.java
new file mode 100644
index 00000000..b111f152
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithSingleLevelContextHierarchyTests.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class SingleTestClassWithSingleLevelContextHierarchyTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNull("parent ApplicationContext", context.getParent());
+ assertEquals("foo", foo);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests.java
new file mode 100644
index 00000000..5591b915
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy({
+ @ContextConfiguration(classes = SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests.ParentConfig.class),
+ @ContextConfiguration("SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests-ChildConfig.xml") })
+public class SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests {
+
+ @Configuration
+ static class ParentConfig {
+
+ @Bean
+ public String foo() {
+ return "foo";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz-parent";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private 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/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java
new file mode 100644
index 00000000..0333e5fb
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyTests.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy({
+ @ContextConfiguration(classes = SingleTestClassWithTwoLevelContextHierarchyTests.ParentConfig.class),
+ @ContextConfiguration(classes = SingleTestClassWithTwoLevelContextHierarchyTests.ChildConfig.class) })
+public class SingleTestClassWithTwoLevelContextHierarchyTests {
+
+ @Configuration
+ static class ParentConfig {
+
+ @Bean
+ public String foo() {
+ return "foo";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz-parent";
+ }
+ }
+
+ @Configuration
+ static class ChildConfig {
+
+ @Bean
+ public String bar() {
+ return "bar";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz-child";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private 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/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSubclassTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSubclassTests.java
new file mode 100644
index 00000000..3dbf5794
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSubclassTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class TestHierarchyLevelOneWithBareContextConfigurationInSubclassTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-1";
+ }
+
+ @Bean
+ public String bar() {
+ return "bar";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNull("parent ApplicationContext", context.getParent());
+ assertEquals("foo-level-1", foo);
+ assertEquals("bar", bar);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSuperclassTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSuperclassTests.java
new file mode 100644
index 00000000..16b04c7f
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithBareContextConfigurationInSuperclassTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class TestHierarchyLevelOneWithBareContextConfigurationInSuperclassTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-1";
+ }
+
+ @Bean
+ public String bar() {
+ return "bar";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNull("parent ApplicationContext", context.getParent());
+ assertEquals("foo-level-1", foo);
+ assertEquals("bar", bar);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithSingleLevelContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithSingleLevelContextHierarchyTests.java
new file mode 100644
index 00000000..3e91f375
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelOneWithSingleLevelContextHierarchyTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class TestHierarchyLevelOneWithSingleLevelContextHierarchyTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-1";
+ }
+
+ @Bean
+ public String bar() {
+ return "bar";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNull("parent ApplicationContext", context.getParent());
+ assertEquals("foo-level-1", foo);
+ assertEquals("bar", bar);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSubclassTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSubclassTests.java
new file mode 100644
index 00000000..2054f450
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSubclassTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class TestHierarchyLevelTwoWithBareContextConfigurationInSubclassTests extends
+ TestHierarchyLevelOneWithBareContextConfigurationInSubclassTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-2";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("foo-level-2", foo);
+ assertEquals("bar", bar);
+ assertEquals("baz", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSuperclassTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSuperclassTests.java
new file mode 100644
index 00000000..bc38d2bb
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithBareContextConfigurationInSuperclassTests.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class TestHierarchyLevelTwoWithBareContextConfigurationInSuperclassTests extends
+ TestHierarchyLevelOneWithBareContextConfigurationInSuperclassTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-2";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("foo-level-2", foo);
+ assertEquals("bar", bar);
+ assertEquals("baz", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests.java
new file mode 100644
index 00000000..c6edd3af
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+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.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests extends
+ TestHierarchyLevelOneWithSingleLevelContextHierarchyTests {
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertNull("grandparent ApplicationContext", context.getParent().getParent());
+ assertEquals("foo-level-2", foo);
+ assertEquals("bar", bar);
+ assertEquals("baz", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyTests.java
new file mode 100644
index 00000000..edc8fb33
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.standard;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextHierarchy(@ContextConfiguration)
+public class TestHierarchyLevelTwoWithSingleLevelContextHierarchyTests extends
+ TestHierarchyLevelOneWithSingleLevelContextHierarchyTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "foo-level-2";
+ }
+
+ @Bean
+ public String baz() {
+ return "baz";
+ }
+ }
+
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+ @Autowired
+ private String baz;
+
+ @Autowired
+ private ApplicationContext context;
+
+
+ @Test
+ @Override
+ public void loadContextHierarchy() {
+ assertNotNull("child ApplicationContext", context);
+ assertNotNull("parent ApplicationContext", context.getParent());
+ assertEquals("foo-level-2", foo);
+ assertEquals("bar", bar);
+ assertEquals("baz", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/ControllerIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/ControllerIntegrationTests.java
new file mode 100644
index 00000000..a6282218
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/ControllerIntegrationTests.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.web;
+
+import javax.servlet.ServletContext;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.hierarchies.web.ControllerIntegrationTests.AppConfig;
+import org.springframework.test.context.hierarchies.web.ControllerIntegrationTests.WebConfig;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@WebAppConfiguration
+@ContextHierarchy({
+ //
+ @ContextConfiguration(name = "root", classes = AppConfig.class),
+ @ContextConfiguration(name = "dispatcher", classes = WebConfig.class) //
+})
+public class ControllerIntegrationTests {
+
+ @Configuration
+ static class AppConfig {
+
+ @Bean
+ public String foo() {
+ return "foo";
+ }
+ }
+
+ @Configuration
+ static class WebConfig {
+
+ @Bean
+ public String bar() {
+ return "bar";
+ }
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ @Autowired
+ private WebApplicationContext wac;
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ private String bar;
+
+
+ @Test
+ public void verifyRootWacSupport() {
+ assertEquals("foo", foo);
+ assertEquals("bar", bar);
+
+ ApplicationContext parent = wac.getParent();
+ assertNotNull(parent);
+ assertTrue(parent instanceof WebApplicationContext);
+ WebApplicationContext root = (WebApplicationContext) parent;
+ assertFalse(root.getBeansOfType(String.class).containsKey("bar"));
+
+ ServletContext childServletContext = wac.getServletContext();
+ assertNotNull(childServletContext);
+ ServletContext rootServletContext = root.getServletContext();
+ assertNotNull(rootServletContext);
+ assertSame(childServletContext, rootServletContext);
+
+ assertSame(root, rootServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
+ assertSame(root, childServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java
new file mode 100644
index 00000000..622547c4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.web;
+
+import javax.servlet.ServletContext;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@ContextHierarchy(@ContextConfiguration)
+public class DispatcherWacRootWacEarTests extends RootWacEarTests {
+
+ @Autowired
+ private WebApplicationContext wac;
+
+ @Autowired
+ private String ear;
+
+ @Autowired
+ private String root;
+
+ @Autowired
+ private String dispatcher;
+
+
+ @Test
+ @Override
+ public void verifyEarConfig() {
+ /* no-op */
+ }
+
+ @Test
+ @Override
+ public void verifyRootWacConfig() {
+ /* no-op */
+ }
+
+ @Test
+ public void verifyDispatcherWacConfig() {
+ ApplicationContext parent = wac.getParent();
+ assertNotNull(parent);
+ assertTrue(parent instanceof WebApplicationContext);
+
+ ApplicationContext grandParent = parent.getParent();
+ assertNotNull(grandParent);
+ assertFalse(grandParent instanceof WebApplicationContext);
+
+ ServletContext dispatcherServletContext = wac.getServletContext();
+ assertNotNull(dispatcherServletContext);
+ ServletContext rootServletContext = ((WebApplicationContext) parent).getServletContext();
+ assertNotNull(rootServletContext);
+ assertSame(dispatcherServletContext, rootServletContext);
+
+ assertSame(parent,
+ rootServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
+ assertSame(parent,
+ dispatcherServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
+
+ assertEquals("ear", ear);
+ assertEquals("root", root);
+ assertEquals("dispatcher", dispatcher);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/EarTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/EarTests.java
new file mode 100644
index 00000000..8cd7eaf1
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/EarTests.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.web;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class EarTests {
+
+ @Configuration
+ static class EarConfig {
+
+ @Bean
+ public String ear() {
+ return "ear";
+ }
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ @Autowired
+ private ApplicationContext context;
+
+ @Autowired
+ private String ear;
+
+
+ @Test
+ public void verifyEarConfig() {
+ assertFalse(context instanceof WebApplicationContext);
+ assertNull(context.getParent());
+ assertEquals("ear", ear);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java
new file mode 100644
index 00000000..dbd3e93a
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/hierarchies/web/RootWacEarTests.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.hierarchies.web;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextHierarchy;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2.2
+ */
+@WebAppConfiguration
+@ContextHierarchy(@ContextConfiguration)
+public class RootWacEarTests extends EarTests {
+
+ @Configuration
+ static class RootWacConfig {
+
+ @Bean
+ public String root() {
+ return "root";
+ }
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ @Autowired
+ private WebApplicationContext wac;
+
+ @Autowired
+ private String ear;
+
+ @Autowired
+ private String root;
+
+
+ @Test
+ @Override
+ public void verifyEarConfig() {
+ /* no-op */
+ }
+
+ @Test
+ public void verifyRootWacConfig() {
+ ApplicationContext parent = wac.getParent();
+ assertNotNull(parent);
+ assertFalse(parent instanceof WebApplicationContext);
+ assertEquals("ear", ear);
+ assertEquals("root", root);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit38/ConcreteTransactionalJUnit38SpringContextTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit38/ConcreteTransactionalJUnit38SpringContextTests-context.xml
new file mode 100644
index 00000000..9f2fdef4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit38/ConcreteTransactionalJUnit38SpringContextTests-context.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="employee" class="org.springframework.tests.sample.beans.Employee">
+ <property name="name" value="John Smith" />
+ <property name="age" value="42" />
+ <property name="company" value="Acme Widgets, Inc." />
+ </bean>
+
+ <bean id="pet" class="org.springframework.tests.sample.beans.Pet">
+ <constructor-arg value="Fido" />
+ </bean>
+
+ <bean id="foo" class="java.lang.String">
+ <constructor-arg value="Foo" />
+ </bean>
+
+ <bean id="bar" class="java.lang.String">
+ <constructor-arg value="Bar" />
+ </bean>
+
+ <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
+ p:driverClassName="org.hsqldb.jdbcDriver" p:url="jdbc:hsqldb:mem:transactional_tests" p:username="sa" p:password="" />
+
+ <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:data-source-ref="dataSource" />
+
+ <bean id="databaseSetup"
+ class="org.springframework.test.context.junit4.ConcreteTransactionalJUnit4SpringContextTests$DatabaseSetup" />
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit38/ConcreteTransactionalJUnit38SpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/junit38/ConcreteTransactionalJUnit38SpringContextTests.java
new file mode 100644
index 00000000..735f96a0
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit38/ConcreteTransactionalJUnit38SpringContextTests.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit38;
+
+import java.util.ArrayList;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+import org.junit.internal.runners.JUnit38ClassRunner;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.BadSqlGrammarException;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.annotation.ExpectedException;
+import org.springframework.test.annotation.NotTransactional;
+import org.springframework.test.annotation.Timed;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.test.jdbc.SimpleJdbcTestUtils;
+
+/**
+ * Combined integration test for {@link AbstractJUnit38SpringContextTests} and
+ * {@link AbstractTransactionalJUnit38SpringContextTests}.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@SuppressWarnings("deprecation")
+@RunWith(JUnit38ClassRunner.class)
+@ContextConfiguration
+public class ConcreteTransactionalJUnit38SpringContextTests extends AbstractTransactionalJUnit38SpringContextTests
+ implements BeanNameAware, InitializingBean {
+
+ protected static final String BOB = "bob";
+ protected static final String JANE = "jane";
+ protected static final String SUE = "sue";
+ protected static final String YODA = "yoda";
+
+ private boolean beanInitialized = false;
+
+ private String beanName = "replace me with [" + getClass().getName() + "]";
+
+ private Employee employee;
+
+ @Autowired
+ private Pet pet;
+
+ @Autowired(required = false)
+ protected Long nonrequiredLong;
+
+ @Resource()
+ protected String foo;
+
+ protected String bar;
+
+ private boolean inTransaction = false;
+
+
+ public ConcreteTransactionalJUnit38SpringContextTests() throws Exception {
+ this(null);
+ }
+
+ public ConcreteTransactionalJUnit38SpringContextTests(final String name) throws Exception {
+ super(name);
+ }
+
+ protected static int clearPersonTable(final SimpleJdbcTemplate simpleJdbcTemplate) {
+ return SimpleJdbcTestUtils.deleteFromTables(simpleJdbcTemplate, "person");
+ }
+
+ protected static void createPersonTable(final SimpleJdbcTemplate simpleJdbcTemplate) {
+ try {
+ simpleJdbcTemplate.update("CREATE TABLE person (name VARCHAR(20) NOT NULL, PRIMARY KEY(name))");
+ }
+ catch (final BadSqlGrammarException bsge) {
+ /* ignore */
+ }
+ }
+
+ protected static int countRowsInPersonTable(final SimpleJdbcTemplate simpleJdbcTemplate) {
+ return SimpleJdbcTestUtils.countRowsInTable(simpleJdbcTemplate, "person");
+ }
+
+ protected static int addPerson(final SimpleJdbcTemplate simpleJdbcTemplate, final String name) {
+ return simpleJdbcTemplate.update("INSERT INTO person VALUES(?)", name);
+ }
+
+ protected static int deletePerson(final SimpleJdbcTemplate simpleJdbcTemplate, final String name) {
+ return simpleJdbcTemplate.update("DELETE FROM person WHERE name=?", name);
+ }
+
+ @Override
+ public final void afterPropertiesSet() throws Exception {
+ this.beanInitialized = true;
+ }
+
+ @Override
+ public final void setBeanName(final String beanName) {
+ this.beanName = beanName;
+ }
+
+ @Autowired
+ protected final void setEmployee(final Employee employee) {
+ this.employee = employee;
+ }
+
+ @Resource
+ protected final void setBar(final String bar) {
+ this.bar = bar;
+ }
+
+ @NotTransactional
+ @Timed(millis = 10000)
+ public void testNoOpShouldNotTimeOut() throws Exception {
+ /* no-op */
+ }
+
+ @NotTransactional
+ @ExpectedException(IndexOutOfBoundsException.class)
+ public void testExpectedExceptionAnnotation() {
+ new ArrayList<Object>().get(1);
+ }
+
+ @NotTransactional
+ public void testApplicationContextSet() {
+ assertNotNull("The application context should have been set due to ApplicationContextAware semantics.",
+ super.applicationContext);
+ }
+
+ @NotTransactional
+ public void testBeanInitialized() {
+ assertTrue("This test bean should have been initialized due to InitializingBean semantics.",
+ this.beanInitialized);
+ }
+
+ @NotTransactional
+ public void testBeanNameSet() {
+ assertEquals("The bean name of this test instance should have been set to the fully qualified class name "
+ + "due to BeanNameAware semantics.", getClass().getName(), this.beanName);
+ }
+
+ @NotTransactional
+ public void testAnnotationAutowiredFields() {
+ assertNull("The nonrequiredLong property should NOT have been autowired.", this.nonrequiredLong);
+ assertNotNull("The pet field should have been autowired.", this.pet);
+ assertEquals("Fido", this.pet.getName());
+ }
+
+ @NotTransactional
+ public void testAnnotationAutowiredMethods() {
+ assertNotNull("The employee setter method should have been autowired.", this.employee);
+ assertEquals("John Smith", this.employee.getName());
+ }
+
+ @NotTransactional
+ public void testResourceAnnotationWiredFields() {
+ assertEquals("The foo field should have been wired via @Resource.", "Foo", this.foo);
+ }
+
+ @NotTransactional
+ public void testResourceAnnotationWiredMethods() {
+ assertEquals("The bar method should have been wired via @Resource.", "Bar", this.bar);
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ this.inTransaction = true;
+ assertEquals("Verifying the number of rows in the person table before a transactional test method.", 1,
+ countRowsInPersonTable(super.simpleJdbcTemplate));
+ assertEquals("Adding yoda", 1, addPerson(super.simpleJdbcTemplate, YODA));
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ assertEquals("Verifying the number of rows in the person table before a test method.", (this.inTransaction ? 2
+ : 1), countRowsInPersonTable(super.simpleJdbcTemplate));
+ }
+
+ public void testModifyTestDataWithinTransaction() {
+ assertEquals("Adding jane", 1, addPerson(super.simpleJdbcTemplate, JANE));
+ assertEquals("Adding sue", 1, addPerson(super.simpleJdbcTemplate, SUE));
+ assertEquals("Verifying the number of rows in the person table within transactionalMethod2().", 4,
+ countRowsInPersonTable(super.simpleJdbcTemplate));
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ assertEquals("Verifying the number of rows in the person table after a test method.", (this.inTransaction ? 4
+ : 1), countRowsInPersonTable(super.simpleJdbcTemplate));
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertEquals("Deleting yoda", 1, deletePerson(super.simpleJdbcTemplate, YODA));
+ assertEquals("Verifying the number of rows in the person table after a transactional test method.", 1,
+ countRowsInPersonTable(super.simpleJdbcTemplate));
+ }
+
+
+ public static class DatabaseSetup {
+
+ @Autowired
+ void setDataSource(final DataSource dataSource) {
+ final SimpleJdbcTemplate simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ createPersonTable(simpleJdbcTemplate);
+ clearPersonTable(simpleJdbcTemplate);
+ addPerson(simpleJdbcTemplate, BOB);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit38/FailingBeforeAndAfterMethodsTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit38/FailingBeforeAndAfterMethodsTests-context.xml
new file mode 100644
index 00000000..3c4fc976
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit38/FailingBeforeAndAfterMethodsTests-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
+ p:driverClassName="org.hsqldb.jdbcDriver" p:url="jdbc:hsqldb:mem:transactional_tests" p:username="sa" p:password="" />
+
+ <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:data-source-ref="dataSource" />
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit38/FailingBeforeAndAfterMethodsTests.java b/spring-test/src/test/java/org/springframework/test/context/junit38/FailingBeforeAndAfterMethodsTests.java
new file mode 100644
index 00000000..192702ab
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit38/FailingBeforeAndAfterMethodsTests.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2002-2012 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.junit38;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.TestExecutionListener;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.support.AbstractTestExecutionListener;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+
+/**
+ * <p>
+ * JUnit 4 based integration test for verifying that '<em>before</em>' and '<em>after</em>'
+ * methods of {@link TestExecutionListener TestExecutionListeners} as well as
+ * {@link BeforeTransaction @BeforeTransaction} and
+ * {@link AfterTransaction @AfterTransaction} methods can fail a test in a JUnit
+ * 3.8 environment, as requested in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3960"
+ * target="_blank">SPR-3960</a>.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@RunWith(Parameterized.class)
+public class FailingBeforeAndAfterMethodsTests {
+
+ protected final Class<?> clazz;
+
+
+ public FailingBeforeAndAfterMethodsTests(final Class<?> clazz) {
+ this.clazz = clazz;
+ }
+
+ @Parameters
+ public static Collection<Object[]> testData() {
+ return Arrays.asList(new Object[][] {
+
+ { AlwaysFailingBeforeTestMethodTestCase.class },
+
+ { AlwaysFailingAfterTestMethodTestCase.class },
+
+ { FailingBeforeTransactionalTestCase.class },
+
+ { FailingAfterTransactionalTestCase.class }
+
+ });
+ }
+
+ @Test
+ public void runTestAndAssertCounters() throws Exception {
+ final String testName = "testNothing";
+ final TestCase testCase = (TestCase) this.clazz.newInstance();
+ testCase.setName(testName);
+ TestResult testResult = testCase.run();
+ assertEquals("Verifying number of errors for test method [" + testName + "] and class [" + this.clazz + "].",
+ 0, testResult.errorCount());
+ assertEquals("Verifying number of failures for test method [" + testName + "] and class [" + this.clazz + "].",
+ 1, testResult.failureCount());
+ }
+
+
+ static class AlwaysFailingBeforeTestMethodTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void beforeTestMethod(TestContext testContext) {
+ junit.framework.Assert.fail("always failing beforeTestMethod()");
+ }
+ }
+
+ static class AlwaysFailingAfterTestMethodTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void afterTestMethod(TestContext testContext) {
+ junit.framework.Assert.fail("always failing afterTestMethod()");
+ }
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @SuppressWarnings("deprecation")
+ @TestExecutionListeners(listeners = AlwaysFailingBeforeTestMethodTestExecutionListener.class, inheritListeners = false)
+ public static class AlwaysFailingBeforeTestMethodTestCase extends AbstractJUnit38SpringContextTests {
+
+ public void testNothing() {
+ }
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @SuppressWarnings("deprecation")
+ @TestExecutionListeners(listeners = AlwaysFailingAfterTestMethodTestExecutionListener.class, inheritListeners = false)
+ public static class AlwaysFailingAfterTestMethodTestCase extends AbstractJUnit38SpringContextTests {
+
+ public void testNothing() {
+ }
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @SuppressWarnings("deprecation")
+ @ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml")
+ public static class FailingBeforeTransactionalTestCase extends AbstractTransactionalJUnit38SpringContextTests {
+
+ public void testNothing() {
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ fail("always failing beforeTransaction()");
+ }
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @SuppressWarnings("deprecation")
+ @ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml")
+ public static class FailingAfterTransactionalTestCase extends AbstractTransactionalJUnit38SpringContextTests {
+
+ public void testNothing() {
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ fail("always failing afterTransaction()");
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit38/ProfileValueJUnit38SpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/junit38/ProfileValueJUnit38SpringContextTests.java
new file mode 100644
index 00000000..6cecd0c2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit38/ProfileValueJUnit38SpringContextTests.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2002-2012 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.junit38;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+
+import org.springframework.test.annotation.IfProfileValue;
+import org.springframework.test.annotation.ProfileValueSource;
+import org.springframework.test.annotation.ProfileValueSourceConfiguration;
+import org.springframework.test.annotation.SystemProfileValueSource;
+import org.springframework.test.context.TestExecutionListeners;
+
+/**
+ * Verifies proper handling of {@link IfProfileValue &#064;IfProfileValue} and
+ * {@link ProfileValueSourceConfiguration &#064;ProfileValueSourceConfiguration}
+ * in conjunction with {@link AbstractJUnit38SpringContextTests}.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class ProfileValueJUnit38SpringContextTests extends TestCase {
+
+ private static final String EMPTY = "testIfProfileValueEmpty";
+ private static final String DISABLED_VIA_WRONG_NAME = "testIfProfileValueDisabledViaWrongName";
+ private static final String DISABLED_VIA_WRONG_VALUE = "testIfProfileValueDisabledViaWrongValue";
+ private static final String ENABLED_VIA_MULTIPLE_VALUES = "testIfProfileValueEnabledViaMultipleValues";
+ private static final String ENABLED_VIA_SINGLE_VALUE = "testIfProfileValueEnabledViaSingleValue";
+ private static final String NOT_CONFIGURED = "testIfProfileValueNotConfigured";
+
+ private static final String NAME = "ProfileValueAnnotationAwareTransactionalTests.profile_value.name";
+ private static final String VALUE = "enigma";
+
+ private final Map<String, Integer> expectedInvocationCounts = new HashMap<String, Integer>();
+
+
+ public ProfileValueJUnit38SpringContextTests() {
+ System.setProperty(NAME, VALUE);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ this.expectedInvocationCounts.put(EMPTY, 0);
+ this.expectedInvocationCounts.put(DISABLED_VIA_WRONG_NAME, 0);
+ this.expectedInvocationCounts.put(DISABLED_VIA_WRONG_VALUE, 0);
+ this.expectedInvocationCounts.put(ENABLED_VIA_SINGLE_VALUE, 1);
+ this.expectedInvocationCounts.put(ENABLED_VIA_MULTIPLE_VALUES, 1);
+ this.expectedInvocationCounts.put(NOT_CONFIGURED, 1);
+ }
+
+ private void configureDisabledClassExpectations() {
+ this.expectedInvocationCounts.put(ENABLED_VIA_SINGLE_VALUE, 0);
+ this.expectedInvocationCounts.put(ENABLED_VIA_MULTIPLE_VALUES, 0);
+ this.expectedInvocationCounts.put(NOT_CONFIGURED, 0);
+ }
+
+ private void runTestAndAssertCounters(Class<? extends DefaultProfileValueSourceTestCase> testCaseType,
+ String testName, int expectedInvocationCount, int expectedErrorCount, int expectedFailureCount)
+ throws Exception {
+
+ DefaultProfileValueSourceTestCase testCase = testCaseType.newInstance();
+ testCase.setName(testName);
+ TestResult testResult = testCase.run();
+ assertEquals("Verifying number of invocations for test method [" + testName + "].", expectedInvocationCount,
+ testCase.invocationCount);
+ assertEquals("Verifying number of errors for test method [" + testName + "].", expectedErrorCount,
+ testResult.errorCount());
+ assertEquals("Verifying number of failures for test method [" + testName + "].", expectedFailureCount,
+ testResult.failureCount());
+ }
+
+ private void runTests(final Class<? extends DefaultProfileValueSourceTestCase> testCaseType) throws Exception {
+ runTestAndAssertCounters(testCaseType, EMPTY, expectedInvocationCounts.get(EMPTY), 0, 0);
+ runTestAndAssertCounters(testCaseType, DISABLED_VIA_WRONG_NAME,
+ expectedInvocationCounts.get(DISABLED_VIA_WRONG_NAME), 0, 0);
+ runTestAndAssertCounters(testCaseType, DISABLED_VIA_WRONG_VALUE,
+ expectedInvocationCounts.get(DISABLED_VIA_WRONG_VALUE), 0, 0);
+ runTestAndAssertCounters(testCaseType, ENABLED_VIA_SINGLE_VALUE,
+ expectedInvocationCounts.get(ENABLED_VIA_SINGLE_VALUE), 0, 0);
+ runTestAndAssertCounters(testCaseType, ENABLED_VIA_MULTIPLE_VALUES,
+ expectedInvocationCounts.get(ENABLED_VIA_MULTIPLE_VALUES), 0, 0);
+ runTestAndAssertCounters(testCaseType, NOT_CONFIGURED, expectedInvocationCounts.get(NOT_CONFIGURED), 0, 0);
+ }
+
+ public void testDefaultProfileValueSource() throws Exception {
+ assertEquals("Verifying the type of the configured ProfileValueSource.", SystemProfileValueSource.class,
+ new DefaultProfileValueSourceTestCase().getProfileValueSource().getClass());
+ runTests(DefaultProfileValueSourceTestCase.class);
+ }
+
+ public void testHardCodedProfileValueSource() throws Exception {
+ assertEquals("Verifying the type of the configured ProfileValueSource.", HardCodedProfileValueSource.class,
+ new HardCodedProfileValueSourceTestCase().getProfileValueSource().getClass());
+ runTests(HardCodedProfileValueSourceTestCase.class);
+ }
+
+ public void testClassLevelIfProfileValueEnabledSingleValue() throws Exception {
+ runTests(ClassLevelIfProfileValueEnabledSingleValueTestCase.class);
+ }
+
+ public void testClassLevelIfProfileValueDisabledSingleValue() throws Exception {
+ configureDisabledClassExpectations();
+ runTests(ClassLevelIfProfileValueDisabledSingleValueTestCase.class);
+ }
+
+ public void testClassLevelIfProfileValueEnabledMultiValue() throws Exception {
+ runTests(ClassLevelIfProfileValueEnabledMultiValueTestCase.class);
+ }
+
+ public void testClassLevelIfProfileValueDisabledMultiValue() throws Exception {
+ configureDisabledClassExpectations();
+ runTests(ClassLevelIfProfileValueDisabledMultiValueTestCase.class);
+ }
+
+
+ // -------------------------------------------------------------------
+
+ /**
+ * Note that {@link TestExecutionListeners @TestExecutionListeners} is
+ * explicitly configured with an empty list, thus disabling all default
+ * listeners.
+ */
+ @SuppressWarnings("deprecation")
+ @TestExecutionListeners(listeners = {}, inheritListeners = false)
+ public static class DefaultProfileValueSourceTestCase extends AbstractJUnit38SpringContextTests {
+
+ int invocationCount = 0;
+
+
+ public ProfileValueSource getProfileValueSource() {
+ return super.profileValueSource;
+ }
+
+ @IfProfileValue(name = NAME, value = "")
+ public void testIfProfileValueEmpty() {
+ this.invocationCount++;
+ fail("An empty profile value should throw an IllegalArgumentException.");
+ }
+
+ @IfProfileValue(name = NAME + "X", value = VALUE)
+ public void testIfProfileValueDisabledViaWrongName() {
+ this.invocationCount++;
+ fail("The body of a disabled test should never be executed!");
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE + "X")
+ public void testIfProfileValueDisabledViaWrongValue() {
+ this.invocationCount++;
+ fail("The body of a disabled test should never be executed!");
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE)
+ public void testIfProfileValueEnabledViaSingleValue() {
+ this.invocationCount++;
+ }
+
+ @IfProfileValue(name = NAME, values = { "foo", VALUE, "bar" })
+ public void testIfProfileValueEnabledViaMultipleValues() {
+ this.invocationCount++;
+ }
+
+ public void testIfProfileValueNotConfigured() {
+ this.invocationCount++;
+ }
+ }
+
+ @ProfileValueSourceConfiguration(HardCodedProfileValueSource.class)
+ public static class HardCodedProfileValueSourceTestCase extends DefaultProfileValueSourceTestCase {
+ }
+
+ public static class HardCodedProfileValueSource implements ProfileValueSource {
+
+ @Override
+ public String get(final String key) {
+ return (key.equals(NAME) ? VALUE : null);
+ }
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE)
+ public static class ClassLevelIfProfileValueEnabledSingleValueTestCase extends DefaultProfileValueSourceTestCase {
+ }
+
+ @IfProfileValue(name = NAME, value = VALUE + "X")
+ public static class ClassLevelIfProfileValueDisabledSingleValueTestCase extends DefaultProfileValueSourceTestCase {
+ }
+
+ @IfProfileValue(name = NAME, values = { "foo", VALUE, "bar" })
+ public static class ClassLevelIfProfileValueEnabledMultiValueTestCase extends DefaultProfileValueSourceTestCase {
+ }
+
+ @IfProfileValue(name = NAME, values = { "foo", "bar", "baz" })
+ public static class ClassLevelIfProfileValueDisabledMultiValueTestCase extends DefaultProfileValueSourceTestCase {
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit38/RepeatedJUnit38SpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/junit38/RepeatedJUnit38SpringContextTests.java
new file mode 100644
index 00000000..e33f290a
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit38/RepeatedJUnit38SpringContextTests.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2002-2012 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.junit38;
+
+import junit.framework.TestCase;
+
+import org.springframework.test.annotation.Repeat;
+import org.springframework.test.context.TestExecutionListeners;
+
+/**
+ * Unit test for {@link AbstractJUnit38SpringContextTests} which focuses on
+ * proper support of the {@link Repeat @Repeat} annotation.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class RepeatedJUnit38SpringContextTests extends TestCase {
+
+ public RepeatedJUnit38SpringContextTests() throws Exception {
+ super();
+ }
+
+ public RepeatedJUnit38SpringContextTests(final String name) throws Exception {
+ super(name);
+ }
+
+ private void assertRepetitions(final String testName, final int expectedNumInvocations) throws Exception {
+ final RepeatedTestCase repeatedTestCase = new RepeatedTestCase(testName);
+ repeatedTestCase.run();
+ assertEquals("Verifying number of invocations for test method [" + testName + "].", expectedNumInvocations,
+ repeatedTestCase.invocationCount);
+ }
+
+ public void testRepeatAnnotationSupport() throws Exception {
+ assertRepetitions("testNonAnnotated", 1);
+ assertRepetitions("testNegativeRepeatValue", 1);
+ assertRepetitions("testDefaultRepeatValue", 1);
+ assertRepetitions("testRepeatedFiveTimes", 5);
+ }
+
+
+ /**
+ * Note that {@link TestExecutionListeners @TestExecutionListeners} is
+ * explicitly configured with an empty list, thus disabling all default
+ * listeners.
+ */
+ @SuppressWarnings("deprecation")
+ @TestExecutionListeners(listeners = {}, inheritListeners = false)
+ public static class RepeatedTestCase extends AbstractJUnit38SpringContextTests {
+
+ int invocationCount = 0;
+
+ public RepeatedTestCase(final String name) throws Exception {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ this.invocationCount++;
+ }
+
+ public void testNonAnnotated() {
+ /* no-op */
+ }
+
+ @Repeat(-5)
+ public void testNegativeRepeatValue() {
+ /* no-op */
+ }
+
+ @Repeat
+ public void testDefaultRepeatValue() {
+ /* no-op */
+ }
+
+ @Repeat(5)
+ public void testRepeatedFiveTimes() {
+ /* no-op */
+ }
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/AbsolutePathSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/AbsolutePathSpringJUnit4ClassRunnerAppCtxTests.java
new file mode 100644
index 00000000..0e53eef4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/AbsolutePathSpringJUnit4ClassRunnerAppCtxTests.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2012 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.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that
+ * we can specify an explicit, <em>absolute path</em> location for our
+ * application context.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see SpringJUnit4ClassRunnerAppCtxTests
+ * @see ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests
+ * @see RelativePathSpringJUnit4ClassRunnerAppCtxTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = { SpringJUnit4ClassRunnerAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH }, inheritLocations = false)
+public class AbsolutePathSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests {
+ /* all tests are in the parent class. */
+}
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
new file mode 100644
index 00000000..1469cc35
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/AbstractTransactionalSpringRunnerTests.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.annotation.NotTransactional;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Abstract base class for verifying support of Spring's {@link Transactional
+ * &#64;Transactional} and {@link NotTransactional &#64;NotTransactional}
+ * annotations.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see ClassLevelTransactionalSpringRunnerTests
+ * @see MethodLevelTransactionalSpringRunnerTests
+ * @see Transactional
+ * @see NotTransactional
+ */
+@SuppressWarnings("deprecation")
+@ContextConfiguration("transactionalTests-context.xml")
+public abstract class AbstractTransactionalSpringRunnerTests {
+
+ protected static final String BOB = "bob";
+ protected static final String JANE = "jane";
+ protected static final String SUE = "sue";
+ protected static final String LUKE = "luke";
+ protected static final String LEIA = "leia";
+ protected static final String YODA = "yoda";
+
+
+ protected static int clearPersonTable(SimpleJdbcTemplate simpleJdbcTemplate) {
+ return simpleJdbcTemplate.update("DELETE FROM person");
+ }
+
+ protected static void createPersonTable(SimpleJdbcTemplate simpleJdbcTemplate) {
+ try {
+ simpleJdbcTemplate.update("CREATE TABLE person (name VARCHAR(20) NOT NULL, PRIMARY KEY(name))");
+ }
+ catch (DataAccessException dae) {
+ // ignore
+ }
+ }
+
+ protected static int countRowsInPersonTable(SimpleJdbcTemplate simpleJdbcTemplate) {
+ return simpleJdbcTemplate.queryForInt("SELECT COUNT(0) FROM person");
+ }
+
+ protected static int addPerson(SimpleJdbcTemplate simpleJdbcTemplate, String name) {
+ return simpleJdbcTemplate.update("INSERT INTO person VALUES(?)", name);
+ }
+
+ protected static int deletePerson(SimpleJdbcTemplate simpleJdbcTemplate, String name) {
+ return simpleJdbcTemplate.update("DELETE FROM person WHERE name=?", name);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests-context.xml
new file mode 100644
index 00000000..666f357b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests-context.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="databaseSetup"
+ class="org.springframework.test.context.junit4.BeforeAndAfterTransactionAnnotationTests$DatabaseSetup" />
+
+</beans>
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
new file mode 100644
index 00000000..7dc2af2c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/BeforeAndAfterTransactionAnnotationTests.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2002-2008 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 static org.junit.Assert.assertEquals;
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * JUnit 4 based integration test which verifies
+ * {@link BeforeTransaction @BeforeTransaction} and
+ * {@link AfterTransaction @AfterTransaction} behavior.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@SuppressWarnings("deprecation")
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@TestExecutionListeners({ TransactionalTestExecutionListener.class })
+public class BeforeAndAfterTransactionAnnotationTests extends AbstractTransactionalSpringRunnerTests {
+
+ protected static SimpleJdbcTemplate simpleJdbcTemplate;
+
+ protected static int numBeforeTransactionCalls = 0;
+ protected static int numAfterTransactionCalls = 0;
+
+ protected boolean inTransaction = false;
+
+
+ @BeforeClass
+ public static void beforeClass() {
+ BeforeAndAfterTransactionAnnotationTests.numBeforeTransactionCalls = 0;
+ BeforeAndAfterTransactionAnnotationTests.numAfterTransactionCalls = 0;
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ assertEquals("Verifying the final number of rows in the person table after all tests.", 3,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ assertEquals("Verifying the total number of calls to beforeTransaction().", 2,
+ BeforeAndAfterTransactionAnnotationTests.numBeforeTransactionCalls);
+ assertEquals("Verifying the total number of calls to afterTransaction().", 2,
+ BeforeAndAfterTransactionAnnotationTests.numAfterTransactionCalls);
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ assertInTransaction(false);
+ this.inTransaction = true;
+ BeforeAndAfterTransactionAnnotationTests.numBeforeTransactionCalls++;
+ clearPersonTable(simpleJdbcTemplate);
+ assertEquals("Adding yoda", 1, addPerson(simpleJdbcTemplate, YODA));
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertInTransaction(false);
+ this.inTransaction = false;
+ BeforeAndAfterTransactionAnnotationTests.numAfterTransactionCalls++;
+ assertEquals("Deleting yoda", 1, deletePerson(simpleJdbcTemplate, YODA));
+ assertEquals("Verifying the number of rows in the person table after a transactional test method.", 0,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Before
+ public void before() {
+ assertEquals("Verifying the number of rows in the person table before a test method.", (this.inTransaction ? 1
+ : 0), countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Test
+ @Transactional
+ public void transactionalMethod1() {
+ assertInTransaction(true);
+ assertEquals("Adding jane", 1, addPerson(simpleJdbcTemplate, JANE));
+ assertEquals("Verifying the number of rows in the person table within transactionalMethod1().", 2,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Test
+ @Transactional
+ public void transactionalMethod2() {
+ assertInTransaction(true);
+ assertEquals("Adding jane", 1, addPerson(simpleJdbcTemplate, JANE));
+ assertEquals("Adding sue", 1, addPerson(simpleJdbcTemplate, SUE));
+ assertEquals("Verifying the number of rows in the person table within transactionalMethod2().", 3,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Test
+ public void nonTransactionalMethod() {
+ assertInTransaction(false);
+ assertEquals("Adding luke", 1, addPerson(simpleJdbcTemplate, LUKE));
+ assertEquals("Adding leia", 1, addPerson(simpleJdbcTemplate, LEIA));
+ assertEquals("Adding yoda", 1, addPerson(simpleJdbcTemplate, YODA));
+ assertEquals("Verifying the number of rows in the person table without a transaction.", 3,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+
+ public static class DatabaseSetup {
+
+ @Resource
+ void setDataSource(DataSource dataSource) {
+ simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ createPersonTable(simpleJdbcTemplate);
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..7ae72d58
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelDisabledSpringRunnerTests.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.annotation.IfProfileValue;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.TestExecutionListener;
+import org.springframework.test.context.TestExecutionListeners;
+
+/**
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@TestExecutionListeners(ClassLevelDisabledSpringRunnerTests.CustomTestExecutionListener.class)
+@IfProfileValue(name = "ClassLevelDisabledSpringRunnerTests.profile_value.name", value = "enigmaX")
+public class ClassLevelDisabledSpringRunnerTests {
+
+ @Test
+ public void testIfProfileValueDisabled() {
+ fail("The body of a disabled test should never be executed!");
+ }
+
+
+ public static class CustomTestExecutionListener implements TestExecutionListener {
+
+ @Override
+ public void beforeTestClass(TestContext testContext) throws Exception {
+ fail("A listener method for a disabled test should never be executed!");
+ }
+
+ @Override
+ public void prepareTestInstance(TestContext testContext) throws Exception {
+ fail("A listener method for a disabled test should never be executed!");
+ }
+
+ @Override
+ public void beforeTestMethod(TestContext testContext) throws Exception {
+ fail("A listener method for a disabled test should never be executed!");
+ }
+
+ @Override
+ public void afterTestMethod(TestContext testContext) throws Exception {
+ fail("A listener method for a disabled test should never be executed!");
+ }
+
+ @Override
+ public void afterTestClass(TestContext testContext) throws Exception {
+ fail("A listener method for a disabled test should never be executed!");
+ }
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests-context.xml
new file mode 100644
index 00000000..9df7964a
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests-context.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="databaseSetup"
+ class="org.springframework.test.context.junit4.ClassLevelTransactionalSpringRunnerTests$DatabaseSetup" />
+
+</beans>
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
new file mode 100644
index 00000000..02c0dcf0
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassLevelTransactionalSpringRunnerTests.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.annotation.NotTransactional;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestExecutionListener;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
+import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * <p>
+ * JUnit 4 based integration test which verifies support of Spring's
+ * {@link Transactional &#64;Transactional}, {@link NotTransactional
+ * &#64;NotTransactional}, {@link TestExecutionListeners
+ * &#64;TestExecutionListeners}, and {@link ContextConfiguration
+ * &#64;ContextConfiguration} annotations in conjunction with the
+ * {@link SpringJUnit4ClassRunner} and the following
+ * {@link TestExecutionListener TestExecutionListeners}:
+ * </p>
+ * <ul>
+ * <li>{@link DependencyInjectionTestExecutionListener}</li>
+ * <li>{@link DirtiesContextTestExecutionListener}</li>
+ * <li>{@link TransactionalTestExecutionListener}</li>
+ * </ul>
+ * <p>
+ * This class specifically tests usage of {@code &#064;Transactional}
+ * defined at the <strong>class level</strong>.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see MethodLevelTransactionalSpringRunnerTests
+ */
+@SuppressWarnings("deprecation")
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@Transactional
+public class ClassLevelTransactionalSpringRunnerTests extends AbstractTransactionalSpringRunnerTests {
+
+ protected static SimpleJdbcTemplate simpleJdbcTemplate;
+
+
+ @AfterClass
+ public static void verifyFinalTestData() {
+ assertEquals("Verifying the final number of rows in the person table after all tests.", 4,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Before
+ public void verifyInitialTestData() {
+ clearPersonTable(simpleJdbcTemplate);
+ assertEquals("Adding bob", 1, addPerson(simpleJdbcTemplate, BOB));
+ assertEquals("Verifying the initial number of rows in the person table.", 1,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Test
+ public void modifyTestDataWithinTransaction() {
+ assertInTransaction(true);
+ assertEquals("Deleting bob", 1, deletePerson(simpleJdbcTemplate, BOB));
+ assertEquals("Adding jane", 1, addPerson(simpleJdbcTemplate, JANE));
+ assertEquals("Adding sue", 1, addPerson(simpleJdbcTemplate, SUE));
+ assertEquals("Verifying the number of rows in the person table within a transaction.", 2,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Test
+ @NotTransactional
+ public void modifyTestDataWithoutTransaction() {
+ assertInTransaction(false);
+ assertEquals("Adding luke", 1, addPerson(simpleJdbcTemplate, LUKE));
+ assertEquals("Adding leia", 1, addPerson(simpleJdbcTemplate, LEIA));
+ assertEquals("Adding yoda", 1, addPerson(simpleJdbcTemplate, YODA));
+ assertEquals("Verifying the number of rows in the person table without a transaction.", 4,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+
+ public static class DatabaseSetup {
+
+ @Resource
+ public void setDataSource(DataSource dataSource) {
+ simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ createPersonTable(simpleJdbcTemplate);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.java
new file mode 100644
index 00000000..4bb3fb5b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2012 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.runner.RunWith;
+
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that
+ * we can specify an explicit, <em>classpath</em> location for our application
+ * context.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see SpringJUnit4ClassRunnerAppCtxTests
+ * @see #CLASSPATH_CONTEXT_RESOURCE_PATH
+ * @see AbsolutePathSpringJUnit4ClassRunnerAppCtxTests
+ * @see RelativePathSpringJUnit4ClassRunnerAppCtxTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = { ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.CLASSPATH_CONTEXT_RESOURCE_PATH })
+public class ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests {
+
+ /**
+ * Classpath-based resource path for the application context configuration
+ * for {@link SpringJUnit4ClassRunnerAppCtxTests}:
+ * {@code &quot;classpath:/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml&quot;}
+ *
+ * @see SpringJUnit4ClassRunnerAppCtxTests#DEFAULT_CONTEXT_RESOURCE_PATH
+ * @see ResourceUtils#CLASSPATH_URL_PREFIX
+ */
+ public static final String CLASSPATH_CONTEXT_RESOURCE_PATH = ResourceUtils.CLASSPATH_URL_PREFIX
+ + SpringJUnit4ClassRunnerAppCtxTests.DEFAULT_CONTEXT_RESOURCE_PATH;
+
+ /* all tests are in the parent class. */
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests-context.xml
new file mode 100644
index 00000000..a9117841
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests-context.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <import resource="transactionalTests-context.xml" />
+
+ <bean id="databaseSetup"
+ class="org.springframework.test.context.junit4.ConcreteTransactionalJUnit4SpringContextTests$DatabaseSetup" />
+
+ <bean id="employee" class="org.springframework.tests.sample.beans.Employee">
+ <property name="name" value="John Smith" />
+ <property name="age" value="42" />
+ <property name="company" value="Acme Widgets, Inc." />
+ </bean>
+
+ <bean id="pet" class="org.springframework.tests.sample.beans.Pet">
+ <constructor-arg value="Fido" />
+ </bean>
+
+ <bean id="foo" class="java.lang.String">
+ <constructor-arg value="Foo" />
+ </bean>
+
+ <bean id="bar" class="java.lang.String">
+ <constructor-arg value="Bar" />
+ </bean>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests.java
new file mode 100644
index 00000000..bd7ce811
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ConcreteTransactionalJUnit4SpringContextTests.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.annotation.NotTransactional;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.test.jdbc.SimpleJdbcTestUtils;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.tests.sample.beans.Pet;
+
+import static org.junit.Assert.*;
+import static org.springframework.test.transaction.TransactionTestUtils.*;
+
+/**
+ * Combined integration test for {@link AbstractJUnit4SpringContextTests} and
+ * {@link AbstractTransactionalJUnit4SpringContextTests}.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@SuppressWarnings("deprecation")
+@ContextConfiguration
+public class ConcreteTransactionalJUnit4SpringContextTests extends AbstractTransactionalJUnit4SpringContextTests
+ implements BeanNameAware, InitializingBean {
+
+ protected static final String BOB = "bob";
+ protected static final String JANE = "jane";
+ protected static final String SUE = "sue";
+ protected static final String LUKE = "luke";
+ protected static final String LEIA = "leia";
+ protected static final String YODA = "yoda";
+
+ private boolean beanInitialized = false;
+
+ private String beanName = "replace me with [" + getClass().getName() + "]";
+
+ private Employee employee;
+
+ @Autowired
+ private Pet pet;
+
+ @Autowired(required = false)
+ protected Long nonrequiredLong;
+
+ @Resource
+ protected String foo;
+
+ protected String bar;
+
+
+ protected static int clearPersonTable(final SimpleJdbcTemplate simpleJdbcTemplate) {
+ return SimpleJdbcTestUtils.deleteFromTables(simpleJdbcTemplate, "person");
+ }
+
+ protected static void createPersonTable(final SimpleJdbcTemplate simpleJdbcTemplate) {
+ try {
+ simpleJdbcTemplate.update("CREATE TABLE person (name VARCHAR(20) NOT NULL, PRIMARY KEY(name))");
+ }
+ catch (DataAccessException dae) {
+ /* ignore */
+ }
+ }
+
+ protected static int countRowsInPersonTable(final SimpleJdbcTemplate simpleJdbcTemplate) {
+ return SimpleJdbcTestUtils.countRowsInTable(simpleJdbcTemplate, "person");
+ }
+
+ protected static int addPerson(final SimpleJdbcTemplate simpleJdbcTemplate, final String name) {
+ return simpleJdbcTemplate.update("INSERT INTO person VALUES(?)", name);
+ }
+
+ protected static int deletePerson(final SimpleJdbcTemplate simpleJdbcTemplate, final String name) {
+ return simpleJdbcTemplate.update("DELETE FROM person WHERE name=?", name);
+ }
+
+ @Override
+ @Resource
+ public void setDataSource(DataSource dataSource) {
+ super.setDataSource(dataSource);
+ }
+
+ @Autowired
+ protected final void setEmployee(final Employee employee) {
+ this.employee = employee;
+ }
+
+ @Resource
+ protected final void setBar(final String bar) {
+ this.bar = bar;
+ }
+
+ @Override
+ public final void setBeanName(final String beanName) {
+ this.beanName = beanName;
+ }
+
+ @Override
+ public final void afterPropertiesSet() throws Exception {
+ this.beanInitialized = true;
+ }
+
+ @Test
+ @NotTransactional
+ public final void verifyApplicationContext() {
+ assertInTransaction(false);
+ assertNotNull("The application context should have been set due to ApplicationContextAware semantics.",
+ super.applicationContext);
+ }
+
+ @Test
+ @NotTransactional
+ public final void verifyBeanInitialized() {
+ assertInTransaction(false);
+ assertTrue("This test bean should have been initialized due to InitializingBean semantics.",
+ this.beanInitialized);
+ }
+
+ @Test
+ @NotTransactional
+ public final void verifyBeanNameSet() {
+ assertInTransaction(false);
+ assertEquals("The bean name of this test instance should have been set to the fully qualified class name "
+ + "due to BeanNameAware semantics.", getClass().getName(), this.beanName);
+ }
+
+ @Test
+ @NotTransactional
+ public final void verifyAnnotationAutowiredFields() {
+ assertInTransaction(false);
+ assertNull("The nonrequiredLong property should NOT have been autowired.", this.nonrequiredLong);
+ assertNotNull("The pet field should have been autowired.", this.pet);
+ assertEquals("Fido", this.pet.getName());
+ }
+
+ @Test
+ @NotTransactional
+ public final void verifyAnnotationAutowiredMethods() {
+ assertInTransaction(false);
+ assertNotNull("The employee setter method should have been autowired.", this.employee);
+ assertEquals("John Smith", this.employee.getName());
+ }
+
+ @Test
+ @NotTransactional
+ public final void verifyResourceAnnotationWiredFields() {
+ assertInTransaction(false);
+ assertEquals("The foo field should have been wired via @Resource.", "Foo", this.foo);
+ }
+
+ @Test
+ @NotTransactional
+ public final void verifyResourceAnnotationWiredMethods() {
+ assertInTransaction(false);
+ assertEquals("The bar method should have been wired via @Resource.", "Bar", this.bar);
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ assertEquals("Verifying the number of rows in the person table before a transactional test method.", 1,
+ countRowsInPersonTable(super.simpleJdbcTemplate));
+ assertEquals("Adding yoda", 1, addPerson(super.simpleJdbcTemplate, YODA));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ assertEquals("Verifying the number of rows in the person table before a test method.",
+ (inTransaction() ? 2 : 1), countRowsInPersonTable(super.simpleJdbcTemplate));
+ }
+
+ @Test
+ public void modifyTestDataWithinTransaction() {
+ assertInTransaction(true);
+ assertEquals("Adding jane", 1, addPerson(super.simpleJdbcTemplate, JANE));
+ assertEquals("Adding sue", 1, addPerson(super.simpleJdbcTemplate, SUE));
+ assertEquals("Verifying the number of rows in the person table in modifyTestDataWithinTransaction().", 4,
+ countRowsInPersonTable(super.simpleJdbcTemplate));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ assertEquals("Verifying the number of rows in the person table after a test method.",
+ (inTransaction() ? 4 : 1), countRowsInPersonTable(super.simpleJdbcTemplate));
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertEquals("Deleting yoda", 1, deletePerson(super.simpleJdbcTemplate, YODA));
+ assertEquals("Verifying the number of rows in the person table after a transactional test method.", 1,
+ countRowsInPersonTable(super.simpleJdbcTemplate));
+ }
+
+
+ public static class DatabaseSetup {
+
+ @Resource
+ public void setDataSource(DataSource dataSource) {
+ SimpleJdbcTemplate simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ createPersonTable(simpleJdbcTemplate);
+ clearPersonTable(simpleJdbcTemplate);
+ addPerson(simpleJdbcTemplate, BOB);
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..3ee77974
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/CustomDefaultContextLoaderClassSpringRunnerTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.InitializationError;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.GenericPropertiesContextLoader;
+
+/**
+ * Integration tests which verify that a subclass of {@link SpringJUnit4ClassRunner}
+ * can specify a custom <em>default ContextLoader class name</em> that overrides
+ * the standard default class name.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@RunWith(CustomDefaultContextLoaderClassSpringRunnerTests.PropertiesBasedSpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = "PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties")
+public class CustomDefaultContextLoaderClassSpringRunnerTests {
+
+ @Autowired
+ private Pet cat;
+
+ @Autowired
+ private String testString;
+
+
+ @Test
+ public void verifyAnnotationAutowiredFields() {
+ assertNotNull("The cat field should have been autowired.", this.cat);
+ assertEquals("Garfield", this.cat.getName());
+
+ assertNotNull("The testString field should have been autowired.", this.testString);
+ assertEquals("Test String", this.testString);
+ }
+
+
+ public static final class PropertiesBasedSpringJUnit4ClassRunner extends SpringJUnit4ClassRunner {
+
+ public PropertiesBasedSpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
+ super(clazz);
+ }
+
+ @Override
+ protected String getDefaultContextLoaderClassName(Class<?> clazz) {
+ return GenericPropertiesContextLoader.class.getName();
+ }
+
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseTransactionalSpringRunnerTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseTransactionalSpringRunnerTests-context.xml
new file mode 100644
index 00000000..d9663d7c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseTransactionalSpringRunnerTests-context.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
+ p:driverClassName="org.hsqldb.jdbcDriver" p:url="jdbc:hsqldb:mem:transactional_tests" p:username="sa" p:password="" />
+
+ <bean id="txMgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:data-source-ref="dataSource" />
+
+ <bean id="databaseSetup"
+ class="org.springframework.test.context.junit4.DefaultRollbackFalseTransactionalSpringRunnerTests$DatabaseSetup" />
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseTransactionalSpringRunnerTests.java
new file mode 100644
index 00000000..08f91b53
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackFalseTransactionalSpringRunnerTests.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.transaction.TransactionConfiguration;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * <p>
+ * JUnit 4 based integration test which verifies proper transactional behavior when the
+ * {@link TransactionConfiguration#defaultRollback() defaultRollback} attribute
+ * of the {@link TransactionConfiguration} annotation is set to <strong>{@code false}</strong>.
+ * Also tests configuration of the
+ * {@link TransactionConfiguration#transactionManager() transaction manager name}.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see TransactionConfiguration
+ */
+@SuppressWarnings("deprecation")
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@TransactionConfiguration(transactionManager = "txMgr", defaultRollback = false)
+@Transactional
+public class DefaultRollbackFalseTransactionalSpringRunnerTests extends AbstractTransactionalSpringRunnerTests {
+
+ protected static SimpleJdbcTemplate simpleJdbcTemplate;
+
+
+ @AfterClass
+ public static void verifyFinalTestData() {
+ assertEquals("Verifying the final number of rows in the person table after all tests.", 2,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Before
+ public void verifyInitialTestData() {
+ clearPersonTable(simpleJdbcTemplate);
+ assertEquals("Adding bob", 1, addPerson(simpleJdbcTemplate, BOB));
+ assertEquals("Verifying the initial number of rows in the person table.", 1,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Test
+ public void modifyTestDataWithinTransaction() {
+ assertInTransaction(true);
+ assertEquals("Deleting bob", 1, deletePerson(simpleJdbcTemplate, BOB));
+ assertEquals("Adding jane", 1, addPerson(simpleJdbcTemplate, JANE));
+ assertEquals("Adding sue", 1, addPerson(simpleJdbcTemplate, SUE));
+ assertEquals("Verifying the number of rows in the person table within a transaction.", 2,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+
+ public static class DatabaseSetup {
+
+ @Resource
+ public void setDataSource(DataSource dataSource) {
+ simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ createPersonTable(simpleJdbcTemplate);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueTransactionalSpringRunnerTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueTransactionalSpringRunnerTests-context.xml
new file mode 100644
index 00000000..1c44b0c0
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueTransactionalSpringRunnerTests-context.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="databaseSetup"
+ class="org.springframework.test.context.junit4.DefaultRollbackTrueTransactionalSpringRunnerTests$DatabaseSetup" />
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueTransactionalSpringRunnerTests.java
new file mode 100644
index 00000000..d7bfb8c2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/DefaultRollbackTrueTransactionalSpringRunnerTests.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.transaction.TransactionConfiguration;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * JUnit 4 based integration test which verifies proper transactional behavior when the
+ * {@link TransactionConfiguration#defaultRollback() defaultRollback} attribute
+ * of the {@link TransactionConfiguration} annotation is set to <strong>{@code true}</strong>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see TransactionConfiguration
+ */
+@SuppressWarnings("deprecation")
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@TransactionConfiguration(defaultRollback = true)
+public class DefaultRollbackTrueTransactionalSpringRunnerTests extends AbstractTransactionalSpringRunnerTests {
+
+ protected static int originalNumRows;
+
+ protected static SimpleJdbcTemplate simpleJdbcTemplate;
+
+
+ @AfterClass
+ public static void verifyFinalTestData() {
+ assertEquals("Verifying the final number of rows in the person table after all tests.", originalNumRows,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Before
+ public void verifyInitialTestData() {
+ originalNumRows = clearPersonTable(simpleJdbcTemplate);
+ assertEquals("Adding bob", 1, addPerson(simpleJdbcTemplate, BOB));
+ assertEquals("Verifying the initial number of rows in the person table.", 1,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Test(timeout = 1000)
+ @Transactional
+ public void modifyTestDataWithinTransaction() {
+ assertInTransaction(true);
+ assertEquals("Adding jane", 1, addPerson(simpleJdbcTemplate, JANE));
+ assertEquals("Adding sue", 1, addPerson(simpleJdbcTemplate, SUE));
+ assertEquals("Verifying the number of rows in the person table within a transaction.", 3,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+
+ public static class DatabaseSetup {
+
+ @Resource
+ public void setDataSource(DataSource dataSource) {
+ simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ createPersonTable(simpleJdbcTemplate);
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..ca7b3126
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/EnabledAndIgnoredSpringRunnerTests.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.annotation.IfProfileValue;
+import org.springframework.test.annotation.ProfileValueSource;
+import org.springframework.test.annotation.ProfileValueSourceConfiguration;
+import org.springframework.test.context.TestExecutionListeners;
+
+/**
+ * Verifies proper handling of JUnit's {@link Ignore &#064;Ignore} and Spring's
+ * {@link IfProfileValue &#064;IfProfileValue} and
+ * {@link ProfileValueSourceConfiguration &#064;ProfileValueSourceConfiguration}
+ * (with the <em>implicit, default {@link ProfileValueSource}</em>) annotations in
+ * conjunction with the {@link SpringJUnit4ClassRunner}.
+ * <p>
+ * Note that {@link TestExecutionListeners &#064;TestExecutionListeners} is
+ * explicitly configured with an empty list, thus disabling all default
+ * listeners.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see HardCodedProfileValueSourceSpringRunnerTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@TestExecutionListeners( {})
+public class EnabledAndIgnoredSpringRunnerTests {
+
+ protected static final String NAME = "EnabledAndIgnoredSpringRunnerTests.profile_value.name";
+
+ protected static final String VALUE = "enigma";
+
+ protected static int numTestsExecuted = 0;
+
+
+ @BeforeClass
+ public static void setProfileValue() {
+ numTestsExecuted = 0;
+ System.setProperty(NAME, VALUE);
+ }
+
+ @AfterClass
+ public static void verifyNumTestsExecuted() {
+ assertEquals("Verifying the number of tests executed.", 3, numTestsExecuted);
+ }
+
+ @Test
+ @IfProfileValue(name = NAME, value = VALUE + "X")
+ public void testIfProfileValueDisabled() {
+ numTestsExecuted++;
+ fail("The body of a disabled test should never be executed!");
+ }
+
+ @Test
+ @IfProfileValue(name = NAME, value = VALUE)
+ public void testIfProfileValueEnabledViaSingleValue() {
+ numTestsExecuted++;
+ }
+
+ @Test
+ @IfProfileValue(name = NAME, values = { "foo", VALUE, "bar" })
+ public void testIfProfileValueEnabledViaMultipleValues() {
+ numTestsExecuted++;
+ }
+
+ @Test
+ public void testIfProfileValueNotConfigured() {
+ numTestsExecuted++;
+ }
+
+ @Test
+ @Ignore
+ public void testJUnitIgnoreAnnotation() {
+ numTestsExecuted++;
+ fail("The body of an ignored test should never be executed!");
+ }
+
+}
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
new file mode 100644
index 00000000..d9403519
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ExpectedExceptionSpringRunnerTests.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+import java.util.ArrayList;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.JUnit4;
+import org.springframework.test.annotation.ExpectedException;
+import org.springframework.test.context.TestExecutionListeners;
+
+/**
+ * Verifies proper handling of the following in conjunction with the
+ * {@link SpringJUnit4ClassRunner}:
+ * <ul>
+ * <li>JUnit's {@link Test#expected() &#064;Test(expected=...)}</li>
+ * <li>Spring's {@link ExpectedException &#064;ExpectedException}</li>
+ * </ul>
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@SuppressWarnings("deprecation")
+@RunWith(JUnit4.class)
+public class ExpectedExceptionSpringRunnerTests {
+
+ @Test
+ public void expectedExceptions() throws Exception {
+ Class<ExpectedExceptionSpringRunnerTestCase> testClass = ExpectedExceptionSpringRunnerTestCase.class;
+ TrackingRunListener listener = new TrackingRunListener();
+ RunNotifier notifier = new RunNotifier();
+ notifier.addListener(listener);
+
+ new SpringJUnit4ClassRunner(testClass).run(notifier);
+ assertEquals("Verifying number of failures for test class [" + testClass + "].", 1,
+ listener.getTestFailureCount());
+ assertEquals("Verifying number of tests started for test class [" + testClass + "].", 3,
+ listener.getTestStartedCount());
+ assertEquals("Verifying number of tests finished for test class [" + testClass + "].", 3,
+ listener.getTestFinishedCount());
+ }
+
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @RunWith(SpringJUnit4ClassRunner.class)
+ @TestExecutionListeners({})
+ public static final class ExpectedExceptionSpringRunnerTestCase {
+
+ // Should Pass.
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void verifyJUnitExpectedException() {
+ new ArrayList<Object>().get(1);
+ }
+
+ // Should Pass.
+ @Test
+ @ExpectedException(IndexOutOfBoundsException.class)
+ public void verifySpringExpectedException() {
+ new ArrayList<Object>().get(1);
+ }
+
+ // Should Fail due to duplicate configuration.
+ @Test(expected = IllegalStateException.class)
+ @ExpectedException(IllegalStateException.class)
+ public void verifyJUnitAndSpringExpectedException() {
+ new ArrayList<Object>().get(1);
+ }
+
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTests-context.xml
new file mode 100644
index 00000000..3c4fc976
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTests-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
+ p:driverClassName="org.hsqldb.jdbcDriver" p:url="jdbc:hsqldb:mem:transactional_tests" p:username="sa" p:password="" />
+
+ <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:data-source-ref="dataSource" />
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTests.java
new file mode 100644
index 00000000..0f4fb936
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/FailingBeforeAndAfterMethodsTests.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.TestExecutionListener;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.support.AbstractTestExecutionListener;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+
+/**
+ * <p>
+ * JUnit 4 based integration test for verifying that '<i>before</i>' and '<i>after</i>'
+ * methods of {@link TestExecutionListener TestExecutionListeners} as well as
+ * {@link BeforeTransaction &#064;BeforeTransaction} and
+ * {@link AfterTransaction &#064;AfterTransaction} methods can fail a test in a
+ * JUnit 4.4 environment, as requested in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3960"
+ * target="_blank">SPR-3960</a>.
+ * </p>
+ * <p>
+ * Indirectly, this class also verifies that all {@link TestExecutionListener}
+ * lifecycle callbacks are called.
+ * </p>
+ * <p>
+ * As of Spring 3.0, this class also tests support for the new
+ * {@link TestExecutionListener#beforeTestClass(TestContext) beforeTestClass()}
+ * and {@link TestExecutionListener#afterTestClass(TestContext)
+ * afterTestClass()} lifecycle callback methods.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@RunWith(Parameterized.class)
+public class FailingBeforeAndAfterMethodsTests {
+
+ protected final Class<?> clazz;
+
+
+ public FailingBeforeAndAfterMethodsTests(final Class<?> clazz) {
+ this.clazz = clazz;
+ }
+
+ @Parameters
+ public static Collection<Object[]> testData() {
+ return Arrays.asList(new Object[][] {//
+ //
+ { AlwaysFailingBeforeTestClassTestCase.class },//
+ { AlwaysFailingAfterTestClassTestCase.class },//
+ { AlwaysFailingPrepareTestInstanceTestCase.class },//
+ { AlwaysFailingBeforeTestMethodTestCase.class },//
+ { AlwaysFailingAfterTestMethodTestCase.class },//
+ { FailingBeforeTransactionTestCase.class },//
+ { FailingAfterTransactionTestCase.class } //
+ });
+ }
+
+ @Test
+ public void runTestAndAssertCounters() throws Exception {
+ final TrackingRunListener listener = new TrackingRunListener();
+ final RunNotifier notifier = new RunNotifier();
+ notifier.addListener(listener);
+
+ new SpringJUnit4ClassRunner(this.clazz).run(notifier);
+ assertEquals("Verifying number of failures for test class [" + this.clazz + "].", 1,
+ listener.getTestFailureCount());
+ }
+
+
+ // -------------------------------------------------------------------
+
+ static class AlwaysFailingBeforeTestClassTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ public void beforeTestClass(TestContext testContext) {
+ fail("always failing beforeTestClass()");
+ }
+ }
+
+ static class AlwaysFailingAfterTestClassTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ public void afterTestClass(TestContext testContext) {
+ fail("always failing afterTestClass()");
+ }
+ }
+
+ static class AlwaysFailingPrepareTestInstanceTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ public void prepareTestInstance(TestContext testContext) throws Exception {
+ fail("always failing prepareTestInstance()");
+ }
+ }
+
+ static class AlwaysFailingBeforeTestMethodTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ public void beforeTestMethod(TestContext testContext) {
+ fail("always failing beforeTestMethod()");
+ }
+ }
+
+ static class AlwaysFailingAfterTestMethodTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ public void afterTestMethod(TestContext testContext) {
+ fail("always failing afterTestMethod()");
+ }
+ }
+
+ @RunWith(SpringJUnit4ClassRunner.class)
+ @TestExecutionListeners({})
+ public static abstract class BaseTestCase {
+
+ @Test
+ public void testNothing() {
+ }
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @TestExecutionListeners(AlwaysFailingBeforeTestClassTestExecutionListener.class)
+ public static class AlwaysFailingBeforeTestClassTestCase extends BaseTestCase {
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @TestExecutionListeners(AlwaysFailingAfterTestClassTestExecutionListener.class)
+ public static class AlwaysFailingAfterTestClassTestCase extends BaseTestCase {
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @TestExecutionListeners(AlwaysFailingPrepareTestInstanceTestExecutionListener.class)
+ public static class AlwaysFailingPrepareTestInstanceTestCase extends BaseTestCase {
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @TestExecutionListeners(AlwaysFailingBeforeTestMethodTestExecutionListener.class)
+ public static class AlwaysFailingBeforeTestMethodTestCase extends BaseTestCase {
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @TestExecutionListeners(AlwaysFailingAfterTestMethodTestExecutionListener.class)
+ public static class AlwaysFailingAfterTestMethodTestCase extends BaseTestCase {
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml")
+ public static class FailingBeforeTransactionTestCase extends AbstractTransactionalJUnit4SpringContextTests {
+
+ @Test
+ public void testNothing() {
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ fail("always failing beforeTransaction()");
+ }
+ }
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml")
+ public static class FailingAfterTransactionTestCase extends AbstractTransactionalJUnit4SpringContextTests {
+
+ @Test
+ public void testNothing() {
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ fail("always failing afterTransaction()");
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..cea2a29c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/HardCodedProfileValueSourceSpringRunnerTests.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2012 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.BeforeClass;
+import org.springframework.test.annotation.ProfileValueSource;
+import org.springframework.test.annotation.ProfileValueSourceConfiguration;
+
+/**
+ * <p>
+ * Verifies proper handling of JUnit's {@link org.junit.Ignore &#064;Ignore} and
+ * Spring's {@link org.springframework.test.annotation.IfProfileValue
+ * &#064;IfProfileValue} and {@link ProfileValueSourceConfiguration
+ * &#064;ProfileValueSourceConfiguration} (with an
+ * <em>explicit, custom defined {@link ProfileValueSource}</em>) annotations in
+ * conjunction with the {@link SpringJUnit4ClassRunner}.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see EnabledAndIgnoredSpringRunnerTests
+ */
+@ProfileValueSourceConfiguration(HardCodedProfileValueSourceSpringRunnerTests.HardCodedProfileValueSource.class)
+public class HardCodedProfileValueSourceSpringRunnerTests extends EnabledAndIgnoredSpringRunnerTests {
+
+ @BeforeClass
+ public static void setProfileValue() {
+ numTestsExecuted = 0;
+ // Set the system property to something other than VALUE as a sanity
+ // check.
+ System.setProperty(NAME, "999999999999");
+ }
+
+
+ public static class HardCodedProfileValueSource implements ProfileValueSource {
+
+ @Override
+ public String get(final String key) {
+ return (key.equals(NAME) ? VALUE : null);
+ }
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/InheritedConfigSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/InheritedConfigSpringJUnit4ClassRunnerAppCtxTests.java
new file mode 100644
index 00000000..fec86ee6
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/InheritedConfigSpringJUnit4ClassRunnerAppCtxTests.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2007 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.lang.annotation.Inherited;
+
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * Extension of {@link SpringJUnit4ClassRunnerAppCtxTests} which verifies that
+ * the configuration of an application context and dependency injection of a
+ * test instance function as expected within a class hierarchy, since
+ * {@link ContextConfiguration configuration} is {@link Inherited inherited}.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see SpringJUnit4ClassRunnerAppCtxTests
+ */
+public class InheritedConfigSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests {
+ /* all tests are in the parent class. */
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests-context.xml
new file mode 100644
index 00000000..c9e6fa05
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests-context.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="databaseSetup"
+ class="org.springframework.test.context.junit4.MethodLevelTransactionalSpringRunnerTests$DatabaseSetup" />
+
+</beans>
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
new file mode 100644
index 00000000..6cc35c64
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/MethodLevelTransactionalSpringRunnerTests.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestExecutionListener;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
+import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * <p>
+ * JUnit 4 based integration test which verifies support of Spring's
+ * {@link Transactional &#64;Transactional}, {@link TestExecutionListeners
+ * &#64;TestExecutionListeners}, and {@link ContextConfiguration
+ * &#64;ContextConfiguration} annotations in conjunction with the
+ * {@link SpringJUnit4ClassRunner} and the following
+ * {@link TestExecutionListener TestExecutionListeners}:
+ * </p>
+ * <ul>
+ * <li>{@link DependencyInjectionTestExecutionListener}</li>
+ * <li>{@link DirtiesContextTestExecutionListener}</li>
+ * <li>{@link TransactionalTestExecutionListener}</li>
+ * </ul>
+ * <p>
+ * This class specifically tests usage of {@code &#064;Transactional}
+ * defined at the <strong>method level</strong>. In contrast to
+ * {@link ClassLevelTransactionalSpringRunnerTests}, this class omits usage of
+ * {@code &#064;NotTransactional}.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see ClassLevelTransactionalSpringRunnerTests
+ */
+@SuppressWarnings("deprecation")
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
+ TransactionalTestExecutionListener.class })
+public class MethodLevelTransactionalSpringRunnerTests extends AbstractTransactionalSpringRunnerTests {
+
+ protected static SimpleJdbcTemplate simpleJdbcTemplate;
+
+
+ @AfterClass
+ public static void verifyFinalTestData() {
+ assertEquals("Verifying the final number of rows in the person table after all tests.", 4,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Before
+ public void verifyInitialTestData() {
+ clearPersonTable(simpleJdbcTemplate);
+ assertEquals("Adding bob", 1, addPerson(simpleJdbcTemplate, BOB));
+ assertEquals("Verifying the initial number of rows in the person table.", 1,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Test
+ @Transactional("transactionManager2")
+ public void modifyTestDataWithinTransaction() {
+ assertInTransaction(true);
+ assertEquals("Deleting bob", 1, deletePerson(simpleJdbcTemplate, BOB));
+ assertEquals("Adding jane", 1, addPerson(simpleJdbcTemplate, JANE));
+ assertEquals("Adding sue", 1, addPerson(simpleJdbcTemplate, SUE));
+ assertEquals("Verifying the number of rows in the person table within a transaction.", 2,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Test
+ public void modifyTestDataWithoutTransaction() {
+ assertInTransaction(false);
+ assertEquals("Adding luke", 1, addPerson(simpleJdbcTemplate, LUKE));
+ assertEquals("Adding leia", 1, addPerson(simpleJdbcTemplate, LEIA));
+ assertEquals("Adding yoda", 1, addPerson(simpleJdbcTemplate, YODA));
+ assertEquals("Verifying the number of rows in the person table without a transaction.", 4,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+
+ public static class DatabaseSetup {
+
+ @Resource
+ public void setDataSource2(DataSource dataSource) {
+ simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ createPersonTable(simpleJdbcTemplate);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context1.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context1.xml
new file mode 100644
index 00000000..b40fed69
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context1.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="employee" class="org.springframework.tests.sample.beans.Employee">
+ <property name="name" value="John Smith" />
+ <property name="age" value="42" />
+ <property name="company" value="Acme Widgets, Inc." />
+ </bean>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context2.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context2.xml
new file mode 100644
index 00000000..f6a90a62
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context2.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="pet" class="org.springframework.tests.sample.beans.Pet">
+ <constructor-arg value="Fido" />
+ </bean>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context3.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context3.xml
new file mode 100644
index 00000000..d8c31a18
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context3.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="foo" class="java.lang.String">
+ <constructor-arg value="Foo" />
+ </bean>
+
+ <bean id="bar" class="java.lang.String">
+ <constructor-arg value="Bar" />
+ </bean>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.java
new file mode 100644
index 00000000..a77f7239
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2012 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.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that
+ * we can specify multiple resource locations for our application context, each
+ * configured differently.
+ * <p>
+ * As of Spring 3.0,
+ * {@code MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests} is also used
+ * to verify support for the new {@code value} attribute alias for
+ * {@code &#064;ContextConfiguration}'s {@code locations} attribute.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see SpringJUnit4ClassRunnerAppCtxTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration( { MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.CLASSPATH_RESOURCE_PATH,
+ MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.LOCAL_RESOURCE_PATH,
+ MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.ABSOLUTE_RESOURCE_PATH })
+public class MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests {
+
+ public static final String CLASSPATH_RESOURCE_PATH = ResourceUtils.CLASSPATH_URL_PREFIX
+ + "/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context1.xml";
+ public static final String LOCAL_RESOURCE_PATH = "MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context2.xml";
+ public static final String ABSOLUTE_RESOURCE_PATH = "/org/springframework/test/context/junit4/MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests-context3.xml";
+
+ /* all tests are in the parent class. */
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ParameterizedDependencyInjectionTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/ParameterizedDependencyInjectionTests-context.xml
new file mode 100644
index 00000000..67590a6b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ParameterizedDependencyInjectionTests-context.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="employee1" class="org.springframework.tests.sample.beans.Employee">
+ <property name="name" value="John Smith" />
+ <property name="age" value="42" />
+ <property name="company" value="Acme Widgets, Inc." />
+ </bean>
+
+ <bean id="employee2" class="org.springframework.tests.sample.beans.Employee">
+ <property name="name" value="Jane Smith" />
+ <property name="age" value="38" />
+ <property name="company" value="Acme Widgets, Inc." />
+ </bean>
+
+ <bean id="pet" class="org.springframework.tests.sample.beans.Pet">
+ <constructor-arg value="Fido" />
+ </bean>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/ParameterizedDependencyInjectionTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/ParameterizedDependencyInjectionTests.java
new file mode 100644
index 00000000..bf079197
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/ParameterizedDependencyInjectionTests.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestContextManager;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+
+/**
+ * Simple JUnit 4 based integration test which demonstrates how to use JUnit's
+ * {@link Parameterized} Runner in conjunction with
+ * {@link ContextConfiguration @ContextConfiguration}, the
+ * {@link DependencyInjectionTestExecutionListener}, and a
+ * {@link TestContextManager} to provide dependency injection to a
+ * <em>parameterized test instance</em>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@RunWith(Parameterized.class)
+@ContextConfiguration
+@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class })
+public class ParameterizedDependencyInjectionTests {
+
+ private static final List<Employee> employees = new ArrayList<Employee>();
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Autowired
+ private Pet pet;
+
+ private final String employeeBeanName;
+ private final String employeeName;
+
+ private final TestContextManager testContextManager;
+
+
+ public ParameterizedDependencyInjectionTests(final String employeeBeanName, final String employeeName)
+ throws Exception {
+ this.testContextManager = new TestContextManager(getClass());
+ this.employeeBeanName = employeeBeanName;
+ this.employeeName = employeeName;
+ }
+
+ @Parameters
+ public static Collection<String[]> employeeData() {
+ return Arrays.asList(new String[][] { { "employee1", "John Smith" }, { "employee2", "Jane Smith" } });
+ }
+
+ @BeforeClass
+ public static void clearEmployees() {
+ employees.clear();
+ }
+
+ @Before
+ public void injectDependencies() throws Throwable {
+ this.testContextManager.prepareTestInstance(this);
+ }
+
+ @Test
+ public final void verifyPetAndEmployee() {
+
+ // Verifying dependency injection:
+ assertNotNull("The pet field should have been autowired.", this.pet);
+
+ // Verifying 'parameterized' support:
+ final Employee employee = (Employee) this.applicationContext.getBean(this.employeeBeanName);
+ employees.add(employee);
+ assertEquals("Verifying the name of the employee configured as bean [" + this.employeeBeanName + "].",
+ this.employeeName, employee.getName());
+ }
+
+ @AfterClass
+ public static void verifyNumParameterizedRuns() {
+ assertEquals("Verifying the number of times the parameterized test method was executed.",
+ employeeData().size(), employees.size());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties b/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties
new file mode 100644
index 00000000..6df81585
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties
@@ -0,0 +1,5 @@
+cat.(class)=org.springframework.tests.sample.beans.Pet
+cat.$0=Garfield
+
+testString.(class)=java.lang.String
+testString.$0=Test String
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
new file mode 100644
index 00000000..fd82c559
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Properties;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.GenericPropertiesContextLoader;
+
+/**
+ * <p>
+ * JUnit 4 based test class, which verifies the expected functionality of
+ * {@link SpringJUnit4ClassRunner} 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
+ * {@link org.springframework.test.context.support.GenericPropertiesContextLoader#getResourceSuffix()
+ * resourceSuffix} property of {@code GenericPropertiesContextLoader} are tested.
+ * </p>
+ * <p>
+ * Since no {@link ContextConfiguration#locations() locations} are explicitly defined, the
+ * {@code resourceSuffix} is set to &quot;-context.properties&quot;, and since default
+ * resource locations will be detected by default, this test class's dependencies will be
+ * injected via {@link Autowired annotation-based autowiring} from beans defined in the
+ * {@link ApplicationContext} loaded from the default classpath resource: &quot;
+ * {@code /org/springframework/test/junit4/PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests-context.properties}
+ * &quot;.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see GenericPropertiesContextLoader
+ * @see SpringJUnit4ClassRunnerAppCtxTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(loader = GenericPropertiesContextLoader.class)
+public class PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests {
+
+ @Autowired
+ private Pet cat;
+
+ @Autowired
+ private String testString;
+
+
+ @Test
+ public void verifyAnnotationAutowiredFields() {
+ assertNotNull("The cat field should have been autowired.", this.cat);
+ assertEquals("Garfield", this.cat.getName());
+
+ assertNotNull("The testString field should have been autowired.", this.testString);
+ assertEquals("Test String", this.testString);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RelativePathSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/RelativePathSpringJUnit4ClassRunnerAppCtxTests.java
new file mode 100644
index 00000000..478874a7
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/RelativePathSpringJUnit4ClassRunnerAppCtxTests.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2007 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.runner.RunWith;
+
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * Extension of {@link SpringJUnit4ClassRunnerAppCtxTests}, which verifies that
+ * we can specify an explicit, <em>relative path</em> location for our
+ * application context.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see SpringJUnit4ClassRunnerAppCtxTests
+ * @see AbsolutePathSpringJUnit4ClassRunnerAppCtxTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = { "SpringJUnit4ClassRunnerAppCtxTests-context.xml" })
+public class RelativePathSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests {
+ /* all tests are in the parent class. */
+}
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
new file mode 100644
index 00000000..6b724126
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.springframework.test.annotation.Repeat;
+import org.springframework.test.annotation.Timed;
+import org.springframework.test.context.TestExecutionListeners;
+
+/**
+ * Verifies proper handling of the following in conjunction with the
+ * {@link SpringJUnit4ClassRunner}:
+ * <ul>
+ * <li>Spring's {@link Repeat &#064;Repeat}</li>
+ * <li>Spring's {@link Timed &#064;Timed}</li>
+ * </ul>
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@RunWith(Parameterized.class)
+public class RepeatedSpringRunnerTests {
+
+ private static final AtomicInteger invocationCount = new AtomicInteger();
+
+ private final Class<? extends AbstractRepeatedTestCase> testClass;
+
+ private final int expectedFailureCount;
+
+ private final int expectedTestStartedCount;
+
+ private final int expectedTestFinishedCount;
+
+ private final int expectedInvocationCount;
+
+
+ public RepeatedSpringRunnerTests(Class<? extends AbstractRepeatedTestCase> testClass, int expectedFailureCount,
+ int expectedTestStartedCount, int expectedTestFinishedCount, int expectedInvocationCount) {
+ this.testClass = testClass;
+ this.expectedFailureCount = expectedFailureCount;
+ this.expectedTestStartedCount = expectedTestStartedCount;
+ this.expectedTestFinishedCount = expectedTestFinishedCount;
+ this.expectedInvocationCount = expectedInvocationCount;
+ }
+
+ @Parameters
+ public static Collection<Object[]> repetitionData() {
+ return Arrays.asList(new Object[][] {//
+ //
+ { NonAnnotatedRepeatedTestCase.class, 0, 1, 1, 1 },//
+ { DefaultRepeatValueRepeatedTestCase.class, 0, 1, 1, 1 },//
+ { NegativeRepeatValueRepeatedTestCase.class, 0, 1, 1, 1 },//
+ { RepeatedFiveTimesRepeatedTestCase.class, 0, 1, 1, 5 },//
+ { TimedRepeatedTestCase.class, 3, 4, 4, (5 + 1 + 4 + 10) } //
+ });
+ }
+
+ @Test
+ public void assertRepetitions() throws Exception {
+ TrackingRunListener listener = new TrackingRunListener();
+ RunNotifier notifier = new RunNotifier();
+ notifier.addListener(listener);
+ invocationCount.set(0);
+
+ new SpringJUnit4ClassRunner(this.testClass).run(notifier);
+ assertEquals("Verifying number of failures for test class [" + this.testClass + "].",
+ this.expectedFailureCount, listener.getTestFailureCount());
+ assertEquals("Verifying number of tests started for test class [" + this.testClass + "].",
+ this.expectedTestStartedCount, listener.getTestStartedCount());
+ assertEquals("Verifying number of tests finished for test class [" + this.testClass + "].",
+ this.expectedTestFinishedCount, listener.getTestFinishedCount());
+ assertEquals("Verifying number of invocations for test class [" + this.testClass + "].",
+ this.expectedInvocationCount, invocationCount.get());
+ }
+
+
+ @RunWith(SpringJUnit4ClassRunner.class)
+ @TestExecutionListeners({})
+ public abstract static class AbstractRepeatedTestCase {
+
+ protected void incrementInvocationCount() throws IOException {
+ invocationCount.incrementAndGet();
+ }
+ }
+
+ public static final class NonAnnotatedRepeatedTestCase extends AbstractRepeatedTestCase {
+
+ @Test
+ @Timed(millis = 10000)
+ public void nonAnnotated() throws Exception {
+ incrementInvocationCount();
+ }
+ }
+
+ public static final class DefaultRepeatValueRepeatedTestCase extends AbstractRepeatedTestCase {
+
+ @Test
+ @Repeat
+ @Timed(millis = 10000)
+ public void defaultRepeatValue() throws Exception {
+ incrementInvocationCount();
+ }
+ }
+
+ public static final class NegativeRepeatValueRepeatedTestCase extends AbstractRepeatedTestCase {
+
+ @Test
+ @Repeat(-5)
+ @Timed(millis = 10000)
+ public void negativeRepeatValue() throws Exception {
+ incrementInvocationCount();
+ }
+ }
+
+ public static final class RepeatedFiveTimesRepeatedTestCase extends AbstractRepeatedTestCase {
+
+ @Test
+ @Repeat(5)
+ public void repeatedFiveTimes() throws Exception {
+ incrementInvocationCount();
+ }
+ }
+
+ /**
+ * Unit tests for claims raised in <a
+ * href="http://jira.springframework.org/browse/SPR-6011"
+ * target="_blank">SPR-6011</a>.
+ */
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ public static final class TimedRepeatedTestCase extends AbstractRepeatedTestCase {
+
+ @Test
+ @Timed(millis = 1000)
+ @Repeat(5)
+ public void repeatedFiveTimesButDoesNotExceedTimeout() throws Exception {
+ incrementInvocationCount();
+ }
+
+ @Test
+ @Timed(millis = 10)
+ @Repeat(1)
+ public void singleRepetitionExceedsTimeout() throws Exception {
+ incrementInvocationCount();
+ Thread.sleep(15);
+ }
+
+ @Test
+ @Timed(millis = 20)
+ @Repeat(4)
+ public void firstRepetitionOfManyExceedsTimeout() throws Exception {
+ incrementInvocationCount();
+ Thread.sleep(25);
+ }
+
+ @Test
+ @Timed(millis = 100)
+ @Repeat(10)
+ public void collectiveRepetitionsExceedTimeout() throws Exception {
+ incrementInvocationCount();
+ Thread.sleep(11);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseTransactionalSpringRunnerTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseTransactionalSpringRunnerTests-context.xml
new file mode 100644
index 00000000..44a8ae54
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseTransactionalSpringRunnerTests-context.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
+ p:driverClassName="org.hsqldb.jdbcDriver" p:url="jdbc:hsqldb:mem:transactional_tests" p:username="sa" p:password="" />
+
+ <bean id="txMgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:data-source-ref="dataSource" />
+
+ <bean id="databaseSetup"
+ class="org.springframework.test.context.junit4.RollbackOverrideDefaultRollbackFalseTransactionalSpringRunnerTests$DatabaseSetup" />
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseTransactionalSpringRunnerTests.java
new file mode 100644
index 00000000..231ea884
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackFalseTransactionalSpringRunnerTests.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2002-2008 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 static org.junit.Assert.assertEquals;
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * Extension of {@link DefaultRollbackFalseTransactionalSpringRunnerTests} which
+ * tests method-level <em>rollback override</em> behavior via the
+ * {@link Rollback @Rollback} annotation.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see Rollback
+ */
+@SuppressWarnings("deprecation")
+@ContextConfiguration
+public class RollbackOverrideDefaultRollbackFalseTransactionalSpringRunnerTests extends
+ DefaultRollbackFalseTransactionalSpringRunnerTests {
+
+ protected static int originalNumRows;
+
+ protected static SimpleJdbcTemplate simpleJdbcTemplate;
+
+
+ @AfterClass
+ public static void verifyFinalTestData() {
+ assertEquals("Verifying the final number of rows in the person table after all tests.", originalNumRows,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Before
+ @Override
+ public void verifyInitialTestData() {
+ originalNumRows = clearPersonTable(simpleJdbcTemplate);
+ assertEquals("Adding bob", 1, addPerson(simpleJdbcTemplate, BOB));
+ assertEquals("Verifying the initial number of rows in the person table.", 1,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Test
+ @Rollback(true)
+ @Override
+ public void modifyTestDataWithinTransaction() {
+ assertInTransaction(true);
+ assertEquals("Deleting bob", 1, deletePerson(simpleJdbcTemplate, BOB));
+ assertEquals("Adding jane", 1, addPerson(simpleJdbcTemplate, JANE));
+ assertEquals("Adding sue", 1, addPerson(simpleJdbcTemplate, SUE));
+ assertEquals("Verifying the number of rows in the person table within a transaction.", 2,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+
+ public static class DatabaseSetup {
+
+ @Resource
+ public void setDataSource(DataSource dataSource) {
+ simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ createPersonTable(simpleJdbcTemplate);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueTransactionalSpringRunnerTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueTransactionalSpringRunnerTests-context.xml
new file mode 100644
index 00000000..cda53c56
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueTransactionalSpringRunnerTests-context.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="databaseSetup"
+ class="org.springframework.test.context.junit4.RollbackOverrideDefaultRollbackTrueTransactionalSpringRunnerTests$DatabaseSetup" />
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueTransactionalSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueTransactionalSpringRunnerTests.java
new file mode 100644
index 00000000..a662b7b8
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/RollbackOverrideDefaultRollbackTrueTransactionalSpringRunnerTests.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Extension of {@link DefaultRollbackTrueTransactionalSpringRunnerTests} which
+ * tests method-level <em>rollback override</em> behavior via the
+ * {@link Rollback @Rollback} annotation.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see Rollback
+ */
+@SuppressWarnings("deprecation")
+@ContextConfiguration
+public class RollbackOverrideDefaultRollbackTrueTransactionalSpringRunnerTests extends
+ DefaultRollbackTrueTransactionalSpringRunnerTests {
+
+ protected static SimpleJdbcTemplate simpleJdbcTemplate;
+
+
+ @AfterClass
+ public static void verifyFinalTestData() {
+ assertEquals("Verifying the final number of rows in the person table after all tests.", 3,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Override
+ @Before
+ public void verifyInitialTestData() {
+ clearPersonTable(simpleJdbcTemplate);
+ assertEquals("Adding bob", 1, addPerson(simpleJdbcTemplate, BOB));
+ assertEquals("Verifying the initial number of rows in the person table.", 1,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+ @Override
+ @Test
+ @Transactional
+ @Rollback(false)
+ public void modifyTestDataWithinTransaction() {
+ assertInTransaction(true);
+ assertEquals("Adding jane", 1, addPerson(simpleJdbcTemplate, JANE));
+ assertEquals("Adding sue", 1, addPerson(simpleJdbcTemplate, SUE));
+ assertEquals("Verifying the number of rows in the person table within a transaction.", 3,
+ countRowsInPersonTable(simpleJdbcTemplate));
+ }
+
+
+ public static class DatabaseSetup {
+
+ @Resource
+ public void setDataSource(DataSource dataSource) {
+ simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
+ createPersonTable(simpleJdbcTemplate);
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..3229df93
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit47ClassRunnerRuleTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.assertEquals;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.TestExecutionListeners;
+
+/**
+ * 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.
+ *
+ * @author JUnit 4.7 Team
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@TestExecutionListeners( {})
+public class SpringJUnit47ClassRunnerRuleTests {
+
+ @Rule
+ public TestName name = new TestName();
+
+
+ @Test
+ public void testA() {
+ assertEquals("testA", name.getMethodName());
+ }
+
+ @Test
+ public void testB() {
+ assertEquals("testB", name.getMethodName());
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml
new file mode 100644
index 00000000..704b3478
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="employee" class="org.springframework.tests.sample.beans.Employee">
+ <property name="name" value="John Smith" />
+ <property name="age" value="42" />
+ <property name="company" value="Acme Widgets, Inc." />
+ </bean>
+
+ <bean id="pet" class="org.springframework.tests.sample.beans.Pet">
+ <constructor-arg value="Fido" />
+ </bean>
+
+ <bean id="foo" class="java.lang.String">
+ <constructor-arg value="Foo" />
+ </bean>
+
+ <bean id="bar" class="java.lang.String">
+ <constructor-arg value="Bar" />
+ </bean>
+
+ <bean id="quux" class="java.lang.String">
+ <constructor-arg value="Quux" />
+ </bean>
+
+</beans>
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
new file mode 100644
index 00000000..4973d7c6
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.BeansException;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+import org.springframework.test.context.support.GenericXmlContextLoader;
+
+/**
+ * <p>
+ * 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:
+ * </p>
+ * <ul>
+ * <li>{@link ContextConfiguration @ContextConfiguration}</li>
+ * <li>{@link Autowired @Autowired}</li>
+ * <li>{@link Qualifier @Qualifier}</li>
+ * <li>{@link Resource @Resource}</li>
+ * <li>{@link Value @Value}</li>
+ * <li>{@link Inject @Inject}</li>
+ * <li>{@link Named @Named}</li>
+ * <li>{@link ApplicationContextAware}</li>
+ * <li>{@link BeanNameAware}</li>
+ * <li>{@link InitializingBean}</li>
+ * </ul>
+ * <p>
+ * Since no application context resource
+ * {@link ContextConfiguration#locations() locations} are explicitly declared
+ * and since the {@link ContextConfiguration#loader() ContextLoader} is left set
+ * to the default value of {@link GenericXmlContextLoader}, this test class's
+ * dependencies will be injected via {@link Autowired @Autowired},
+ * {@link Inject @Inject}, and {@link Resource @Resource} from beans defined in
+ * the {@link ApplicationContext} loaded from the default classpath resource:
+ *
+ * {@code &quot;/org/springframework/test/context/junit/SpringJUnit4ClassRunnerAppCtxTests-context.xml&quot;}
+ * .
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see AbsolutePathSpringJUnit4ClassRunnerAppCtxTests
+ * @see RelativePathSpringJUnit4ClassRunnerAppCtxTests
+ * @see InheritedConfigSpringJUnit4ClassRunnerAppCtxTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class })
+public class SpringJUnit4ClassRunnerAppCtxTests implements ApplicationContextAware, BeanNameAware, InitializingBean {
+
+ /**
+ * Default resource path for the application context configuration for
+ * {@link SpringJUnit4ClassRunnerAppCtxTests}:
+ *
+ * {@code &quot;/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml&quot;}
+ */
+ public static final String DEFAULT_CONTEXT_RESOURCE_PATH = "/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml";
+
+ private ApplicationContext applicationContext;
+
+ private boolean beanInitialized = false;
+
+ private String beanName = "replace me with [" + getClass().getName() + "]";
+
+ private Employee employee;
+
+ @Autowired
+ private Pet autowiredPet;
+
+ @Inject
+ private Pet injectedPet;
+
+ @Autowired(required = false)
+ protected Long nonrequiredLong;
+
+ @Resource
+ protected String foo;
+
+ protected String bar;
+
+ @Value("enigma")
+ private String literalFieldValue;
+
+ @Value("#{2 == (1+1)}")
+ private Boolean spelFieldValue;
+
+ private String literalParameterValue;
+
+ private Boolean spelParameterValue;
+
+ @Autowired
+ @Qualifier("quux")
+ protected String quux;
+
+ @Inject
+ @Named("quux")
+ protected String namedQuux;
+
+
+ // ------------------------------------------------------------------------|
+
+ @Override
+ public final void afterPropertiesSet() throws Exception {
+ this.beanInitialized = true;
+ }
+
+ @Override
+ public final void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+ @Override
+ public final void setBeanName(final String beanName) {
+ this.beanName = beanName;
+ }
+
+ @Autowired
+ protected final void setEmployee(final Employee employee) {
+ this.employee = employee;
+ }
+
+ @Resource
+ protected final void setBar(final String bar) {
+ this.bar = bar;
+ }
+
+ @Autowired
+ public void setLiteralParameterValue(@Value("enigma") String literalParameterValue) {
+ this.literalParameterValue = literalParameterValue;
+ }
+
+ @Autowired
+ public void setSpelParameterValue(@Value("#{2 == (1+1)}") Boolean spelParameterValue) {
+ this.spelParameterValue = spelParameterValue;
+ }
+
+ // ------------------------------------------------------------------------|
+
+ @Test
+ public final void verifyApplicationContextSet() {
+ assertNotNull("The application context should have been set due to ApplicationContextAware semantics.",
+ this.applicationContext);
+ }
+
+ @Test
+ public final void verifyBeanInitialized() {
+ assertTrue("This test bean should have been initialized due to InitializingBean semantics.",
+ this.beanInitialized);
+ }
+
+ @Test
+ public final void verifyBeanNameSet() {
+ assertEquals("The bean name of this test instance should have been set due to BeanNameAware semantics.",
+ getClass().getName(), this.beanName);
+ }
+
+ @Test
+ public final void verifyAnnotationAutowiredAndInjectedFields() {
+ assertNull("The nonrequiredLong field should NOT have been autowired.", this.nonrequiredLong);
+ assertEquals("The quux field should have been autowired via @Autowired and @Qualifier.", "Quux", this.quux);
+ assertEquals("The namedFoo field should have been injected via @Inject and @Named.", "Quux", this.namedQuux);
+ assertSame("@Autowired/@Qualifier and @Inject/@Named quux should be the same object.", this.quux,
+ this.namedQuux);
+
+ assertNotNull("The pet field should have been autowired.", this.autowiredPet);
+ assertNotNull("The pet field should have been injected.", this.injectedPet);
+ assertEquals("Fido", this.autowiredPet.getName());
+ assertEquals("Fido", this.injectedPet.getName());
+ assertSame("@Autowired and @Inject pet should be the same object.", this.autowiredPet, this.injectedPet);
+ }
+
+ @Test
+ public final void verifyAnnotationAutowiredMethods() {
+ assertNotNull("The employee setter method should have been autowired.", this.employee);
+ assertEquals("John Smith", this.employee.getName());
+ }
+
+ @Test
+ public final void verifyAutowiredAtValueFields() {
+ assertNotNull("Literal @Value field should have been autowired", this.literalFieldValue);
+ assertNotNull("SpEL @Value field should have been autowired.", this.spelFieldValue);
+ assertEquals("enigma", this.literalFieldValue);
+ assertEquals(Boolean.TRUE, this.spelFieldValue);
+ }
+
+ @Test
+ public final void verifyAutowiredAtValueMethods() {
+ assertNotNull("Literal @Value method parameter should have been autowired.", this.literalParameterValue);
+ assertNotNull("SpEL @Value method parameter should have been autowired.", this.spelParameterValue);
+ assertEquals("enigma", this.literalParameterValue);
+ assertEquals(Boolean.TRUE, this.spelParameterValue);
+ }
+
+ @Test
+ public final void verifyResourceAnnotationInjectedFields() {
+ assertEquals("The foo field should have been injected via @Resource.", "Foo", this.foo);
+ }
+
+ @Test
+ public final void verifyResourceAnnotationInjectedMethods() {
+ assertEquals("The bar method should have been wired via @Resource.", "Bar", this.bar);
+ }
+
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerTests.java
new file mode 100644
index 00000000..aedd5ad4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerTests.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2007 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.springframework.test.context.TestContextManager;
+
+/**
+ * @author Rick Evans
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class SpringJUnit4ClassRunnerTests {
+
+ @Test(expected = Exception.class)
+ public void checkThatExceptionsAreNotSilentlySwallowed() throws Exception {
+ SpringJUnit4ClassRunner runner = new SpringJUnit4ClassRunner(getClass()) {
+
+ @Override
+ protected TestContextManager createTestContextManager(Class<?> clazz) {
+ return new TestContextManager(clazz) {
+
+ @Override
+ public void prepareTestInstance(Object testInstance) {
+ throw new RuntimeException("This RuntimeException should be caught and wrapped in an Exception.");
+ }
+ };
+ }
+ };
+ runner.createTest();
+ }
+
+}
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
new file mode 100644
index 00000000..72ddd1b8
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+import org.springframework.test.context.ClassLevelDirtiesContextTests;
+import org.springframework.test.context.SpringRunnerContextCacheTests;
+import org.springframework.test.context.junit4.annotation.AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests;
+import org.springframework.test.context.junit4.annotation.BeanOverridingDefaultConfigClassesInheritedTests;
+import org.springframework.test.context.junit4.annotation.BeanOverridingExplicitConfigClassesInheritedTests;
+import org.springframework.test.context.junit4.annotation.DefaultConfigClassesBaseTests;
+import org.springframework.test.context.junit4.annotation.DefaultConfigClassesInheritedTests;
+import org.springframework.test.context.junit4.annotation.DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests;
+import org.springframework.test.context.junit4.annotation.DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests;
+import org.springframework.test.context.junit4.annotation.DefaultLoaderDefaultConfigClassesBaseTests;
+import org.springframework.test.context.junit4.annotation.DefaultLoaderDefaultConfigClassesInheritedTests;
+import org.springframework.test.context.junit4.annotation.DefaultLoaderExplicitConfigClassesBaseTests;
+import org.springframework.test.context.junit4.annotation.DefaultLoaderExplicitConfigClassesInheritedTests;
+import org.springframework.test.context.junit4.annotation.ExplicitConfigClassesBaseTests;
+import org.springframework.test.context.junit4.annotation.ExplicitConfigClassesInheritedTests;
+import org.springframework.test.context.junit4.orm.HibernateSessionFlushingTests;
+import org.springframework.test.context.junit4.profile.annotation.DefaultProfileAnnotationConfigTests;
+import org.springframework.test.context.junit4.profile.annotation.DevProfileAnnotationConfigTests;
+import org.springframework.test.context.junit4.profile.xml.DefaultProfileXmlConfigTests;
+import org.springframework.test.context.junit4.profile.xml.DevProfileXmlConfigTests;
+
+/**
+ * JUnit test suite for tests involving {@link SpringJUnit4ClassRunner} 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 Suite} runner.
+ *
+ * <p>Note that tests included in this suite will be executed at least twice if
+ * run from an automated build process, test runner, etc. that is not configured
+ * to exclude tests based on a &quot;*TestSuite.class&quot; pattern match.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@RunWith(Suite.class)
+// Note: the following 'multi-line' layout is for enhanced code readability.
+@SuiteClasses({//
+StandardJUnit4FeaturesTests.class,//
+ StandardJUnit4FeaturesSpringRunnerTests.class,//
+ SpringJUnit47ClassRunnerRuleTests.class,//
+ AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.class,//
+ DefaultConfigClassesBaseTests.class,//
+ DefaultConfigClassesInheritedTests.class,//
+ BeanOverridingDefaultConfigClassesInheritedTests.class,//
+ ExplicitConfigClassesBaseTests.class,//
+ ExplicitConfigClassesInheritedTests.class,//
+ BeanOverridingExplicitConfigClassesInheritedTests.class,//
+ DefaultLoaderDefaultConfigClassesBaseTests.class,//
+ DefaultLoaderDefaultConfigClassesInheritedTests.class,//
+ DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.class,//
+ DefaultLoaderExplicitConfigClassesBaseTests.class,//
+ DefaultLoaderExplicitConfigClassesInheritedTests.class,//
+ DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.class,//
+ DefaultProfileAnnotationConfigTests.class,//
+ DevProfileAnnotationConfigTests.class,//
+ DefaultProfileXmlConfigTests.class,//
+ DevProfileXmlConfigTests.class,//
+ ExpectedExceptionSpringRunnerTests.class,//
+ TimedSpringRunnerTests.class,//
+ RepeatedSpringRunnerTests.class,//
+ EnabledAndIgnoredSpringRunnerTests.class,//
+ HardCodedProfileValueSourceSpringRunnerTests.class,//
+ SpringJUnit4ClassRunnerAppCtxTests.class,//
+ ClassPathResourceSpringJUnit4ClassRunnerAppCtxTests.class,//
+ AbsolutePathSpringJUnit4ClassRunnerAppCtxTests.class,//
+ RelativePathSpringJUnit4ClassRunnerAppCtxTests.class,//
+ MultipleResourcesSpringJUnit4ClassRunnerAppCtxTests.class,//
+ InheritedConfigSpringJUnit4ClassRunnerAppCtxTests.class,//
+ PropertiesBasedSpringJUnit4ClassRunnerAppCtxTests.class,//
+ CustomDefaultContextLoaderClassSpringRunnerTests.class,//
+ SpringRunnerContextCacheTests.class,//
+ ClassLevelDirtiesContextTests.class,//
+ ParameterizedDependencyInjectionTests.class,//
+ ClassLevelTransactionalSpringRunnerTests.class,//
+ MethodLevelTransactionalSpringRunnerTests.class,//
+ DefaultRollbackTrueTransactionalSpringRunnerTests.class,//
+ DefaultRollbackFalseTransactionalSpringRunnerTests.class,//
+ RollbackOverrideDefaultRollbackTrueTransactionalSpringRunnerTests.class,//
+ RollbackOverrideDefaultRollbackFalseTransactionalSpringRunnerTests.class,//
+ BeforeAndAfterTransactionAnnotationTests.class,//
+ TimedTransactionalSpringRunnerTests.class,//
+ HibernateSessionFlushingTests.class //
+})
+public class SpringJUnit4TestSuite {
+ /* this test case consists entirely of tests loaded as a suite. */
+}
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
new file mode 100644
index 00000000..4dd270ca
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesSpringRunnerTests.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2008 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.runner.RunWith;
+
+import org.springframework.test.context.TestExecutionListeners;
+
+/**
+ * <p>
+ * Simple unit test to verify that {@link SpringJUnit4ClassRunner} does not
+ * hinder correct functionality of standard JUnit 4.4+ testing features.
+ * </p>
+ * <p>
+ * Note that {@link TestExecutionListeners @TestExecutionListeners} is
+ * explicitly configured with an empty list, thus disabling all default
+ * listeners.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see StandardJUnit4FeaturesTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@TestExecutionListeners({})
+public class StandardJUnit4FeaturesSpringRunnerTests extends StandardJUnit4FeaturesTests {
+
+ /* All tests are in the parent class... */
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesTests.java
new file mode 100644
index 00000000..3fd0aac6
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/StandardJUnit4FeaturesTests.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2002-2007 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import java.util.ArrayList;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Simple unit test to verify the expected functionality of standard JUnit 4.4+
+ * testing features.
+ * <p>
+ * Currently testing: {@link Test @Test} (including expected exceptions and
+ * timeouts), {@link BeforeClass @BeforeClass}, {@link Before @Before}, and
+ * <em>assumptions</em>.
+ * </p>
+ * <p>
+ * Due to the fact that JUnit does not guarantee a particular ordering of test
+ * method execution, the following are currently not tested:
+ * {@link org.junit.AfterClass @AfterClass} and {@link org.junit.After @After}.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ * @see StandardJUnit4FeaturesSpringRunnerTests
+ */
+public class StandardJUnit4FeaturesTests {
+
+ private static int staticBeforeCounter = 0;
+
+
+ @BeforeClass
+ public static void incrementStaticBeforeCounter() {
+ StandardJUnit4FeaturesTests.staticBeforeCounter++;
+ }
+
+
+ private int beforeCounter = 0;
+
+
+ @Test
+ @Ignore
+ public void alwaysFailsButShouldBeIgnored() {
+ fail("The body of an ignored test should never be executed!");
+ }
+
+ @Test
+ public void alwaysSucceeds() {
+ assertTrue(true);
+ }
+
+ @Test(expected = IndexOutOfBoundsException.class)
+ public void expectingAnIndexOutOfBoundsException() {
+ new ArrayList<Object>().get(1);
+ }
+
+ @Test
+ public void failedAssumptionShouldPrecludeImminentFailure() {
+ assumeTrue(false);
+ fail("A failed assumption should preclude imminent failure!");
+ }
+
+ @Before
+ public void incrementBeforeCounter() {
+ this.beforeCounter++;
+ }
+
+ @Test(timeout = 10000)
+ public void noOpShouldNotTimeOut() {
+ /* no-op */
+ }
+
+ @Test
+ public void verifyBeforeAnnotation() {
+ assertEquals(1, this.beforeCounter);
+ }
+
+ @Test
+ public void verifyBeforeClassAnnotation() {
+ // Instead of testing for equality to 1, we just assert that the value
+ // was incremented at least once, since this test class may serve as a
+ // parent class to other tests in a suite, etc.
+ assertTrue(StandardJUnit4FeaturesTests.staticBeforeCounter > 0);
+ }
+
+}
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
new file mode 100644
index 00000000..3877058b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4;
+
+import static org.junit.Assert.*;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.JUnit4;
+import org.springframework.test.annotation.Timed;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.tests.Assume;
+import org.springframework.tests.TestGroup;
+
+/**
+ * Verifies proper handling of the following in conjunction with the
+ * {@link SpringJUnit4ClassRunner}:
+ * <ul>
+ * <li>JUnit's {@link Test#timeout() @Test(timeout=...)}</li>
+ * <li>Spring's {@link Timed @Timed}</li>
+ * </ul>
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@RunWith(JUnit4.class)
+public class TimedSpringRunnerTests {
+
+ @Test
+ public void timedTests() throws Exception {
+ Assume.group(TestGroup.PERFORMANCE);
+ Class<TimedSpringRunnerTestCase> testClass = TimedSpringRunnerTestCase.class;
+ TrackingRunListener listener = new TrackingRunListener();
+ RunNotifier notifier = new RunNotifier();
+ notifier.addListener(listener);
+
+ new SpringJUnit4ClassRunner(testClass).run(notifier);
+ assertEquals("Verifying number of failures for test class [" + testClass + "].", 3,
+ listener.getTestFailureCount());
+ assertEquals("Verifying number of tests started for test class [" + testClass + "].", 5,
+ listener.getTestStartedCount());
+ assertEquals("Verifying number of tests finished for test class [" + testClass + "].", 5,
+ listener.getTestFinishedCount());
+ }
+
+
+ @Ignore("TestCase classes are run manually by the enclosing test class")
+ @RunWith(SpringJUnit4ClassRunner.class)
+ @TestExecutionListeners({})
+ public static final class TimedSpringRunnerTestCase {
+
+ // Should Pass.
+ @Test(timeout = 2000)
+ public void jUnitTimeoutWithNoOp() {
+ /* no-op */
+ }
+
+ // Should Pass.
+ @Test
+ @Timed(millis = 2000)
+ public void springTimeoutWithNoOp() {
+ /* no-op */
+ }
+
+ // Should Fail due to timeout.
+ @Test(timeout = 10)
+ public void jUnitTimeoutWithSleep() throws Exception {
+ Thread.sleep(20);
+ }
+
+ // Should Fail due to timeout.
+ @Test
+ @Timed(millis = 10)
+ public void springTimeoutWithSleep() throws Exception {
+ Thread.sleep(20);
+ }
+
+ // Should Fail due to duplicate configuration.
+ @Test(timeout = 200)
+ @Timed(millis = 200)
+ public void springAndJUnitTimeouts() {
+ /* no-op */
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..751ec07e
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedTransactionalSpringRunnerTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2012 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 static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.annotation.NotTransactional;
+import org.springframework.test.annotation.Repeat;
+import org.springframework.test.annotation.Timed;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * JUnit 4 based integration test which verifies support of Spring's
+ * {@link Transactional &#64;Transactional} and {@link NotTransactional
+ * &#64;NotTransactional} annotations in conjunction with {@link Timed
+ * &#64;Timed} and JUnit 4's {@link Test#timeout() timeout} attribute.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("transactionalTests-context.xml")
+@Transactional
+@SuppressWarnings("deprecation")
+public class TimedTransactionalSpringRunnerTests {
+
+ @Test
+ @Timed(millis = 10000)
+ @Repeat(5)
+ public void transactionalWithSpringTimeout() {
+ assertInTransaction(true);
+ }
+
+ @Test(timeout = 10000)
+ @Repeat(5)
+ public void transactionalWithJUnitTimeout() {
+ assertInTransaction(true);
+ }
+
+ @Test
+ @NotTransactional
+ @Timed(millis = 10000)
+ @Repeat(5)
+ public void notTransactionalWithSpringTimeout() {
+ assertInTransaction(false);
+ }
+
+ @Test(timeout = 10000)
+ @NotTransactional
+ @Repeat(5)
+ public void notTransactionalWithJUnitTimeout() {
+ assertInTransaction(false);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/TrackingRunListener.java b/spring-test/src/test/java/org/springframework/test/context/junit4/TrackingRunListener.java
new file mode 100644
index 00000000..4c257540
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/TrackingRunListener.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2012 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.concurrent.atomic.AtomicInteger;
+
+import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+
+/**
+ * Simple {@link RunListener} which tracks how many times certain JUnit callback
+ * methods were called: only intended for the integration test suite.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+public class TrackingRunListener extends RunListener {
+
+ private final AtomicInteger testFailureCount = new AtomicInteger();
+
+ private final AtomicInteger testStartedCount = new AtomicInteger();
+
+ private final AtomicInteger testFinishedCount = new AtomicInteger();
+
+
+ public int getTestFailureCount() {
+ return this.testFailureCount.get();
+ }
+
+ public int getTestStartedCount() {
+ return this.testStartedCount.get();
+ }
+
+ public int getTestFinishedCount() {
+ return this.testFinishedCount.get();
+ }
+
+ @Override
+ public void testFailure(Failure failure) throws Exception {
+ this.testFailureCount.incrementAndGet();
+ }
+
+ @Override
+ public void testStarted(Description description) throws Exception {
+ this.testStartedCount.incrementAndGet();
+ }
+
+ @Override
+ public void testFinished(Description description) throws Exception {
+ this.testFinishedCount.incrementAndGet();
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/AciTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/AciTestSuite.java
new file mode 100644
index 00000000..208751f2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/AciTestSuite.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2012 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;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.test.context.junit4.aci.annotation.InitializerWithoutConfigFilesOrClassesTest;
+import org.springframework.test.context.junit4.aci.annotation.MergedInitializersAnnotationConfigTests;
+import org.springframework.test.context.junit4.aci.annotation.MultipleInitializersAnnotationConfigTests;
+import org.springframework.test.context.junit4.aci.annotation.OrderedInitializersAnnotationConfigTests;
+import org.springframework.test.context.junit4.aci.annotation.OverriddenInitializersAnnotationConfigTests;
+import org.springframework.test.context.junit4.aci.annotation.SingleInitializerAnnotationConfigTests;
+import org.springframework.test.context.junit4.aci.xml.MultipleInitializersXmlConfigTests;
+
+/**
+ * Convenience test suite for integration tests that verify support for
+ * {@link ApplicationContextInitializer ApplicationContextInitializers} (ACIs)
+ * in the TestContext framework.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(Suite.class)
+// Note: the following 'multi-line' layout is for enhanced code readability.
+@SuiteClasses({//
+ MultipleInitializersXmlConfigTests.class,//
+ SingleInitializerAnnotationConfigTests.class,//
+ MultipleInitializersAnnotationConfigTests.class,//
+ MergedInitializersAnnotationConfigTests.class,//
+ OverriddenInitializersAnnotationConfigTests.class,//
+ OrderedInitializersAnnotationConfigTests.class,//
+ InitializerWithoutConfigFilesOrClassesTest.class //
+})
+public class AciTestSuite {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/DevProfileInitializer.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/DevProfileInitializer.java
new file mode 100644
index 00000000..f5424607
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/DevProfileInitializer.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2012 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;
+
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.support.GenericApplicationContext;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2
+ */
+public class DevProfileInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
+
+ @Override
+ public void initialize(GenericApplicationContext applicationContext) {
+ applicationContext.getEnvironment().setActiveProfiles("dev");
+ }
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/FooBarAliasInitializer.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/FooBarAliasInitializer.java
new file mode 100644
index 00000000..1259b82d
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/FooBarAliasInitializer.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2012 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;
+
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.support.GenericApplicationContext;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2
+ */
+public class FooBarAliasInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
+
+ @Override
+ public void initialize(GenericApplicationContext applicationContext) {
+ applicationContext.registerAlias("foo", "bar");
+ }
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/DevProfileConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/DevProfileConfig.java
new file mode 100644
index 00000000..9d9834da
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/DevProfileConfig.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2012 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;
+import org.springframework.context.annotation.Profile;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@Configuration
+@Profile("dev")
+class DevProfileConfig {
+
+ @Bean
+ public String baz() {
+ return "dev profile config";
+ }
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/GlobalConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/GlobalConfig.java
new file mode 100644
index 00000000..41393776
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/GlobalConfig.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2012 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 3.2
+ */
+@Configuration
+class GlobalConfig {
+
+ @Bean
+ public String foo() {
+ return "foo";
+ }
+
+ @Bean
+ public String baz() {
+ return "global config";
+ }
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerWithoutConfigFilesOrClassesTest.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerWithoutConfigFilesOrClassesTest.java
new file mode 100644
index 00000000..5d1459c4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/InitializerWithoutConfigFilesOrClassesTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+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.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit4.aci.annotation.InitializerWithoutConfigFilesOrClassesTest.EntireAppInitializer;
+
+/**
+ * Integration test that verifies support for {@link ApplicationContextInitializer
+ * ApplicationContextInitializers} in the TestContext framework when the test
+ * class declares neither XML configuration files nor annotated configuration classes.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(initializers = EntireAppInitializer.class)
+public class InitializerWithoutConfigFilesOrClassesTest {
+
+ @Autowired
+ private String foo;
+
+
+ @Test
+ public void foo() {
+ assertEquals("foo", foo);
+ }
+
+
+ static class EntireAppInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
+
+ @Override
+ public void initialize(GenericApplicationContext applicationContext) {
+ new AnnotatedBeanDefinitionReader(applicationContext).register(GlobalConfig.class);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/MergedInitializersAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/MergedInitializersAnnotationConfigTests.java
new file mode 100644
index 00000000..0bc08e1c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/MergedInitializersAnnotationConfigTests.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.aci.DevProfileInitializer;
+
+/**
+ * Integration tests that verify support for {@link ApplicationContextInitializer
+ * ApplicationContextInitializers} in conjunction with annotation-driven
+ * configuration in the TestContext framework.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@ContextConfiguration(initializers = DevProfileInitializer.class)
+public class MergedInitializersAnnotationConfigTests extends SingleInitializerAnnotationConfigTests {
+
+ @Override
+ @Test
+ public void activeBeans() {
+ assertEquals("foo", foo);
+ assertEquals("foo", bar);
+ assertEquals("dev profile config", baz);
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/MultipleInitializersAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/MultipleInitializersAnnotationConfigTests.java
new file mode 100644
index 00000000..145a44bb
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/MultipleInitializersAnnotationConfigTests.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit4.aci.DevProfileInitializer;
+import org.springframework.test.context.junit4.aci.FooBarAliasInitializer;
+
+/**
+ * Integration tests that verify support for {@link ApplicationContextInitializer
+ * ApplicationContextInitializers} in conjunction with annotation-driven
+ * configuration in the TestContext framework.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { GlobalConfig.class, DevProfileConfig.class }, initializers = {
+ FooBarAliasInitializer.class, DevProfileInitializer.class })
+public class MultipleInitializersAnnotationConfigTests {
+
+ @Autowired
+ private String foo, bar, baz;
+
+
+ @Test
+ public void activeBeans() {
+ assertEquals("foo", foo);
+ assertEquals("foo", bar);
+ assertEquals("dev profile config", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/OrderedInitializersAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/OrderedInitializersAnnotationConfigTests.java
new file mode 100644
index 00000000..2eac4332
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/OrderedInitializersAnnotationConfigTests.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+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.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit4.aci.annotation.OrderedInitializersAnnotationConfigTests.ConfigTwo;
+import org.springframework.test.context.junit4.aci.annotation.OrderedInitializersAnnotationConfigTests.ConfigOne;
+import org.springframework.test.context.junit4.aci.annotation.OrderedInitializersAnnotationConfigTests.GlobalConfig;
+import org.springframework.test.context.junit4.aci.annotation.OrderedInitializersAnnotationConfigTests.OrderedOneInitializer;
+import org.springframework.test.context.junit4.aci.annotation.OrderedInitializersAnnotationConfigTests.OrderedTwoInitializer;
+
+/**
+ * Integration tests that verify that any {@link ApplicationContextInitializer
+ * ApplicationContextInitializers} implementing
+ * {@link org.springframework.core.Ordered Ordered} or marked with
+ * {@link org.springframework.core.annotation.Order @Order} will be sorted
+ * appropriately in conjunction with annotation-driven configuration in the
+ * TestContext framework.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+// Note: the ordering of the config classes is intentionally: global, two, one.
+// Note: the ordering of the initializers is intentionally: two, one.
+@ContextConfiguration(classes = { GlobalConfig.class, ConfigTwo.class, ConfigOne.class }, initializers = {
+ OrderedTwoInitializer.class, OrderedOneInitializer.class })
+public class OrderedInitializersAnnotationConfigTests {
+
+ private static final String PROFILE_GLOBAL = "global";
+ private static final String PROFILE_ONE = "one";
+ private static final String PROFILE_TWO = "two";
+
+ @Autowired
+ private String foo, bar, baz;
+
+
+ @Test
+ public void activeBeans() {
+ assertEquals(PROFILE_GLOBAL, foo);
+ assertEquals(PROFILE_GLOBAL, bar);
+ assertEquals(PROFILE_TWO, baz);
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ @Configuration
+ static class GlobalConfig {
+
+ @Bean
+ public String foo() {
+ return PROFILE_GLOBAL;
+ }
+
+ @Bean
+ public String bar() {
+ return PROFILE_GLOBAL;
+ }
+
+ @Bean
+ public String baz() {
+ return PROFILE_GLOBAL;
+ }
+ }
+
+ @Configuration
+ @Profile(PROFILE_ONE)
+ static class ConfigOne {
+
+ @Bean
+ public String foo() {
+ return PROFILE_ONE;
+ }
+
+ @Bean
+ public String bar() {
+ return PROFILE_ONE;
+ }
+
+ @Bean
+ public String baz() {
+ return PROFILE_ONE;
+ }
+ }
+
+ @Configuration
+ @Profile(PROFILE_TWO)
+ static class ConfigTwo {
+
+ @Bean
+ public String baz() {
+ return PROFILE_TWO;
+ }
+ }
+
+ // -------------------------------------------------------------------------
+
+ static class OrderedOneInitializer implements ApplicationContextInitializer<GenericApplicationContext>, Ordered {
+
+ @Override
+ public void initialize(GenericApplicationContext applicationContext) {
+ applicationContext.getEnvironment().setActiveProfiles(PROFILE_ONE);
+ }
+
+ @Override
+ public int getOrder() {
+ return 1;
+ }
+ }
+
+ @Order(2)
+ static class OrderedTwoInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
+
+ @Override
+ public void initialize(GenericApplicationContext applicationContext) {
+ applicationContext.getEnvironment().setActiveProfiles(PROFILE_TWO);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/OverriddenInitializersAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/OverriddenInitializersAnnotationConfigTests.java
new file mode 100644
index 00000000..f0399853
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/OverriddenInitializersAnnotationConfigTests.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.aci.DevProfileInitializer;
+
+/**
+ * Integration tests that verify support for {@link ApplicationContextInitializer
+ * ApplicationContextInitializers} in conjunction with annotation-driven
+ * configuration in the TestContext framework.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@ContextConfiguration(initializers = DevProfileInitializer.class, inheritInitializers = false)
+public class OverriddenInitializersAnnotationConfigTests extends SingleInitializerAnnotationConfigTests {
+
+ @Test
+ @Override
+ public void activeBeans() {
+ assertEquals("foo", foo);
+ assertNull(bar);
+ assertEquals("dev profile config", baz);
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/SingleInitializerAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/SingleInitializerAnnotationConfigTests.java
new file mode 100644
index 00000000..32528450
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/annotation/SingleInitializerAnnotationConfigTests.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit4.aci.FooBarAliasInitializer;
+
+/**
+ * Integration tests that verify support for {@link ApplicationContextInitializer
+ * ApplicationContextInitializers} in conjunction with annotation-driven
+ * configuration in the TestContext framework.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { GlobalConfig.class, DevProfileConfig.class }, initializers = FooBarAliasInitializer.class)
+public class SingleInitializerAnnotationConfigTests {
+
+ @Autowired
+ protected String foo;
+
+ @Autowired(required = false)
+ @Qualifier("bar")
+ protected String bar;
+
+ @Autowired
+ protected String baz;
+
+
+ @Test
+ public void activeBeans() {
+ assertEquals("foo", foo);
+ assertEquals("foo", bar);
+ assertEquals("global config", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml
new file mode 100644
index 00000000..d8e6c2f8
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="foo" class="java.lang.String">
+ <constructor-arg value="foo" />
+ </bean>
+
+ <bean id="baz" class="java.lang.String">
+ <constructor-arg value="global config" />
+ </bean>
+
+ <beans profile="dev">
+ <bean id="baz" class="java.lang.String">
+ <constructor-arg value="dev profile config" />
+ </bean>
+ </beans>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests.java
new file mode 100644
index 00000000..4314538c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2012 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.xml;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.junit4.aci.DevProfileInitializer;
+import org.springframework.test.context.junit4.aci.FooBarAliasInitializer;
+
+/**
+ * Integration tests that verify support for {@link ApplicationContextInitializer
+ * ApplicationContextInitializers} in conjunction with XML configuration files
+ * in the TestContext framework.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(initializers = { FooBarAliasInitializer.class, DevProfileInitializer.class })
+public class MultipleInitializersXmlConfigTests {
+
+ @Autowired
+ private String foo, bar, baz;
+
+
+ @Test
+ public void activeBeans() {
+ assertEquals("foo", foo);
+ assertEquals("foo", bar);
+ assertEquals("dev profile config", baz);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.java
new file mode 100644
index 00000000..219010f1
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunnerAppCtxTests;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework.
+ *
+ * <p>Furthermore, by extending {@link SpringJUnit4ClassRunnerAppCtxTests},
+ * this class also verifies support for several basic features of the
+ * Spring TestContext Framework. See JavaDoc in
+ * {@code SpringJUnit4ClassRunnerAppCtxTests} for details.
+ *
+ * <p>Configuration will be loaded from {@link PojoAndStringConfig}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@ContextConfiguration(classes = PojoAndStringConfig.class, inheritLocations = false)
+public class AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests extends SpringJUnit4ClassRunnerAppCtxTests {
+ /* all tests are in the parent class. */
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigTestSuite.java
new file mode 100644
index 00000000..f0dde3f7
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/AnnotationConfigTestSuite.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2011 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.annotation;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+/**
+ * JUnit test suite for annotation-driven <em>configuration class</em>
+ * support in the Spring TestContext Framework.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@RunWith(Suite.class)
+// Note: the following 'multi-line' layout is for enhanced code readability.
+@SuiteClasses({//
+AnnotationConfigSpringJUnit4ClassRunnerAppCtxTests.class,//
+ DefaultConfigClassesBaseTests.class,//
+ DefaultConfigClassesInheritedTests.class,//
+ BeanOverridingDefaultConfigClassesInheritedTests.class,//
+ ExplicitConfigClassesBaseTests.class,//
+ ExplicitConfigClassesInheritedTests.class,//
+ BeanOverridingExplicitConfigClassesInheritedTests.class,//
+ DefaultLoaderDefaultConfigClassesBaseTests.class,//
+ DefaultLoaderDefaultConfigClassesInheritedTests.class,//
+ DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.class,//
+ DefaultLoaderExplicitConfigClassesBaseTests.class,//
+ DefaultLoaderExplicitConfigClassesInheritedTests.class,//
+ DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.class //
+})
+public class AnnotationConfigTestSuite {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingDefaultConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingDefaultConfigClassesInheritedTests.java
new file mode 100644
index 00000000..a0c51ded
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingDefaultConfigClassesInheritedTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework.
+ *
+ * <p>Configuration will be loaded from {@link DefaultConfigClassesBaseTests.ContextConfiguration}
+ * and {@link BeanOverridingDefaultConfigClassesInheritedTests.ContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@ContextConfiguration
+public class BeanOverridingDefaultConfigClassesInheritedTests extends DefaultConfigClassesBaseTests {
+
+ @Configuration
+ static class ContextConfiguration {
+
+ @Bean
+ public Employee employee() {
+ Employee employee = new Employee();
+ employee.setName("Yoda");
+ employee.setAge(900);
+ employee.setCompany("The Force");
+ return employee;
+ }
+ }
+
+
+ @Test
+ @Override
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee should have been autowired.", this.employee);
+ assertEquals("The employee bean should have been overridden.", "Yoda", this.employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingExplicitConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingExplicitConfigClassesInheritedTests.java
new file mode 100644
index 00000000..54a9dd26
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/BeanOverridingExplicitConfigClassesInheritedTests.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework.
+ *
+ * <p>Configuration will be loaded from {@link DefaultConfigClassesBaseTests.ContextConfiguration}
+ * and {@link BeanOverridingDefaultConfigClassesInheritedTests.ContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@ContextConfiguration(classes = BeanOverridingDefaultConfigClassesInheritedTests.ContextConfiguration.class)
+public class BeanOverridingExplicitConfigClassesInheritedTests extends ExplicitConfigClassesBaseTests {
+
+ @Test
+ @Override
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee should have been autowired.", this.employee);
+ assertEquals("The employee bean should have been overridden.", "Yoda", this.employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesBaseTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesBaseTests.java
new file mode 100644
index 00000000..e8a440a2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesBaseTests.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework.
+ *
+ * <p>Configuration will be loaded from {@link DefaultConfigClassesBaseTests.ContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see DefaultLoaderDefaultConfigClassesBaseTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
+public class DefaultConfigClassesBaseTests {
+
+ @Configuration
+ static class ContextConfiguration {
+
+ @Bean
+ public Employee employee() {
+ Employee employee = new Employee();
+ employee.setName("John Smith");
+ employee.setAge(42);
+ employee.setCompany("Acme Widgets, Inc.");
+ return employee;
+ }
+ }
+
+
+ @Autowired
+ protected Employee employee;
+
+
+ @Test
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee field should have been autowired.", this.employee);
+ assertEquals("John Smith", this.employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesInheritedTests.java
new file mode 100644
index 00000000..05c74a29
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultConfigClassesInheritedTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework.
+ *
+ * <p>Configuration will be loaded from {@link DefaultConfigClassesBaseTests.ContextConfiguration}
+ * and {@link DefaultConfigClassesInheritedTests.ContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@ContextConfiguration
+public class DefaultConfigClassesInheritedTests extends DefaultConfigClassesBaseTests {
+
+ @Configuration
+ static class ContextConfiguration {
+
+ @Bean
+ public Pet pet() {
+ return new Pet("Fido");
+ }
+ }
+
+
+ @Autowired
+ private Pet pet;
+
+
+ @Test
+ public void verifyPetSetFromExtendedContextConfig() {
+ assertNotNull("The pet should have been autowired.", this.pet);
+ assertEquals("Fido", this.pet.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.java
new file mode 100644
index 00000000..7af6a541
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.DelegatingSmartContextLoader;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework in conjunction with the
+ * {@link DelegatingSmartContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@ContextConfiguration
+public class DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests extends
+ DefaultLoaderDefaultConfigClassesBaseTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public Employee employee() {
+ Employee employee = new Employee();
+ employee.setName("Yoda");
+ employee.setAge(900);
+ employee.setCompany("The Force");
+ return employee;
+ }
+ }
+
+
+ @Test
+ @Override
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee should have been autowired.", this.employee);
+ assertEquals("The employee bean should have been overridden.", "Yoda", this.employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.java
new file mode 100644
index 00000000..26dd5f47
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2012 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.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.DelegatingSmartContextLoader;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework in conjunction with the
+ * {@link DelegatingSmartContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@ContextConfiguration(classes = DefaultLoaderBeanOverridingDefaultConfigClassesInheritedTests.Config.class)
+public class DefaultLoaderBeanOverridingExplicitConfigClassesInheritedTests extends
+ DefaultLoaderExplicitConfigClassesBaseTests {
+
+ @Test
+ @Override
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee should have been autowired.", this.employee);
+ assertEquals("The employee bean should have been overridden.", "Yoda", this.employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesBaseTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesBaseTests.java
new file mode 100644
index 00000000..00da19f1
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesBaseTests.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.DelegatingSmartContextLoader;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework in conjunction with the
+ * {@link DelegatingSmartContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see DefaultConfigClassesBaseTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class DefaultLoaderDefaultConfigClassesBaseTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public Employee employee() {
+ Employee employee = new Employee();
+ employee.setName("John Smith");
+ employee.setAge(42);
+ employee.setCompany("Acme Widgets, Inc.");
+ return employee;
+ }
+ }
+
+
+ @Autowired
+ protected Employee employee;
+
+
+ @Test
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee field should have been autowired.", this.employee);
+ assertEquals("John Smith", this.employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesInheritedTests.java
new file mode 100644
index 00000000..9b4793e4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderDefaultConfigClassesInheritedTests.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.support.DelegatingSmartContextLoader;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework in conjunction with the
+ * {@link DelegatingSmartContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@ContextConfiguration
+public class DefaultLoaderDefaultConfigClassesInheritedTests extends DefaultLoaderDefaultConfigClassesBaseTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public Pet pet() {
+ return new Pet("Fido");
+ }
+ }
+
+
+ @Autowired
+ private Pet pet;
+
+
+ @Test
+ public void verifyPetSetFromExtendedContextConfig() {
+ assertNotNull("The pet should have been autowired.", this.pet);
+ assertEquals("Fido", this.pet.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesBaseTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesBaseTests.java
new file mode 100644
index 00000000..6613a695
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesBaseTests.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.DelegatingSmartContextLoader;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework in conjunction with the
+ * {@link DelegatingSmartContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = DefaultLoaderDefaultConfigClassesBaseTests.Config.class)
+public class DefaultLoaderExplicitConfigClassesBaseTests {
+
+ @Autowired
+ protected Employee employee;
+
+
+ @Test
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee should have been autowired.", this.employee);
+ assertEquals("John Smith", this.employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesInheritedTests.java
new file mode 100644
index 00000000..a0f7668a
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/DefaultLoaderExplicitConfigClassesInheritedTests.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.DelegatingSmartContextLoader;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework in conjunction with the
+ * {@link DelegatingSmartContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = DefaultLoaderDefaultConfigClassesInheritedTests.Config.class)
+public class DefaultLoaderExplicitConfigClassesInheritedTests extends DefaultLoaderExplicitConfigClassesBaseTests {
+
+ @Autowired
+ private Pet pet;
+
+
+ @Test
+ public void verifyPetSetFromExtendedContextConfig() {
+ assertNotNull("The pet should have been autowired.", this.pet);
+ assertEquals("Fido", this.pet.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesBaseTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesBaseTests.java
new file mode 100644
index 00000000..7da5e68b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesBaseTests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework.
+ *
+ * <p>Configuration will be loaded from {@link DefaultConfigClassesBaseTests.ContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = DefaultConfigClassesBaseTests.ContextConfiguration.class)
+public class ExplicitConfigClassesBaseTests {
+
+ @Autowired
+ protected Employee employee;
+
+
+ @Test
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee should have been autowired.", this.employee);
+ assertEquals("John Smith", this.employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesInheritedTests.java
new file mode 100644
index 00000000..cca20f48
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/ExplicitConfigClassesInheritedTests.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * Integration tests that verify support for configuration classes in
+ * the Spring TestContext Framework.
+ *
+ * <p>Configuration will be loaded from {@link DefaultConfigClassesInheritedTests.ContextConfiguration}
+ * and {@link DefaultConfigClassesBaseTests.ContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = DefaultConfigClassesInheritedTests.ContextConfiguration.class)
+public class ExplicitConfigClassesInheritedTests extends ExplicitConfigClassesBaseTests {
+
+ @Autowired
+ private Pet pet;
+
+
+ @Test
+ public void verifyPetSetFromExtendedContextConfig() {
+ assertNotNull("The pet should have been autowired.", this.pet);
+ assertEquals("Fido", this.pet.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/PojoAndStringConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/PojoAndStringConfig.java
new file mode 100644
index 00000000..19eefe38
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/annotation/PojoAndStringConfig.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.annotation;
+
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * ApplicationContext configuration class for various integration tests.
+ *
+ * <p>The beans defined in this configuration class map directly to the
+ * beans defined in {@code SpringJUnit4ClassRunnerAppCtxTests-context.xml}.
+ * Consequently, the application contexts loaded from these two sources
+ * should be identical with regard to bean definitions.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@Configuration
+public class PojoAndStringConfig {
+
+ @Bean
+ public Employee employee() {
+ Employee employee = new Employee();
+ employee.setName("John Smith");
+ employee.setAge(42);
+ employee.setCompany("Acme Widgets, Inc.");
+ return employee;
+ }
+
+ @Bean
+ public Pet pet() {
+ return new Pet("Fido");
+ }
+
+ @Bean
+ public String foo() {
+ return "Foo";
+ }
+
+ @Bean
+ public String bar() {
+ return "Bar";
+ }
+
+ @Bean
+ public String quux() {
+ return "Quux";
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests-context.xml
new file mode 100644
index 00000000..d1f5ca78
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests-context.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
+ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">
+
+ <context:component-scan base-package="org.springframework.test.context.junit4.orm" />
+
+ <tx:annotation-driven />
+
+ <jdbc:embedded-database id="dataSource" type="HSQL">
+ <jdbc:script location="classpath:/org/springframework/test/context/junit4/orm/db-schema.sql" />
+ <jdbc:script location="classpath:/org/springframework/test/context/junit4/orm/db-test-data.sql" />
+ </jdbc:embedded-database>
+
+ <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"
+ p:dataSource-ref="dataSource">
+ <property name="hibernateProperties">
+ <props>
+ <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
+ <prop key="hibernate.show_sql">false</prop>
+ </props>
+ </property>
+ <property name="mappingResources">
+ <list>
+ <value>org/springframework/test/context/junit4/orm/domain/Person.hbm.xml</value>
+ <value>org/springframework/test/context/junit4/orm/domain/DriversLicense.hbm.xml</value>
+ </list>
+ </property>
+ </bean>
+
+ <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"
+ p:sessionFactory-ref="sessionFactory" />
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java
new file mode 100644
index 00000000..69aecb72
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2002-2012 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.orm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+
+import org.hibernate.SessionFactory;
+import org.hibernate.exception.ConstraintViolationException;
+import org.hibernate.exception.GenericJDBCException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
+import org.springframework.test.context.junit4.orm.domain.DriversLicense;
+import org.springframework.test.context.junit4.orm.domain.Person;
+import org.springframework.test.context.junit4.orm.service.PersonService;
+
+/**
+ * Transactional integration tests regarding <i>manual</i> session flushing with
+ * Hibernate.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@ContextConfiguration
+public class HibernateSessionFlushingTests extends AbstractTransactionalJUnit4SpringContextTests {
+
+ private static final String SAM = "Sam";
+ private static final String JUERGEN = "Juergen";
+
+ @Autowired
+ private PersonService personService;
+
+ @Autowired
+ private SessionFactory sessionFactory;
+
+
+ protected int countRowsInPersonTable() {
+ return countRowsInTable("person");
+ }
+
+ protected void assertPersonCount(int expectedCount) {
+ assertEquals("Verifying number of rows in the 'person' table.", expectedCount, countRowsInPersonTable());
+ }
+
+ @Before
+ public void setUp() {
+ assertInTransaction(true);
+ assertNotNull("PersonService should have been autowired.", personService);
+ assertNotNull("SessionFactory should have been autowired.", sessionFactory);
+ }
+
+ @Test
+ public void findSam() {
+ Person sam = personService.findByName(SAM);
+ assertNotNull("Should be able to find Sam", sam);
+ DriversLicense driversLicense = sam.getDriversLicense();
+ assertNotNull("Sam's driver's license should not be null", driversLicense);
+ assertEquals("Verifying Sam's driver's license number", new Long(1234), driversLicense.getNumber());
+ }
+
+ @Test
+ public void saveJuergenWithDriversLicense() {
+ DriversLicense driversLicense = new DriversLicense(2L, 2222L);
+ Person juergen = new Person(JUERGEN, driversLicense);
+ int numRows = countRowsInPersonTable();
+ personService.save(juergen);
+ assertPersonCount(numRows + 1);
+ assertNotNull("Should be able to save and retrieve Juergen", personService.findByName(JUERGEN));
+ assertNotNull("Juergen's ID should have been set", juergen.getId());
+ }
+
+ @Test(expected = ConstraintViolationException.class)
+ public void saveJuergenWithNullDriversLicense() {
+ personService.save(new Person(JUERGEN));
+ }
+
+ private void updateSamWithNullDriversLicense() {
+ Person sam = personService.findByName(SAM);
+ assertNotNull("Should be able to find Sam", sam);
+ sam.setDriversLicense(null);
+ personService.save(sam);
+ }
+
+ @Test
+ // no expected exception!
+ public void updateSamWithNullDriversLicenseWithoutSessionFlush() {
+ updateSamWithNullDriversLicense();
+ // False positive, since an exception will be thrown once the session is
+ // finally flushed (i.e., in production code)
+ }
+
+ @Test(expected = GenericJDBCException.class)
+ public void updateSamWithNullDriversLicenseWithSessionFlush() {
+ updateSamWithNullDriversLicense();
+ // Manual flush is required to avoid false positive in test
+ sessionFactory.getCurrentSession().flush();
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/db-schema.sql b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/db-schema.sql
new file mode 100644
index 00000000..960b101e
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/db-schema.sql
@@ -0,0 +1,16 @@
+DROP TABLE drivers_license IF EXISTS;
+DROP TABLE person IF EXISTS;
+
+CREATE TABLE person (
+ id INTEGER NOT NULL IDENTITY PRIMARY KEY,
+ name VARCHAR(50) NOT NULL,
+ drivers_license_id INTEGER NOT NULL
+);
+CREATE UNIQUE INDEX person_name ON person(name);
+CREATE UNIQUE INDEX person_drivers_license_id ON person(drivers_license_id);
+
+CREATE TABLE drivers_license (
+ id INTEGER NOT NULL IDENTITY PRIMARY KEY,
+ license_number INTEGER NOT NULL
+);
+CREATE UNIQUE INDEX drivers_license_license_number ON drivers_license(license_number);
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/db-test-data.sql b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/db-test-data.sql
new file mode 100644
index 00000000..a174e3fb
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/db-test-data.sql
@@ -0,0 +1,3 @@
+INSERT INTO drivers_license(id, license_number) values(1, 1234);
+
+INSERT INTO person(id, name, drivers_license_id) values(1, 'Sam', 1);
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/DriversLicense.hbm.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/DriversLicense.hbm.xml
new file mode 100644
index 00000000..02525630
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/DriversLicense.hbm.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+<hibernate-mapping auto-import="true" default-lazy="false">
+
+ <class name="org.springframework.test.context.junit4.orm.domain.DriversLicense" table="drivers_license">
+ <id name="id" column="id">
+ <generator class="identity" />
+ </id>
+ <property name="number" column="license_number" />
+ </class>
+
+</hibernate-mapping>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/DriversLicense.java b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/DriversLicense.java
new file mode 100644
index 00000000..dc06571f
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/DriversLicense.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2012 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.orm.domain;
+
+/**
+ * DriversLicense POJO.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+public class DriversLicense {
+
+ private Long id;
+
+ private Long number;
+
+
+ public DriversLicense() {
+ }
+
+ public DriversLicense(Long number) {
+ this(null, number);
+ }
+
+ public DriversLicense(Long id, Long number) {
+ this.id = id;
+ this.number = number;
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ protected void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getNumber() {
+ return this.number;
+ }
+
+ public void setNumber(Long number) {
+ this.number = number;
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/Person.hbm.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/Person.hbm.xml
new file mode 100644
index 00000000..aa6478b9
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/Person.hbm.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
+ "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
+
+<hibernate-mapping auto-import="true" default-lazy="false">
+
+ <class name="org.springframework.test.context.junit4.orm.domain.Person" table="person">
+ <id name="id" column="id">
+ <generator class="identity" />
+ </id>
+ <property name="name" column="name" />
+ <many-to-one name="driversLicense" class="org.springframework.test.context.junit4.orm.domain.DriversLicense"
+ column="drivers_license_id" unique="true" />
+ </class>
+
+</hibernate-mapping>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/Person.java b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/Person.java
new file mode 100644
index 00000000..c6f765ea
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/domain/Person.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2002-2012 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.orm.domain;
+
+/**
+ * Person POJO.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+public class Person {
+
+ private Long id;
+ private String name;
+ private DriversLicense driversLicense;
+
+
+ public Person() {
+ }
+
+ public Person(Long id) {
+ this(id, null, null);
+ }
+
+ public Person(String name) {
+ this(name, null);
+ }
+
+ public Person(String name, DriversLicense driversLicense) {
+ this(null, name, driversLicense);
+ }
+
+ public Person(Long id, String name, DriversLicense driversLicense) {
+ this.id = id;
+ this.name = name;
+ this.driversLicense = driversLicense;
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ protected void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public DriversLicense getDriversLicense() {
+ return this.driversLicense;
+ }
+
+ public void setDriversLicense(DriversLicense driversLicense) {
+ this.driversLicense = driversLicense;
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/PersonRepository.java b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/PersonRepository.java
new file mode 100644
index 00000000..409aade5
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/PersonRepository.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2012 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.orm.repository;
+
+import org.springframework.test.context.junit4.orm.domain.Person;
+
+/**
+ * Person Repository API.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+public interface PersonRepository {
+
+ Person findByName(String name);
+
+ Person save(Person person);
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java
new file mode 100644
index 00000000..eac2522c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/repository/hibernate/HibernatePersonRepository.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2012 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.orm.repository.hibernate;
+
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.springframework.test.context.junit4.orm.domain.Person;
+import org.springframework.test.context.junit4.orm.repository.PersonRepository;
+
+/**
+ * Hibernate implementation of the {@link PersonRepository} API.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@Repository
+public class HibernatePersonRepository implements PersonRepository {
+
+ private final SessionFactory sessionFactory;
+
+
+ @Autowired
+ public HibernatePersonRepository(SessionFactory sessionFactory) {
+ this.sessionFactory = sessionFactory;
+ }
+
+ @Override
+ public Person save(Person person) {
+ this.sessionFactory.getCurrentSession().save(person);
+ return person;
+ }
+
+ @Override
+ public Person findByName(String name) {
+ return (Person) this.sessionFactory.getCurrentSession().createQuery(
+ "from Person person where person.name = :name").setString("name", name).uniqueResult();
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/PersonService.java b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/PersonService.java
new file mode 100644
index 00000000..76421e37
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/PersonService.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2012 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.orm.service;
+
+import org.springframework.test.context.junit4.orm.domain.Person;
+
+/**
+ * Person Service API.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+public interface PersonService {
+
+ Person findByName(String name);
+
+ Person save(Person person);
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/impl/StandardPersonService.java b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/impl/StandardPersonService.java
new file mode 100644
index 00000000..92602e15
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/orm/service/impl/StandardPersonService.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2012 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.orm.service.impl;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.test.context.junit4.orm.domain.Person;
+import org.springframework.test.context.junit4.orm.repository.PersonRepository;
+import org.springframework.test.context.junit4.orm.service.PersonService;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Standard implementation of the {@link PersonService} API.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@Service
+@Transactional(readOnly = true)
+public class StandardPersonService implements PersonService {
+
+ private final PersonRepository personRepository;
+
+
+ @Autowired
+ public StandardPersonService(PersonRepository personRepository) {
+ this.personRepository = personRepository;
+ }
+
+ @Override
+ public Person findByName(String name) {
+ return this.personRepository.findByName(name);
+ }
+
+ @Override
+ @Transactional(readOnly = false)
+ public Person save(Person person) {
+ return this.personRepository.save(person);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileAnnotationConfigTests.java
new file mode 100644
index 00000000..836dca41
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileAnnotationConfigTests.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.AnnotationConfigContextLoader;
+
+/**
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { DefaultProfileConfig.class, DevProfileConfig.class }, loader = AnnotationConfigContextLoader.class)
+public class DefaultProfileAnnotationConfigTests {
+
+ @Autowired
+ protected Pet pet;
+
+ @Autowired(required = false)
+ protected Employee employee;
+
+
+ @Test
+ public void pet() {
+ assertNotNull(pet);
+ assertEquals("Fido", pet.getName());
+ }
+
+ @Test
+ public void employee() {
+ assertNull("employee bean should not be created for the default profile", employee);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileConfig.java
new file mode 100644
index 00000000..44b5675a
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DefaultProfileConfig.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.annotation;
+
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@Configuration
+public class DefaultProfileConfig {
+
+ @Bean
+ public Pet pet() {
+ return new Pet("Fido");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileAnnotationConfigTests.java
new file mode 100644
index 00000000..813cc828
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileAnnotationConfigTests.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.annotation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.test.context.ActiveProfiles;
+
+/**
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@ActiveProfiles("dev")
+public class DevProfileAnnotationConfigTests extends DefaultProfileAnnotationConfigTests {
+
+ @Test
+ @Override
+ public void employee() {
+ assertNotNull("employee bean should be loaded for the 'dev' profile", employee);
+ assertEquals("John Smith", employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileConfig.java
new file mode 100644
index 00000000..cafc8b6e
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/DevProfileConfig.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.annotation;
+
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+/**
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@Profile("dev")
+@Configuration
+public class DevProfileConfig {
+
+ @Bean
+ public Employee employee() {
+ Employee employee = new Employee();
+ employee.setName("John Smith");
+ employee.setAge(42);
+ employee.setCompany("Acme Widgets, Inc.");
+ return employee;
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java
new file mode 100644
index 00000000..b3b7068c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/annotation/ProfileAnnotationConfigTestSuite.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.annotation;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+/**
+ * JUnit test suite for <em>bean definition profile</em> support in the
+ * Spring TestContext Framework with annotation-based configuration.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@RunWith(Suite.class)
+// Note: the following 'multi-line' layout is for enhanced code readability.
+@SuiteClasses({//
+DefaultProfileAnnotationConfigTests.class,//
+ DevProfileAnnotationConfigTests.class //
+})
+public class ProfileAnnotationConfigTestSuite {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java
new file mode 100644
index 00000000..07a8d2d5
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileAnnotationConfigTests.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.importresource;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Juergen Hoeller
+ * @since 3.1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = DefaultProfileConfig.class)
+public class DefaultProfileAnnotationConfigTests {
+
+ @Autowired
+ protected Pet pet;
+
+ @Autowired(required = false)
+ protected Employee employee;
+
+
+ @Test
+ public void pet() {
+ assertNotNull(pet);
+ assertEquals("Fido", pet.getName());
+ }
+
+ @Test
+ public void employee() {
+ assertNull("employee bean should not be created for the default profile", employee);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java
new file mode 100644
index 00000000..818212dd
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DefaultProfileConfig.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.importresource;
+
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.ImportResource;
+
+/**
+ * @author Juergen Hoeller
+ * @since 3.1
+ */
+@Configuration
+@ImportResource("org/springframework/test/context/junit4/profile/importresource/import.xml")
+public class DefaultProfileConfig {
+
+ @Bean
+ public Pet pet() {
+ return new Pet("Fido");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java
new file mode 100644
index 00000000..9e6ff731
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/DevProfileAnnotationConfigTests.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.importresource;
+
+import org.junit.Test;
+
+import org.springframework.test.context.ActiveProfiles;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Juergen Hoeller
+ * @since 3.1
+ */
+@ActiveProfiles("dev")
+public class DevProfileAnnotationConfigTests extends DefaultProfileAnnotationConfigTests {
+
+ @Test
+ @Override
+ public void employee() {
+ assertNotNull("employee bean should be loaded for the 'dev' profile", employee);
+ assertEquals("John Smith", employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/import.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/import.xml
new file mode 100644
index 00000000..3861d0ec
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/importresource/import.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+ <beans profile="dev">
+ <bean id="employee" class="org.springframework.tests.sample.beans.Employee">
+ <property name="name" value="John Smith" />
+ <property name="age" value="42" />
+ <property name="company" value="Acme Widgets, Inc." />
+ </bean>
+ </beans>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests-context.xml
new file mode 100644
index 00000000..0e672de0
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests-context.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+ <bean id="pet" class="org.springframework.tests.sample.beans.Pet">
+ <constructor-arg value="Fido" />
+ </bean>
+
+ <beans profile="dev">
+ <bean id="employee" class="org.springframework.tests.sample.beans.Employee">
+ <property name="name" value="John Smith" />
+ <property name="age" value="42" />
+ <property name="company" value="Acme Widgets, Inc." />
+ </bean>
+ </beans>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests.java
new file mode 100644
index 00000000..1ed4f546
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class DefaultProfileXmlConfigTests {
+
+ @Autowired
+ protected Pet pet;
+
+ @Autowired(required = false)
+ protected Employee employee;
+
+
+ @Test
+ public void pet() {
+ assertNotNull(pet);
+ assertEquals("Fido", pet.getName());
+ }
+
+ @Test
+ public void employee() {
+ assertNull("employee bean should not be created for the default profile", employee);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileXmlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileXmlConfigTests.java
new file mode 100644
index 00000000..43d6a5a7
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/DevProfileXmlConfigTests.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.test.context.ActiveProfiles;
+
+/**
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@ActiveProfiles("dev")
+public class DevProfileXmlConfigTests extends DefaultProfileXmlConfigTests {
+
+ @Test
+ @Override
+ public void employee() {
+ assertNotNull("employee bean should be loaded for the 'dev' profile", employee);
+ assertEquals("John Smith", employee.getName());
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java
new file mode 100644
index 00000000..38a5939c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/profile/xml/ProfileXmlConfigTestSuite.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.profile.xml;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+/**
+ * JUnit test suite for <em>bean definition profile</em> support in the
+ * Spring TestContext Framework with XML-based configuration.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@RunWith(Suite.class)
+// Note: the following 'multi-line' layout is for enhanced code readability.
+@SuiteClasses({//
+DefaultProfileXmlConfigTests.class,//
+ DevProfileXmlConfigTests.class //
+})
+public class ProfileXmlConfigTestSuite {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests-context.xml
new file mode 100644
index 00000000..7238260b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests-context.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="employee" class="org.springframework.tests.sample.beans.Employee">
+ <property name="name" value="Yoda" />
+ <property name="age" value="900" />
+ <property name="company" value="The Force" />
+ </bean>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests.java
new file mode 100644
index 00000000..cf46ce66
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingDefaultLocationsInheritedTests.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2007 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.spr3896;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * JUnit 4 based integration test for verifying support for the
+ * {@link ContextConfiguration#inheritLocations() inheritLocations} flag of
+ * {@link ContextConfiguration @ContextConfiguration} indirectly proposed in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3896"
+ * target="_blank">SPR-3896</a>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@ContextConfiguration
+public class BeanOverridingDefaultLocationsInheritedTests extends DefaultLocationsBaseTests {
+
+ @Test
+ @Override
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee should have been autowired.", this.employee);
+ assertEquals("The employee bean should have been overridden.", "Yoda", this.employee.getName());
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingExplicitLocationsInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingExplicitLocationsInheritedTests.java
new file mode 100644
index 00000000..5d4a3ef0
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/BeanOverridingExplicitLocationsInheritedTests.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2007 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.spr3896;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * JUnit 4 based integration test for verifying support for the
+ * {@link ContextConfiguration#inheritLocations() inheritLocations} flag of
+ * {@link ContextConfiguration @ContextConfiguration} indirectly proposed in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3896"
+ * target="_blank">SPR-3896</a>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@ContextConfiguration("BeanOverridingDefaultLocationsInheritedTests-context.xml")
+public class BeanOverridingExplicitLocationsInheritedTests extends ExplicitLocationsBaseTests {
+
+ @Test
+ @Override
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee should have been autowired.", this.employee);
+ assertEquals("The employee bean should have been overridden.", "Yoda", this.employee.getName());
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsBaseTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsBaseTests-context.xml
new file mode 100644
index 00000000..b40fed69
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsBaseTests-context.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="employee" class="org.springframework.tests.sample.beans.Employee">
+ <property name="name" value="John Smith" />
+ <property name="age" value="42" />
+ <property name="company" value="Acme Widgets, Inc." />
+ </bean>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsBaseTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsBaseTests.java
new file mode 100644
index 00000000..7131201d
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsBaseTests.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr3896;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * JUnit 4 based integration test for verifying support for the
+ * {@link ContextConfiguration#inheritLocations() inheritLocations} flag of
+ * {@link ContextConfiguration @ContextConfiguration} indirectly proposed in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3896"
+ * target="_blank">SPR-3896</a>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class DefaultLocationsBaseTests {
+
+ @Autowired
+ protected Employee employee;
+
+
+ @Test
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee should have been autowired.", this.employee);
+ assertEquals("John Smith", this.employee.getName());
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsInheritedTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsInheritedTests-context.xml
new file mode 100644
index 00000000..f6a90a62
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsInheritedTests-context.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="pet" class="org.springframework.tests.sample.beans.Pet">
+ <constructor-arg value="Fido" />
+ </bean>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsInheritedTests.java
new file mode 100644
index 00000000..1f488ec2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/DefaultLocationsInheritedTests.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr3896;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * JUnit 4 based integration test for verifying support for the
+ * {@link ContextConfiguration#inheritLocations() inheritLocations} flag of
+ * {@link ContextConfiguration @ContextConfiguration} indirectly proposed in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3896"
+ * target="_blank">SPR-3896</a>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@ContextConfiguration
+public class DefaultLocationsInheritedTests extends DefaultLocationsBaseTests {
+
+ @Autowired
+ private Pet pet;
+
+
+ @Test
+ public void verifyPetSetFromExtendedContextConfig() {
+ assertNotNull("The pet should have been autowired.", this.pet);
+ assertEquals("Fido", this.pet.getName());
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/ExplicitLocationsBaseTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/ExplicitLocationsBaseTests.java
new file mode 100644
index 00000000..cd81b6be
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/ExplicitLocationsBaseTests.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr3896;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * JUnit 4 based integration test for verifying support for the
+ * {@link ContextConfiguration#inheritLocations() inheritLocations} flag of
+ * {@link ContextConfiguration @ContextConfiguration} indirectly proposed in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3896"
+ * target="_blank">SPR-3896</a>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("DefaultLocationsBaseTests-context.xml")
+public class ExplicitLocationsBaseTests {
+
+ @Autowired
+ protected Employee employee;
+
+
+ @Test
+ public void verifyEmployeeSetFromBaseContextConfig() {
+ assertNotNull("The employee should have been autowired.", this.employee);
+ assertEquals("John Smith", this.employee.getName());
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/ExplicitLocationsInheritedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/ExplicitLocationsInheritedTests.java
new file mode 100644
index 00000000..b0176425
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/ExplicitLocationsInheritedTests.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr3896;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * JUnit 4 based integration test for verifying support for the
+ * {@link ContextConfiguration#inheritLocations() inheritLocations} flag of
+ * {@link ContextConfiguration @ContextConfiguration} indirectly proposed in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3896"
+ * target="_blank">SPR-3896</a>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@ContextConfiguration("DefaultLocationsInheritedTests-context.xml")
+public class ExplicitLocationsInheritedTests extends ExplicitLocationsBaseTests {
+
+ @Autowired
+ private Pet pet;
+
+
+ @Test
+ public void verifyPetSetFromExtendedContextConfig() {
+ assertNotNull("The pet should have been autowired.", this.pet);
+ assertEquals("Fido", this.pet.getName());
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/Spr3896SuiteTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/Spr3896SuiteTests.java
new file mode 100644
index 00000000..5b399b58
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr3896/Spr3896SuiteTests.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2007 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.spr3896;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+/**
+ * JUnit 4 based test suite for functionality proposed in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3896"
+ * target="_blank">SPR-3896</a>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@RunWith(Suite.class)
+// Note: the following 'multi-line' layout is for enhanced code readability.
+@SuiteClasses({
+
+DefaultLocationsBaseTests.class,
+
+DefaultLocationsInheritedTests.class,
+
+ExplicitLocationsBaseTests.class,
+
+ExplicitLocationsInheritedTests.class,
+
+BeanOverridingDefaultLocationsInheritedTests.class,
+
+BeanOverridingExplicitLocationsInheritedTests.class
+
+})
+public class Spr3896SuiteTests {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/Jsr250LifecycleTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/Jsr250LifecycleTests.java
new file mode 100644
index 00000000..44b7d1bd
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/Jsr250LifecycleTests.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2002-2012 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.spr4868;
+
+import static org.junit.Assert.assertNotNull;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.After;
+import org.junit.Before;
+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.ContextConfiguration;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
+
+/**
+ * Integration tests that investigate the applicability of JSR-250 lifecycle
+ * annotations in test classes.
+ *
+ * <p>This class does not really contain actual <em>tests</em> per se. Rather it
+ * can be used to empirically verify the expected log output (see below). In
+ * order to see the log output, one would naturally need to ensure that the
+ * logger category for this class is enabled at {@code INFO} level.
+ *
+ * <h4>Expected Log Output</h4>
+ * <pre>
+ * INFO : org.springframework.test.context.junit4.spr4868.LifecycleBean - initializing
+ * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - beforeAllTests()
+ * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - setUp()
+ * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - test1()
+ * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - tearDown()
+ * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - beforeAllTests()
+ * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - setUp()
+ * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - test2()
+ * INFO : org.springframework.test.context.junit4.spr4868.ExampleTest - tearDown()
+ * INFO : org.springframework.test.context.junit4.spr4868.LifecycleBean - destroying
+ * </pre>
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class })
+@ContextConfiguration
+public class Jsr250LifecycleTests {
+
+ private final Log logger = LogFactory.getLog(Jsr250LifecycleTests.class);
+
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public LifecycleBean lifecycleBean() {
+ return new LifecycleBean();
+ }
+ }
+
+
+ @Autowired
+ private LifecycleBean lifecycleBean;
+
+
+ @PostConstruct
+ public void beforeAllTests() {
+ logger.info("beforeAllTests()");
+ }
+
+ @PreDestroy
+ public void afterTestSuite() {
+ logger.info("afterTestSuite()");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ logger.info("setUp()");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ logger.info("tearDown()");
+ }
+
+ @Test
+ public void test1() {
+ logger.info("test1()");
+ assertNotNull(lifecycleBean);
+ }
+
+ @Test
+ public void test2() {
+ logger.info("test2()");
+ assertNotNull(lifecycleBean);
+ }
+
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/LifecycleBean.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/LifecycleBean.java
new file mode 100644
index 00000000..1b8dda70
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr4868/LifecycleBean.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2012 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.spr4868;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2
+ */
+class LifecycleBean {
+
+ private final Log logger = LogFactory.getLog(LifecycleBean.class);
+
+
+ @PostConstruct
+ public void init() {
+ logger.info("initializing");
+ }
+
+ @PreDestroy
+ public void destroy() {
+ logger.info("destroying");
+ }
+
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests-context.xml
new file mode 100644
index 00000000..6b39409b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests-context.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="foo" class="java.lang.String">
+ <constructor-arg value="normal" />
+ </bean>
+
+ <bean id="customFoo" class="java.lang.String">
+ <constructor-arg value="custom" />
+ </bean>
+
+</beans> \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests.java
new file mode 100644
index 00000000..6bdeeee2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr6128/AutowiredQualifierTests.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2012 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.spr6128;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * Integration tests to verify claims made in <a
+ * href="http://jira.springframework.org/browse/SPR-6128"
+ * target="_blank">SPR-6128</a>.
+ *
+ * @author Sam Brannen
+ * @author Chris Beams
+ * @since 3.0
+ */
+@ContextConfiguration
+@RunWith(SpringJUnit4ClassRunner.class)
+public class AutowiredQualifierTests {
+
+ @Autowired
+ private String foo;
+
+ @Autowired
+ @Qualifier("customFoo")
+ private String customFoo;
+
+
+ @Test
+ public void test() {
+ assertThat(foo, equalTo("normal"));
+ assertThat(customFoo, equalTo("custom"));
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/Spr8849Tests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/Spr8849Tests.java
new file mode 100644
index 00000000..dd589867
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/Spr8849Tests.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2012 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.spr8849;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+/**
+ * Test suite to investigate claims raised in
+ * <a href="https://jira.springsource.org/browse/SPR-8849">SPR-8849</a>.
+ *
+ * <p>By using a SpEL expression to generate a random {@code id} for the
+ * embedded database (see {@code datasource-config.xml}), we ensure that each
+ * {@code ApplicationContext} that imports the common configuration will create
+ * an embedded database with a unique name (since the {@code id} is used as the
+ * database name within
+ * {@link org.springframework.jdbc.config.EmbeddedDatabaseBeanDefinitionParser#useIdAsDatabaseNameIfGiven()}).
+ *
+ * <p>To reproduce the problem mentioned in SPEX-8849, change the {@code id} of
+ * the embedded database in {@code datasource-config.xml} to "dataSource" (or
+ * anything else that is not random) and run this <em>suite</em>.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@SuppressWarnings("javadoc")
+@RunWith(Suite.class)
+@SuiteClasses({ TestClass1.class, TestClass2.class })
+public class Spr8849Tests {
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass1-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass1-context.xml
new file mode 100644
index 00000000..63314700
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass1-context.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+ <import resource="datasource-config.xml"/>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass1.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass1.java
new file mode 100644
index 00000000..b45b6d76
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass1.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2012 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.spr8849;
+
+import javax.sql.DataSource;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * This name of this class intentionally does not end with "Test" or "Tests"
+ * since it should only be run as part of the test suite: {@link Spr8849Tests}.
+ *
+ * @author Mickael Leduque
+ * @author Sam Brannen
+ * @since 3.2
+ * @see Spr8849Tests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class TestClass1 {
+
+ @Autowired
+ DataSource datasource;
+
+
+ @Test
+ public void dummyTest() {
+ // it's sufficient if the ApplicationContext loads without errors.
+ }
+
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass2-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass2-context.xml
new file mode 100644
index 00000000..63314700
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass2-context.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+ <import resource="datasource-config.xml"/>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass2.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass2.java
new file mode 100644
index 00000000..bc8a7800
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/TestClass2.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2012 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.spr8849;
+
+import javax.sql.DataSource;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * This name of this class intentionally does not end with "Test" or "Tests"
+ * since it should only be run as part of the test suite: {@link Spr8849Tests}.
+ *
+ * @author Mickael Leduque
+ * @author Sam Brannen
+ * @since 3.2
+ * @see Spr8849Tests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class TestClass2 {
+
+ @Autowired
+ DataSource dataSource;
+
+
+ @Test
+ public void dummyTest() {
+ // it's sufficient if the ApplicationContext loads without errors.
+ }
+
+} \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/datasource-config.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/datasource-config.xml
new file mode 100644
index 00000000..ed82b6f3
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/datasource-config.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
+ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd">
+
+ <jdbc:embedded-database id="#{T(java.util.UUID).randomUUID().toString()}">
+ <jdbc:script location="classpath:/org/springframework/test/context/junit4/spr8849/spr8849-schema.sql" />
+ </jdbc:embedded-database>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/spr8849-schema.sql b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/spr8849-schema.sql
new file mode 100644
index 00000000..a17d13a9
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr8849/spr8849-schema.sql
@@ -0,0 +1,3 @@
+CREATE TABLE enigma (
+ id INTEGER NOT NULL IDENTITY PRIMARY KEY
+);
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AbstractTransactionalAnnotatedConfigClassTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AbstractTransactionalAnnotatedConfigClassTests.java
new file mode 100644
index 00000000..59cef2c1
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AbstractTransactionalAnnotatedConfigClassTests.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9051;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+import static org.springframework.test.transaction.TransactionTestUtils.inTransaction;
+
+import javax.sql.DataSource;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * This set of tests (i.e., all concrete subclasses) investigates the claims made in
+ * <a href="https://jira.springsource.org/browse/SPR-9051" target="_blank">SPR-9051</a>
+ * with regard to transactional tests.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ * @see org.springframework.test.context.testng.AnnotationConfigTransactionalTestNGSpringContextTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
+public abstract class AbstractTransactionalAnnotatedConfigClassTests {
+
+ protected static final String JANE = "jane";
+ protected static final String SUE = "sue";
+ protected static final String YODA = "yoda";
+
+ protected DataSource dataSourceFromTxManager;
+ protected DataSource dataSourceViaInjection;
+
+ protected JdbcTemplate jdbcTemplate;
+
+ @Autowired
+ private Employee employee;
+
+
+ @Autowired
+ public void setTransactionManager(DataSourceTransactionManager transactionManager) {
+ this.dataSourceFromTxManager = transactionManager.getDataSource();
+ }
+
+ @Autowired
+ public void setDataSource(DataSource dataSource) {
+ this.dataSourceViaInjection = dataSource;
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ private int countRowsInTable(String tableName) {
+ return jdbcTemplate.queryForObject("SELECT COUNT(0) FROM " + tableName, Integer.class);
+ }
+
+ private int createPerson(String name) {
+ return jdbcTemplate.update("INSERT INTO person VALUES(?)", name);
+ }
+
+ protected int deletePerson(String name) {
+ return jdbcTemplate.update("DELETE FROM person WHERE name=?", name);
+ }
+
+ protected void assertNumRowsInPersonTable(int expectedNumRows, String testState) {
+ assertEquals("the number of rows in the person table (" + testState + ").", expectedNumRows,
+ countRowsInTable("person"));
+ }
+
+ protected void assertAddPerson(final String name) {
+ assertEquals("Adding '" + name + "'", 1, createPerson(name));
+ }
+
+ @Test
+ public void autowiringFromConfigClass() {
+ assertNotNull("The employee should have been autowired.", employee);
+ assertEquals("John Smith", employee.getName());
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ assertNumRowsInPersonTable(0, "before a transactional test method");
+ assertAddPerson(YODA);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ assertNumRowsInPersonTable((inTransaction() ? 1 : 0), "before a test method");
+ }
+
+ @Test
+ @Transactional
+ public void modifyTestDataWithinTransaction() {
+ assertInTransaction(true);
+ assertAddPerson(JANE);
+ assertAddPerson(SUE);
+ assertNumRowsInPersonTable(3, "in modifyTestDataWithinTransaction()");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ assertNumRowsInPersonTable((inTransaction() ? 3 : 0), "after a test method");
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertEquals("Deleting yoda", 1, deletePerson(YODA));
+ assertNumRowsInPersonTable(0, "after a transactional test method");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java
new file mode 100644
index 00000000..e5b9bc0c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AnnotatedConfigClassesWithoutAtConfigurationTests.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2002-2012 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.spr9051;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * This set of tests refutes the claims made in
+ * <a href="https://jira.springsource.org/browse/SPR-9051" target="_blank">SPR-9051</a>.
+ *
+ * <p><b>The Claims</b>:
+ *
+ * <blockquote>
+ * When a {@code @ContextConfiguration} test class references a config class
+ * missing an {@code @Configuration} annotation, {@code @Bean} dependencies are
+ * wired successfully but the bean lifecycle is not applied (no init methods are
+ * invoked, for example). Adding the missing {@code @Configuration} annotation
+ * solves the problem, however the problem and solution isn't obvious since
+ * wiring/injection appeared to work.
+ * </blockquote>
+ *
+ * @author Sam Brannen
+ * @author Phillip Webb
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = AnnotatedConfigClassesWithoutAtConfigurationTests.AnnotatedFactoryBeans.class)
+public class AnnotatedConfigClassesWithoutAtConfigurationTests {
+
+ /**
+ * This is intentionally <b>not</b> annotated with {@code @Configuration}.
+ * Consequently, this class contains what we call <i>annotated factory bean
+ * methods</i> instead of standard bean definition methods.
+ */
+ static class AnnotatedFactoryBeans {
+
+ static final AtomicInteger enigmaCallCount = new AtomicInteger();
+
+
+ @Bean
+ public String enigma() {
+ return "enigma #" + enigmaCallCount.incrementAndGet();
+ }
+
+ @Bean
+ public LifecycleBean lifecycleBean() {
+ // The following call to enigma() literally invokes the local
+ // enigma() method, not a CGLIB proxied version, since these methods
+ // are essentially factory bean methods.
+ LifecycleBean bean = new LifecycleBean(enigma());
+ assertFalse(bean.isInitialized());
+ return bean;
+ }
+ }
+
+
+ @Autowired
+ private String enigma;
+
+ @Autowired
+ private LifecycleBean lifecycleBean;
+
+
+ @Test
+ public void testSPR_9051() throws Exception {
+ assertNotNull(enigma);
+ assertNotNull(lifecycleBean);
+ assertTrue(lifecycleBean.isInitialized());
+ Set<String> names = new HashSet<String>();
+ names.add(enigma.toString());
+ names.add(lifecycleBean.getName());
+ assertEquals(names, new HashSet<String>(Arrays.asList("enigma #1", "enigma #2")));
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AtBeanLiteModeScopeTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AtBeanLiteModeScopeTests.java
new file mode 100644
index 00000000..035bc9f2
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/AtBeanLiteModeScopeTests.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2012 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.spr9051;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Scope;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * Integration tests that verify proper scoping of beans created in
+ * <em>{@code @Bean} Lite Mode</em>.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = AtBeanLiteModeScopeTests.LiteBeans.class)
+public class AtBeanLiteModeScopeTests {
+
+ /**
+ * This is intentionally <b>not</b> annotated with {@code @Configuration}.
+ */
+ static class LiteBeans {
+
+ @Bean
+ public LifecycleBean singleton() {
+ LifecycleBean bean = new LifecycleBean("singleton");
+ assertFalse(bean.isInitialized());
+ return bean;
+ }
+
+ @Bean
+ @Scope("prototype")
+ public LifecycleBean prototype() {
+ LifecycleBean bean = new LifecycleBean("prototype");
+ assertFalse(bean.isInitialized());
+ return bean;
+ }
+ }
+
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Autowired
+ @Qualifier("singleton")
+ private LifecycleBean injectedSingletonBean;
+
+ @Autowired
+ @Qualifier("prototype")
+ private LifecycleBean injectedPrototypeBean;
+
+
+ @Test
+ public void singletonLiteBean() {
+ assertNotNull(injectedSingletonBean);
+ assertTrue(injectedSingletonBean.isInitialized());
+
+ LifecycleBean retrievedSingletonBean = applicationContext.getBean("singleton", LifecycleBean.class);
+ assertNotNull(retrievedSingletonBean);
+ assertTrue(retrievedSingletonBean.isInitialized());
+
+ assertSame(injectedSingletonBean, retrievedSingletonBean);
+ }
+
+ @Test
+ public void prototypeLiteBean() {
+ assertNotNull(injectedPrototypeBean);
+ assertTrue(injectedPrototypeBean.isInitialized());
+
+ LifecycleBean retrievedPrototypeBean = applicationContext.getBean("prototype", LifecycleBean.class);
+ assertNotNull(retrievedPrototypeBean);
+ assertTrue(retrievedPrototypeBean.isInitialized());
+
+ assertNotSame(injectedPrototypeBean, retrievedPrototypeBean);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/LifecycleBean.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/LifecycleBean.java
new file mode 100644
index 00000000..07c0550c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/LifecycleBean.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2012 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.spr9051;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * Simple POJO that contains lifecycle callbacks.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+public class LifecycleBean {
+
+ private final String name;
+
+ private boolean initialized = false;
+
+
+ public LifecycleBean(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ @PostConstruct
+ public void init() {
+ initialized = true;
+ }
+
+ public boolean isInitialized() {
+ return this.initialized;
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassWithAtConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassWithAtConfigurationTests.java
new file mode 100644
index 00000000..6e0f84f7
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassWithAtConfigurationTests.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9051;
+
+import static org.junit.Assert.assertSame;
+
+import javax.sql.DataSource;
+
+import org.junit.Before;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.transaction.PlatformTransactionManager;
+
+/**
+ * Concrete implementation of {@link AbstractTransactionalAnnotatedConfigClassTests}
+ * that uses a true {@link Configuration @Configuration class}.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ * @see TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests
+ */
+@ContextConfiguration
+public class TransactionalAnnotatedConfigClassWithAtConfigurationTests extends
+ AbstractTransactionalAnnotatedConfigClassTests {
+
+ /**
+ * This is <b>intentionally</b> annotated with {@code @Configuration}.
+ *
+ * <p>Consequently, this class contains standard singleton bean methods
+ * instead of <i>annotated factory bean methods</i>.
+ */
+ @Configuration
+ static class Config {
+
+ @Bean
+ public Employee employee() {
+ Employee employee = new Employee();
+ employee.setName("John Smith");
+ employee.setAge(42);
+ employee.setCompany("Acme Widgets, Inc.");
+ return employee;
+ }
+
+ @Bean
+ public PlatformTransactionManager transactionManager() {
+ return new DataSourceTransactionManager(dataSource());
+ }
+
+ @Bean
+ public DataSource dataSource() {
+ return new EmbeddedDatabaseBuilder()//
+ .addScript("classpath:/org/springframework/test/context/junit4/spr9051/schema.sql")//
+ // Ensure that this in-memory database is only used by this class:
+ .setName(getClass().getName())//
+ .build();
+ }
+
+ }
+
+
+ @Before
+ public void compareDataSources() throws Exception {
+ // NOTE: the two DataSource instances ARE the same!
+ assertSame(dataSourceFromTxManager, dataSourceViaInjection);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.java
new file mode 100644
index 00000000..622f0dfe
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9051;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+
+import javax.sql.DataSource;
+
+import org.junit.Before;
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
+import org.springframework.transaction.PlatformTransactionManager;
+
+/**
+ * Concrete implementation of {@link AbstractTransactionalAnnotatedConfigClassTests}
+ * that does <b>not</b> use a true {@link Configuration @Configuration class} but
+ * rather a <em>lite mode</em> configuration class (see the Javadoc for {@link Bean @Bean}
+ * for details).
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ * @see Bean
+ * @see TransactionalAnnotatedConfigClassWithAtConfigurationTests
+ */
+@ContextConfiguration(classes = TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests.AnnotatedFactoryBeans.class)
+public class TransactionalAnnotatedConfigClassesWithoutAtConfigurationTests extends
+ AbstractTransactionalAnnotatedConfigClassTests {
+
+ /**
+ * This is intentionally <b>not</b> annotated with {@code @Configuration}.
+ *
+ * <p>Consequently, this class contains <i>annotated factory bean methods</i>
+ * instead of standard singleton bean methods.
+ */
+ // @Configuration
+ static class AnnotatedFactoryBeans {
+
+ @Bean
+ public Employee employee() {
+ Employee employee = new Employee();
+ employee.setName("John Smith");
+ employee.setAge(42);
+ employee.setCompany("Acme Widgets, Inc.");
+ return employee;
+ }
+
+ @Bean
+ public PlatformTransactionManager transactionManager() {
+ return new DataSourceTransactionManager(dataSource());
+ }
+
+ /**
+ * Since this method does not reside in a true {@code @Configuration class},
+ * it acts as a factory method when invoked directly (e.g., from
+ * {@link #transactionManager()}) and as a singleton bean when retrieved
+ * through the application context (e.g., when injected into the test
+ * instance). The result is that this method will be called twice:
+ *
+ * <ol>
+ * <li>once <em>indirectly</em> by the {@link TransactionalTestExecutionListener}
+ * when it retrieves the {@link PlatformTransactionManager} from the
+ * application context</li>
+ * <li>and again when the {@link DataSource} is injected into the test
+ * instance in {@link AbstractTransactionalAnnotatedConfigClassTests#setDataSource(DataSource)}.</li>
+ *</ol>
+ *
+ * Consequently, the {@link JdbcTemplate} used by this test instance and
+ * the {@link PlatformTransactionManager} used by the Spring TestContext
+ * Framework will operate on two different {@code DataSource} instances,
+ * which is almost certainly not the desired or intended behavior.
+ */
+ @Bean
+ public DataSource dataSource() {
+ return new EmbeddedDatabaseBuilder()//
+ .addScript("classpath:/org/springframework/test/context/junit4/spr9051/schema.sql")//
+ // Ensure that this in-memory database is only used by this class:
+ .setName(getClass().getName())//
+ .build();
+ }
+
+ }
+
+
+ @Before
+ public void compareDataSources() throws Exception {
+ // NOTE: the two DataSource instances are NOT the same!
+ assertNotSame(dataSourceFromTxManager, dataSourceViaInjection);
+ }
+
+ /**
+ * Overrides {@code afterTransaction()} in order to assert a different result.
+ *
+ * <p>See in-line comments for details.
+ *
+ * @see AbstractTransactionalAnnotatedConfigClassTests#afterTransaction()
+ * @see AbstractTransactionalAnnotatedConfigClassTests#modifyTestDataWithinTransaction()
+ */
+ @AfterTransaction
+ @Override
+ public void afterTransaction() {
+ assertEquals("Deleting yoda", 1, deletePerson(YODA));
+
+ // NOTE: We would actually expect that there are now ZERO entries in the
+ // person table, since the transaction is rolled back by the framework;
+ // however, since our JdbcTemplate and the transaction manager used by
+ // the Spring TestContext Framework use two different DataSource
+ // instances, our insert statements were executed in transactions that
+ // are not controlled by the test framework. Consequently, there was no
+ // rollback for the two insert statements in
+ // modifyTestDataWithinTransaction().
+ //
+ assertNumRowsInPersonTable(2, "after a transactional test method");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/schema.sql b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/schema.sql
new file mode 100644
index 00000000..81d7e08d
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9051/schema.sql
@@ -0,0 +1,6 @@
+DROP TABLE person IF EXISTS;
+
+CREATE TABLE person (
+ name VARCHAR(20) NOT NULL,
+ PRIMARY KEY(name)
+); \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9604/LookUpTxMgrViaTransactionManagementConfigurerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9604/LookUpTxMgrViaTransactionManagementConfigurerTests.java
new file mode 100644
index 00000000..5dbba76a
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9604/LookUpTxMgrViaTransactionManagementConfigurerTests.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9604;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.tests.transaction.CallCountingTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.TransactionManagementConfigurer;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Integration tests that verify the behavior requested in
+ * <a href="https://jira.springsource.org/browse/SPR-9604">SPR-9604</a>.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@Transactional
+public class LookUpTxMgrViaTransactionManagementConfigurerTests {
+
+ private static final CallCountingTransactionManager txManager1 = new CallCountingTransactionManager();
+ private static final CallCountingTransactionManager txManager2 = new CallCountingTransactionManager();
+
+
+ @Configuration
+ static class Config implements TransactionManagementConfigurer {
+
+ @Override
+ public PlatformTransactionManager annotationDrivenTransactionManager() {
+ return txManager1();
+ }
+
+ @Bean
+ public PlatformTransactionManager txManager1() {
+ return txManager1;
+ }
+
+ @Bean
+ public PlatformTransactionManager txManager2() {
+ return txManager2;
+ }
+ }
+
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ txManager1.clear();
+ txManager2.clear();
+ }
+
+ @Test
+ public void transactionalTest() {
+ assertEquals(1, txManager1.begun);
+ assertEquals(1, txManager1.inflight);
+ assertEquals(0, txManager1.commits);
+ assertEquals(0, txManager1.rollbacks);
+
+ assertEquals(0, txManager2.begun);
+ assertEquals(0, txManager2.inflight);
+ assertEquals(0, txManager2.commits);
+ assertEquals(0, txManager2.rollbacks);
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertEquals(1, txManager1.begun);
+ assertEquals(0, txManager1.inflight);
+ assertEquals(0, txManager1.commits);
+ assertEquals(1, txManager1.rollbacks);
+
+ assertEquals(0, txManager2.begun);
+ assertEquals(0, txManager2.inflight);
+ assertEquals(0, txManager2.commits);
+ assertEquals(0, txManager2.rollbacks);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpNonexistentTxMgrTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpNonexistentTxMgrTests.java
new file mode 100644
index 00000000..78720a8f
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpNonexistentTxMgrTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9645;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.tests.transaction.CallCountingTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+
+/**
+ * Integration tests that verify the behavior requested in
+ * <a href="https://jira.springsource.org/browse/SPR-9645">SPR-9645</a>.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class LookUpNonexistentTxMgrTests {
+
+ private static final CallCountingTransactionManager txManager = new CallCountingTransactionManager();
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public PlatformTransactionManager transactionManager() {
+ return txManager;
+ }
+ }
+
+ @Test
+ public void nonTransactionalTest() {
+ assertEquals(0, txManager.begun);
+ assertEquals(0, txManager.inflight);
+ assertEquals(0, txManager.commits);
+ assertEquals(0, txManager.rollbacks);
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndDefaultNameTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndDefaultNameTests.java
new file mode 100644
index 00000000..331342d4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndDefaultNameTests.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9645;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.tests.transaction.CallCountingTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Integration tests that verify the behavior requested in
+ * <a href="https://jira.springsource.org/browse/SPR-9645">SPR-9645</a>.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@Transactional
+public class LookUpTxMgrByTypeAndDefaultNameTests {
+
+ private static final CallCountingTransactionManager txManager1 = new CallCountingTransactionManager();
+ private static final CallCountingTransactionManager txManager2 = new CallCountingTransactionManager();
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public PlatformTransactionManager transactionManager() {
+ return txManager1;
+ }
+
+ @Bean
+ public PlatformTransactionManager txManager2() {
+ return txManager2;
+ }
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ txManager1.clear();
+ txManager2.clear();
+ }
+
+ @Test
+ public void transactionalTest() {
+ assertEquals(1, txManager1.begun);
+ assertEquals(1, txManager1.inflight);
+ assertEquals(0, txManager1.commits);
+ assertEquals(0, txManager1.rollbacks);
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertEquals(1, txManager1.begun);
+ assertEquals(0, txManager1.inflight);
+ assertEquals(0, txManager1.commits);
+ assertEquals(1, txManager1.rollbacks);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndNameTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndNameTests.java
new file mode 100644
index 00000000..76a82b82
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndNameTests.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9645;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.test.context.transaction.TransactionConfiguration;
+import org.springframework.tests.transaction.CallCountingTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Integration tests that verify the behavior requested in
+ * <a href="https://jira.springsource.org/browse/SPR-9645">SPR-9645</a>.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@Transactional
+@TransactionConfiguration(transactionManager = "txManager1")
+public class LookUpTxMgrByTypeAndNameTests {
+
+ private static final CallCountingTransactionManager txManager1 = new CallCountingTransactionManager();
+ private static final CallCountingTransactionManager txManager2 = new CallCountingTransactionManager();
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public PlatformTransactionManager txManager1() {
+ return txManager1;
+ }
+
+ @Bean
+ public PlatformTransactionManager txManager2() {
+ return txManager2;
+ }
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ txManager1.clear();
+ txManager2.clear();
+ }
+
+ @Test
+ public void transactionalTest() {
+ assertEquals(1, txManager1.begun);
+ assertEquals(1, txManager1.inflight);
+ assertEquals(0, txManager1.commits);
+ assertEquals(0, txManager1.rollbacks);
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertEquals(1, txManager1.begun);
+ assertEquals(0, txManager1.inflight);
+ assertEquals(0, txManager1.commits);
+ assertEquals(1, txManager1.rollbacks);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndQualifierAtClassLevelTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndQualifierAtClassLevelTests.java
new file mode 100644
index 00000000..8492b02d
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndQualifierAtClassLevelTests.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9645;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.tests.transaction.CallCountingTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Integration tests that verify the behavior requested in
+ * <a href="https://jira.springsource.org/browse/SPR-9645">SPR-9645</a>.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@Transactional("txManager1")
+public class LookUpTxMgrByTypeAndQualifierAtClassLevelTests {
+
+ private static final CallCountingTransactionManager txManager1 = new CallCountingTransactionManager();
+ private static final CallCountingTransactionManager txManager2 = new CallCountingTransactionManager();
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public PlatformTransactionManager txManager1() {
+ return txManager1;
+ }
+
+ @Bean
+ public PlatformTransactionManager txManager2() {
+ return txManager2;
+ }
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ txManager1.clear();
+ txManager2.clear();
+ }
+
+ @Test
+ public void transactionalTest() {
+ assertEquals(1, txManager1.begun);
+ assertEquals(1, txManager1.inflight);
+ assertEquals(0, txManager1.commits);
+ assertEquals(0, txManager1.rollbacks);
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertEquals(1, txManager1.begun);
+ assertEquals(0, txManager1.inflight);
+ assertEquals(0, txManager1.commits);
+ assertEquals(1, txManager1.rollbacks);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndQualifierAtMethodLevelTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndQualifierAtMethodLevelTests.java
new file mode 100644
index 00000000..363d47a9
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeAndQualifierAtMethodLevelTests.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9645;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.tests.transaction.CallCountingTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Integration tests that verify the behavior requested in
+ * <a href="https://jira.springsource.org/browse/SPR-9645">SPR-9645</a>.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class LookUpTxMgrByTypeAndQualifierAtMethodLevelTests {
+
+ private static final CallCountingTransactionManager txManager1 = new CallCountingTransactionManager();
+ private static final CallCountingTransactionManager txManager2 = new CallCountingTransactionManager();
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public PlatformTransactionManager txManager1() {
+ return txManager1;
+ }
+
+ @Bean
+ public PlatformTransactionManager txManager2() {
+ return txManager2;
+ }
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ txManager1.clear();
+ txManager2.clear();
+ }
+
+ @Transactional("txManager1")
+ @Test
+ public void transactionalTest() {
+ assertEquals(1, txManager1.begun);
+ assertEquals(1, txManager1.inflight);
+ assertEquals(0, txManager1.commits);
+ assertEquals(0, txManager1.rollbacks);
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertEquals(1, txManager1.begun);
+ assertEquals(0, txManager1.inflight);
+ assertEquals(0, txManager1.commits);
+ assertEquals(1, txManager1.rollbacks);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeTests.java
new file mode 100644
index 00000000..957533f9
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9645/LookUpTxMgrByTypeTests.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.junit4.spr9645;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.tests.transaction.CallCountingTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Integration tests that verify the behavior requested in
+ * <a href="https://jira.springsource.org/browse/SPR-9645">SPR-9645</a>.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@Transactional
+public class LookUpTxMgrByTypeTests {
+
+ private static final CallCountingTransactionManager txManager = new CallCountingTransactionManager();
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public PlatformTransactionManager txManager() {
+ return txManager;
+ }
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ txManager.clear();
+ }
+
+ @Test
+ public void transactionalTest() {
+ assertEquals(1, txManager.begun);
+ assertEquals(1, txManager.inflight);
+ assertEquals(0, txManager.commits);
+ assertEquals(0, txManager.rollbacks);
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertEquals(1, txManager.begun);
+ assertEquals(0, txManager.inflight);
+ assertEquals(0, txManager.commits);
+ assertEquals(1, txManager.rollbacks);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799AnnotationConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799AnnotationConfigTests.java
new file mode 100644
index 00000000..647b0960
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799AnnotationConfigTests.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2012 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.spr9799;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+/**
+ * Integration tests used to assess claims raised in
+ * <a href="https://jira.springsource.org/browse/SPR-9799" target="_blank">SPR-9799</a>.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ * @see Spr9799XmlConfigTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+// NOTE: if we omit the @WebAppConfiguration declaration, the ApplicationContext will fail
+// to load since @EnableWebMvc requires that the context be a WebApplicationContext.
+@WebAppConfiguration
+public class Spr9799AnnotationConfigTests {
+
+ @Configuration
+ @EnableWebMvc
+ static class Config {
+ /* intentionally no beans defined */
+ }
+
+
+ @Test
+ public void applicationContextLoads() {
+ // no-op
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests.java
new file mode 100644
index 00000000..4e93b67c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2012 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.spr9799;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+/**
+ * Integration tests used to assess claims raised in
+ * <a href="https://jira.springsource.org/browse/SPR-9799" target="_blank">SPR-9799</a>.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ * @see Spr9799AnnotationConfigTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+public class Spr9799XmlConfigTests {
+
+ @Test
+ public void applicationContextLoads() {
+ // nothing to assert: we just want to make sure that the context loads without
+ // errors.
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/transactionalTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/junit4/transactionalTests-context.xml
new file mode 100644
index 00000000..01a7cf9f
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/junit4/transactionalTests-context.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
+ p:driverClassName="org.hsqldb.jdbcDriver" p:url="jdbc:hsqldb:mem:transactional_tests" p:username="sa" p:password=""/>
+
+ <bean id="dataSource2" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
+ p:driverClassName="org.hsqldb.jdbcDriver" p:url="jdbc:hsqldb:mem:transactional_tests" p:username="sa" p:password=""/>
+
+ <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:dataSource-ref="dataSource"/>
+
+ <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:dataSource-ref="dataSource2">
+ </bean>
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/AnnotatedFooConfigInnerClassTestCase.java b/spring-test/src/test/java/org/springframework/test/context/support/AnnotatedFooConfigInnerClassTestCase.java
new file mode 100644
index 00000000..bb2e80b9
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/AnnotatedFooConfigInnerClassTestCase.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Not an actual <em>test case</em>.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see AnnotationConfigContextLoaderTests
+ */
+public class AnnotatedFooConfigInnerClassTestCase {
+
+ @Configuration
+ static class FooConfig {
+
+ @Bean
+ public String foo() {
+ return "foo";
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderTests.java
new file mode 100644
index 00000000..9d071b16
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/AnnotationConfigContextLoaderTests.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link AnnotationConfigContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+public class AnnotationConfigContextLoaderTests {
+
+ private final AnnotationConfigContextLoader contextLoader = new AnnotationConfigContextLoader();
+
+
+ @Test
+ public void detectDefaultConfigurationClassesForAnnotatedInnerClass() {
+ Class<?>[] configClasses = contextLoader.detectDefaultConfigurationClasses(ContextConfigurationInnerClassTestCase.class);
+ assertNotNull(configClasses);
+ assertEquals("annotated static ContextConfiguration should be considered.", 1, configClasses.length);
+
+ configClasses = contextLoader.detectDefaultConfigurationClasses(AnnotatedFooConfigInnerClassTestCase.class);
+ assertNotNull(configClasses);
+ assertEquals("annotated static FooConfig should be considered.", 1, configClasses.length);
+ }
+
+ @Test
+ public void detectDefaultConfigurationClassesForMultipleAnnotatedInnerClasses() {
+ Class<?>[] configClasses = contextLoader.detectDefaultConfigurationClasses(MultipleStaticConfigurationClassesTestCase.class);
+ assertNotNull(configClasses);
+ assertEquals("multiple annotated static classes should be considered.", 2, configClasses.length);
+ }
+
+ @Test
+ public void detectDefaultConfigurationClassesForNonAnnotatedInnerClass() {
+ Class<?>[] configClasses = contextLoader.detectDefaultConfigurationClasses(PlainVanillaFooConfigInnerClassTestCase.class);
+ assertNotNull(configClasses);
+ assertEquals("non-annotated static FooConfig should NOT be considered.", 0, configClasses.length);
+ }
+
+ @Test
+ public void detectDefaultConfigurationClassesForFinalAnnotatedInnerClass() {
+ Class<?>[] configClasses = contextLoader.detectDefaultConfigurationClasses(FinalConfigInnerClassTestCase.class);
+ assertNotNull(configClasses);
+ assertEquals("final annotated static Config should NOT be considered.", 0, configClasses.length);
+ }
+
+ @Test
+ public void detectDefaultConfigurationClassesForPrivateAnnotatedInnerClass() {
+ Class<?>[] configClasses = contextLoader.detectDefaultConfigurationClasses(PrivateConfigInnerClassTestCase.class);
+ assertNotNull(configClasses);
+ assertEquals("private annotated inner classes should NOT be considered.", 0, configClasses.length);
+ }
+
+ @Test
+ public void detectDefaultConfigurationClassesForNonStaticAnnotatedInnerClass() {
+ Class<?>[] configClasses = contextLoader.detectDefaultConfigurationClasses(NonStaticConfigInnerClassesTestCase.class);
+ assertNotNull(configClasses);
+ assertEquals("non-static annotated inner classes should NOT be considered.", 0, configClasses.length);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/ContextConfigurationInnerClassTestCase.java b/spring-test/src/test/java/org/springframework/test/context/support/ContextConfigurationInnerClassTestCase.java
new file mode 100644
index 00000000..ea0c4e84
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/ContextConfigurationInnerClassTestCase.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Not an actual <em>test case</em>.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see AnnotationConfigContextLoaderTests
+ */
+public class ContextConfigurationInnerClassTestCase {
+
+ @Configuration
+ static class ContextConfiguration {
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests-context.xml
new file mode 100644
index 00000000..a0570128
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests-context.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <!-- intentionally empty: only needed so that the ContextLoader can load a file -->
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests.java b/spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests.java
new file mode 100644
index 00000000..1d930831
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import org.junit.Test;
+
+import org.springframework.context.support.GenericApplicationContext;
+
+/**
+ * Unit test which verifies that extensions of
+ * {@link AbstractGenericContextLoader} are able to <em>customize</em> the
+ * newly created {@code ApplicationContext}. Specifically, this test
+ * addresses the issues raised in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-4008"
+ * target="_blank">SPR-4008</a>: <em>Supply an opportunity to customize context
+ * before calling refresh in ContextLoaders</em>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public class CustomizedGenericXmlContextLoaderTests {
+
+ @Test
+ public void customizeContext() throws Exception {
+
+ final StringBuilder builder = new StringBuilder();
+ final String expectedContents = "customizeContext() was called";
+
+ new GenericXmlContextLoader() {
+
+ @Override
+ protected void customizeContext(GenericApplicationContext context) {
+ assertFalse("The context should not yet have been refreshed.", context.isActive());
+ builder.append(expectedContents);
+ }
+ }.loadContext("classpath:/org/springframework/test/context/support/CustomizedGenericXmlContextLoaderTests-context.xml");
+
+ assertEquals("customizeContext() should have been called.", expectedContents, builder.toString());
+ }
+
+}
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
new file mode 100644
index 00000000..20355683
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/DelegatingSmartContextLoaderTests.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfigurationAttributes;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.test.context.MergedContextConfiguration;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Unit tests for {@link DelegatingSmartContextLoader}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+public class DelegatingSmartContextLoaderTests {
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
+
+ private final DelegatingSmartContextLoader loader = new DelegatingSmartContextLoader();
+
+
+ private static void assertEmpty(Object[] array) {
+ assertTrue(ObjectUtils.isEmpty(array));
+ }
+
+ // --- SmartContextLoader - processContextConfiguration() ------------------
+
+ @Test(expected = IllegalStateException.class)
+ public void processContextConfigurationWithoutLocationsAndConfigurationClassesForBogusTestClass() {
+ 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);
+ loader.processContextConfiguration(configAttributes);
+ assertEquals(1, configAttributes.getLocations().length);
+ assertEmpty(configAttributes.getClasses());
+ }
+
+ @Test
+ public void processContextConfigurationWithDefaultConfigurationClassGeneration() {
+ ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes(ConfigClassTestCase.class,
+ EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class);
+ loader.processContextConfiguration(configAttributes);
+ assertEquals(1, configAttributes.getClasses().length);
+ assertEmpty(configAttributes.getLocations());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void processContextConfigurationWithDefaultXmlConfigAndConfigurationClassGeneration() {
+ ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes(
+ ImproperDuplicateDefaultXmlAndConfigClassTestCase.class, EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, true, null,
+ true, ContextLoader.class);
+ loader.processContextConfiguration(configAttributes);
+ }
+
+ @Test
+ public void processContextConfigurationWithLocation() {
+ String[] locations = new String[] { "classpath:/foo.xml" };
+ ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes(getClass(), locations,
+ EMPTY_CLASS_ARRAY, true, null, true, ContextLoader.class);
+ loader.processContextConfiguration(configAttributes);
+ assertArrayEquals(locations, configAttributes.getLocations());
+ assertEmpty(configAttributes.getClasses());
+ }
+
+ @Test
+ public void processContextConfigurationWithConfigurationClass() {
+ Class<?>[] classes = new Class<?>[] { getClass() };
+ ContextConfigurationAttributes configAttributes = new ContextConfigurationAttributes(getClass(),
+ EMPTY_STRING_ARRAY, classes, true, null, true, ContextLoader.class);
+ loader.processContextConfiguration(configAttributes);
+ assertArrayEquals(classes, configAttributes.getClasses());
+ assertEmpty(configAttributes.getLocations());
+ }
+
+ // --- SmartContextLoader - loadContext() ----------------------------------
+
+ @Test(expected = IllegalArgumentException.class)
+ public void loadContextWithNullConfig() throws Exception {
+ MergedContextConfiguration mergedConfig = null;
+ loader.loadContext(mergedConfig);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void loadContextWithoutLocationsAndConfigurationClasses() throws Exception {
+ MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ loader.loadContext(mergedConfig);
+ }
+
+ private void assertApplicationContextLoadsAndContainsFooString(MergedContextConfiguration mergedConfig)
+ throws Exception {
+ ApplicationContext applicationContext = loader.loadContext(mergedConfig);
+ assertNotNull(applicationContext);
+ assertEquals("foo", applicationContext.getBean(String.class));
+ assertTrue(applicationContext instanceof ConfigurableApplicationContext);
+ ((ConfigurableApplicationContext) applicationContext).close();
+ }
+
+ @Test
+ public void loadContextWithXmlConfig() throws Exception {
+ MergedContextConfiguration mergedConfig = new MergedContextConfiguration(
+ XmlTestCase.class,
+ new String[] { "classpath:/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$XmlTestCase-context.xml" },
+ EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
+ assertApplicationContextLoadsAndContainsFooString(mergedConfig);
+ }
+
+ @Test
+ public void loadContextWithConfigurationClass() throws Exception {
+ MergedContextConfiguration mergedConfig = new MergedContextConfiguration(ConfigClassTestCase.class,
+ EMPTY_STRING_ARRAY, new Class<?>[] { ConfigClassTestCase.Config.class }, EMPTY_STRING_ARRAY, loader);
+ assertApplicationContextLoadsAndContainsFooString(mergedConfig);
+ }
+
+ // --- ContextLoader -------------------------------------------------------
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void processLocations() {
+ loader.processLocations(getClass(), EMPTY_STRING_ARRAY);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void loadContextFromLocations() throws Exception {
+ loader.loadContext(EMPTY_STRING_ARRAY);
+ }
+
+
+ // -------------------------------------------------------------------------
+
+ static class XmlTestCase {
+ }
+
+ static class ConfigClassTestCase {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return new String("foo");
+ }
+ }
+
+ static class NotAConfigClass {
+
+ }
+ }
+
+ static class ImproperDuplicateDefaultXmlAndConfigClassTestCase {
+
+ @Configuration
+ static class Config {
+ // intentionally empty: we just need the class to be present to fail
+ // the test
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/FinalConfigInnerClassTestCase.java b/spring-test/src/test/java/org/springframework/test/context/support/FinalConfigInnerClassTestCase.java
new file mode 100644
index 00000000..1c5d85d6
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/FinalConfigInnerClassTestCase.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Not an actual <em>test case</em>.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see AnnotationConfigContextLoaderTests
+ */
+public class FinalConfigInnerClassTestCase {
+
+ // Intentionally FINAL.
+ @Configuration
+ static final class Config {
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderResourceLocationsTests$1ClasspathExistentDefaultLocationsTestCase-context.xml b/spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderResourceLocationsTests$1ClasspathExistentDefaultLocationsTestCase-context.xml
new file mode 100644
index 00000000..44d11048
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderResourceLocationsTests$1ClasspathExistentDefaultLocationsTestCase-context.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+ <!-- intentionally empty: only needed so that the ContextLoader can find this file -->
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderResourceLocationsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderResourceLocationsTests.java
new file mode 100644
index 00000000..bf53acc5
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/GenericXmlContextLoaderResourceLocationsTests.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * JUnit 4 based unit test which verifies proper
+ * {@link ContextLoader#processLocations(Class, String...) processing} of
+ * {@code resource locations} by a {@link GenericXmlContextLoader}
+ * configured via {@link ContextConfiguration @ContextConfiguration}.
+ * Specifically, this test addresses the issues raised in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3949"
+ * target="_blank">SPR-3949</a>:
+ * <em>ContextConfiguration annotation should accept not only classpath resources</em>.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@RunWith(Parameterized.class)
+public class GenericXmlContextLoaderResourceLocationsTests {
+
+ private static final Log logger = LogFactory.getLog(GenericXmlContextLoaderResourceLocationsTests.class);
+
+ protected final Class<?> testClass;
+ protected final String[] expectedLocations;
+
+
+ public GenericXmlContextLoaderResourceLocationsTests(final Class<?> testClass, final String[] expectedLocations) {
+ this.testClass = testClass;
+ this.expectedLocations = expectedLocations;
+ }
+
+ @Parameters
+ public static Collection<Object[]> contextConfigurationLocationsData() {
+ @ContextConfiguration
+ class ClasspathNonExistentDefaultLocationsTestCase {
+ }
+
+ @ContextConfiguration
+ class ClasspathExistentDefaultLocationsTestCase {
+ }
+
+ @ContextConfiguration({ "context1.xml", "context2.xml" })
+ class ImplicitClasspathLocationsTestCase {
+ }
+
+ @ContextConfiguration("classpath:context.xml")
+ class ExplicitClasspathLocationsTestCase {
+ }
+
+ @ContextConfiguration("file:/testing/directory/context.xml")
+ class ExplicitFileLocationsTestCase {
+ }
+
+ @ContextConfiguration("http://example.com/context.xml")
+ class ExplicitUrlLocationsTestCase {
+ }
+
+ @ContextConfiguration({ "context1.xml", "classpath:context2.xml", "/context3.xml",
+ "file:/testing/directory/context.xml", "http://example.com/context.xml" })
+ class ExplicitMixedPathTypesLocationsTestCase {
+ }
+
+ return Arrays.asList(new Object[][] {
+
+ { ClasspathNonExistentDefaultLocationsTestCase.class, new String[] {} },
+
+ {
+ ClasspathExistentDefaultLocationsTestCase.class,
+ new String[] { "classpath:/org/springframework/test/context/support/GenericXmlContextLoaderResourceLocationsTests$1ClasspathExistentDefaultLocationsTestCase-context.xml" } },
+
+ {
+ ImplicitClasspathLocationsTestCase.class,
+ new String[] { "classpath:/org/springframework/test/context/support/context1.xml",
+ "classpath:/org/springframework/test/context/support/context2.xml" } },
+
+ { ExplicitClasspathLocationsTestCase.class, new String[] { "classpath:context.xml" } },
+
+ { ExplicitFileLocationsTestCase.class, new String[] { "file:/testing/directory/context.xml" } },
+
+ { ExplicitUrlLocationsTestCase.class, new String[] { "http://example.com/context.xml" } },
+
+ {
+ ExplicitMixedPathTypesLocationsTestCase.class,
+ new String[] { "classpath:/org/springframework/test/context/support/context1.xml",
+ "classpath:context2.xml", "classpath:/context3.xml", "file:/testing/directory/context.xml",
+ "http://example.com/context.xml" } }
+
+ });
+ }
+
+ @Test
+ public void assertContextConfigurationLocations() throws Exception {
+
+ final ContextConfiguration contextConfig = this.testClass.getAnnotation(ContextConfiguration.class);
+ final ContextLoader contextLoader = new GenericXmlContextLoader();
+ final String[] configuredLocations = (String[]) AnnotationUtils.getValue(contextConfig);
+ final String[] processedLocations = contextLoader.processLocations(this.testClass, configuredLocations);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("----------------------------------------------------------------------");
+ logger.debug("Configured locations: " + ObjectUtils.nullSafeToString(configuredLocations));
+ logger.debug("Expected locations: " + ObjectUtils.nullSafeToString(this.expectedLocations));
+ logger.debug("Processed locations: " + ObjectUtils.nullSafeToString(processedLocations));
+ }
+
+ assertArrayEquals("Verifying locations for test [" + this.testClass + "].", this.expectedLocations,
+ processedLocations);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/MultipleStaticConfigurationClassesTestCase.java b/spring-test/src/test/java/org/springframework/test/context/support/MultipleStaticConfigurationClassesTestCase.java
new file mode 100644
index 00000000..a9069a1d
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/MultipleStaticConfigurationClassesTestCase.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Not an actual <em>test case</em>.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see AnnotationConfigContextLoaderTests
+ */
+public class MultipleStaticConfigurationClassesTestCase {
+
+ @Configuration
+ static class ConfigA {
+ }
+
+ @Configuration
+ static class ConfigB {
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/NonStaticConfigInnerClassesTestCase.java b/spring-test/src/test/java/org/springframework/test/context/support/NonStaticConfigInnerClassesTestCase.java
new file mode 100644
index 00000000..db9d9179
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/NonStaticConfigInnerClassesTestCase.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Not an actual <em>test case</em>.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see AnnotationConfigContextLoaderTests
+ */
+public class NonStaticConfigInnerClassesTestCase {
+
+ // Intentionally not static
+ @Configuration
+ class FooConfig {
+ }
+
+ // Intentionally not static
+ @Configuration
+ class BarConfig {
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/PlainVanillaFooConfigInnerClassTestCase.java b/spring-test/src/test/java/org/springframework/test/context/support/PlainVanillaFooConfigInnerClassTestCase.java
new file mode 100644
index 00000000..427191bc
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/PlainVanillaFooConfigInnerClassTestCase.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+/**
+ * Not an actual <em>test case</em>.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see AnnotationConfigContextLoaderTests
+ */
+public class PlainVanillaFooConfigInnerClassTestCase {
+
+ // Intentionally NOT annotated with @Configuration
+ static class FooConfig {
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/support/PrivateConfigInnerClassTestCase.java b/spring-test/src/test/java/org/springframework/test/context/support/PrivateConfigInnerClassTestCase.java
new file mode 100644
index 00000000..5432f578
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/support/PrivateConfigInnerClassTestCase.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2012 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.support;
+
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Not an actual <em>test case</em>.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ * @see AnnotationConfigContextLoaderTests
+ */
+public class PrivateConfigInnerClassTestCase {
+
+ // Intentionally private
+ @Configuration
+ private static class PrivateConfig {
+ }
+
+}
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
new file mode 100644
index 00000000..66ffc202
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/AnnotationConfigTransactionalTestNGSpringContextTests.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.testng;
+
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+import static org.springframework.test.transaction.TransactionTestUtils.inTransaction;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import javax.sql.DataSource;
+
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.test.annotation.NotTransactional;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Integration tests that verify support for
+ * {@link org.springframework.context.annotation.Configuration @Configuration} classes
+ * with TestNG-based tests.
+ *
+ * <p>
+ * Configuration will be loaded from
+ * {@link AnnotationConfigTransactionalTestNGSpringContextTests.ContextConfiguration}.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+@SuppressWarnings("deprecation")
+@ContextConfiguration
+public class AnnotationConfigTransactionalTestNGSpringContextTests extends
+ AbstractTransactionalTestNGSpringContextTests {
+
+ private static final String JANE = "jane";
+ private static final String SUE = "sue";
+ private static final String YODA = "yoda";
+
+ private static final int NUM_TESTS = 2;
+ private static final int NUM_TX_TESTS = 1;
+
+ private static int numSetUpCalls = 0;
+ private static int numSetUpCallsInTransaction = 0;
+ private static int numTearDownCalls = 0;
+ private static int numTearDownCallsInTransaction = 0;
+
+ @Autowired
+ private Employee employee;
+
+ @Autowired
+ private Pet pet;
+
+
+ private int createPerson(String name) {
+ return simpleJdbcTemplate.update("INSERT INTO person VALUES(?)", name);
+ }
+
+ private int deletePerson(String name) {
+ return simpleJdbcTemplate.update("DELETE FROM person WHERE name=?", name);
+ }
+
+ private void assertNumRowsInPersonTable(int expectedNumRows, String testState) {
+ assertEquals(countRowsInTable("person"), expectedNumRows, "the number of rows in the person table ("
+ + testState + ").");
+ }
+
+ private void assertAddPerson(final String name) {
+ assertEquals(createPerson(name), 1, "Adding '" + name + "'");
+ }
+
+ @BeforeClass
+ public void beforeClass() {
+ numSetUpCalls = 0;
+ numSetUpCallsInTransaction = 0;
+ numTearDownCalls = 0;
+ numTearDownCallsInTransaction = 0;
+ }
+
+ @AfterClass
+ public 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().");
+ assertEquals(numTearDownCallsInTransaction, NUM_TX_TESTS, "number of calls to tearDown() within a transaction.");
+ }
+
+ @Test
+ @NotTransactional
+ public void autowiringFromConfigClass() {
+ assertNotNull(employee, "The employee should have been autowired.");
+ assertEquals(employee.getName(), "John Smith");
+
+ assertNotNull(pet, "The pet should have been autowired.");
+ assertEquals(pet.getName(), "Fido");
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ assertNumRowsInPersonTable(1, "before a transactional test method");
+ assertAddPerson(YODA);
+ }
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ numSetUpCalls++;
+ if (inTransaction()) {
+ numSetUpCallsInTransaction++;
+ }
+ assertNumRowsInPersonTable((inTransaction() ? 2 : 1), "before a test method");
+ }
+
+ @Test
+ public void modifyTestDataWithinTransaction() {
+ assertInTransaction(true);
+ assertAddPerson(JANE);
+ assertAddPerson(SUE);
+ assertNumRowsInPersonTable(4, "in modifyTestDataWithinTransaction()");
+ }
+
+ @AfterMethod
+ public void tearDown() throws Exception {
+ numTearDownCalls++;
+ if (inTransaction()) {
+ numTearDownCallsInTransaction++;
+ }
+ assertNumRowsInPersonTable((inTransaction() ? 4 : 1), "after a test method");
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ assertEquals(deletePerson(YODA), 1, "Deleting yoda");
+ assertNumRowsInPersonTable(1, "after a transactional test method");
+ }
+
+
+ @Configuration
+ static class ContextConfiguration {
+
+ @Bean
+ public Employee employee() {
+ Employee employee = new Employee();
+ employee.setName("John Smith");
+ employee.setAge(42);
+ employee.setCompany("Acme Widgets, Inc.");
+ return employee;
+ }
+
+ @Bean
+ public Pet pet() {
+ return new Pet("Fido");
+ }
+
+ @Bean
+ public PlatformTransactionManager transactionManager() {
+ return new DataSourceTransactionManager(dataSource());
+ }
+
+ @Bean
+ public DataSource dataSource() {
+ return new EmbeddedDatabaseBuilder()//
+ .addScript("classpath:/org/springframework/test/context/testng/schema.sql")//
+ .addScript("classpath:/org/springframework/test/context/testng/data.sql")//
+ .build();
+ }
+
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests-context.xml
new file mode 100644
index 00000000..a46a6da3
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests-context.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
+
+ <bean id="employee" class="org.springframework.tests.sample.beans.Employee" p:name="John Smith" p:age="42"
+ p:company="Acme Widgets, Inc." />
+
+ <bean id="pet" class="org.springframework.tests.sample.beans.Pet" c:_="Fido" />
+
+ <bean id="foo" class="java.lang.String" c:_="Foo" />
+
+ <bean id="bar" class="java.lang.String" c:_="Bar" />
+
+ <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:data-source-ref="dataSource" />
+
+ <jdbc:embedded-database id="dataSource">
+ <jdbc:script location="classpath:/org/springframework/test/context/testng/schema.sql" />
+ <jdbc:script location="classpath:/org/springframework/test/context/testng/data.sql" />
+ </jdbc:embedded-database>
+
+</beans>
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
new file mode 100644
index 00000000..ee25cb32
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/ConcreteTransactionalTestNGSpringContextTests.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.testng;
+
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+import static org.springframework.test.transaction.TransactionTestUtils.inTransaction;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import javax.annotation.Resource;
+
+import org.springframework.tests.sample.beans.Employee;
+import org.springframework.tests.sample.beans.Pet;
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.annotation.NotTransactional;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Combined integration test for {@link AbstractTestNGSpringContextTests} and
+ * {@link AbstractTransactionalTestNGSpringContextTests}.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@SuppressWarnings("deprecation")
+@ContextConfiguration
+public class ConcreteTransactionalTestNGSpringContextTests extends AbstractTransactionalTestNGSpringContextTests
+ implements BeanNameAware, InitializingBean {
+
+ private static final String JANE = "jane";
+ private static final String SUE = "sue";
+ private static final String YODA = "yoda";
+
+ private static final int NUM_TESTS = 8;
+ private static final int NUM_TX_TESTS = 1;
+
+ private static int numSetUpCalls = 0;
+ private static int numSetUpCallsInTransaction = 0;
+ private static int numTearDownCalls = 0;
+ private static int numTearDownCallsInTransaction = 0;
+
+ private boolean beanInitialized = false;
+
+ private String beanName = "replace me with [" + getClass().getName() + "]";
+
+ private Employee employee;
+
+ @Autowired
+ private Pet pet;
+
+ @Autowired(required = false)
+ protected Long nonrequiredLong;
+
+ @Resource()
+ protected String foo;
+
+ protected String bar;
+
+
+ private int createPerson(String name) {
+ return simpleJdbcTemplate.update("INSERT INTO person VALUES(?)", name);
+ }
+
+ private int deletePerson(String name) {
+ return simpleJdbcTemplate.update("DELETE FROM person WHERE name=?", name);
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ this.beanInitialized = true;
+ }
+
+ @Override
+ public void setBeanName(String beanName) {
+ this.beanName = beanName;
+ }
+
+ @Autowired
+ protected void setEmployee(Employee employee) {
+ this.employee = employee;
+ }
+
+ @Resource
+ protected void setBar(String bar) {
+ this.bar = bar;
+ }
+
+ private void assertNumRowsInPersonTable(int expectedNumRows, String testState) {
+ assertEquals(countRowsInTable("person"), expectedNumRows, "the number of rows in the person table ("
+ + testState + ").");
+ }
+
+ private void assertAddPerson(final String name) {
+ assertEquals(createPerson(name), 1, "Adding '" + name + "'");
+ }
+
+ @BeforeClass
+ public void beforeClass() {
+ numSetUpCalls = 0;
+ numSetUpCallsInTransaction = 0;
+ numTearDownCalls = 0;
+ numTearDownCallsInTransaction = 0;
+ }
+
+ @AfterClass
+ public 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().");
+ assertEquals(numTearDownCallsInTransaction, NUM_TX_TESTS, "number of calls to tearDown() within a transaction.");
+ }
+
+ @Test
+ @NotTransactional
+ public void verifyApplicationContextSet() {
+ assertInTransaction(false);
+ assertNotNull(super.applicationContext,
+ "The application context should have been set due to ApplicationContextAware semantics.");
+ Employee employeeBean = (Employee) super.applicationContext.getBean("employee");
+ assertEquals(employeeBean.getName(), "John Smith", "employee's name.");
+ }
+
+ @Test
+ @NotTransactional
+ public void verifyBeanInitialized() {
+ assertInTransaction(false);
+ assertTrue(beanInitialized,
+ "This test instance should have been initialized due to InitializingBean semantics.");
+ }
+
+ @Test
+ @NotTransactional
+ public void verifyBeanNameSet() {
+ assertInTransaction(false);
+ assertEquals(beanName, getClass().getName(),
+ "The bean name of this test instance should have been set due to BeanNameAware semantics.");
+ }
+
+ @Test
+ @NotTransactional
+ public void verifyAnnotationAutowiredFields() {
+ assertInTransaction(false);
+ assertNull(nonrequiredLong, "The nonrequiredLong field should NOT have been autowired.");
+ assertNotNull(pet, "The pet field should have been autowired.");
+ assertEquals(pet.getName(), "Fido", "pet's name.");
+ }
+
+ @Test
+ @NotTransactional
+ public void verifyAnnotationAutowiredMethods() {
+ assertInTransaction(false);
+ assertNotNull(employee, "The setEmployee() method should have been autowired.");
+ assertEquals(employee.getName(), "John Smith", "employee's name.");
+ }
+
+ @Test
+ @NotTransactional
+ public void verifyResourceAnnotationInjectedFields() {
+ assertInTransaction(false);
+ assertEquals(foo, "Foo", "The foo field should have been injected via @Resource.");
+ }
+
+ @Test
+ @NotTransactional
+ public void verifyResourceAnnotationInjectedMethods() {
+ assertInTransaction(false);
+ assertEquals(bar, "Bar", "The setBar() method should have been injected via @Resource.");
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ assertNumRowsInPersonTable(1, "before a transactional test method");
+ assertAddPerson(YODA);
+ }
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ numSetUpCalls++;
+ if (inTransaction()) {
+ numSetUpCallsInTransaction++;
+ }
+ assertNumRowsInPersonTable((inTransaction() ? 2 : 1), "before a test method");
+ }
+
+ @Test
+ public void modifyTestDataWithinTransaction() {
+ assertInTransaction(true);
+ assertAddPerson(JANE);
+ assertAddPerson(SUE);
+ assertNumRowsInPersonTable(4, "in modifyTestDataWithinTransaction()");
+ }
+
+ @AfterMethod
+ public void tearDown() throws Exception {
+ numTearDownCalls++;
+ if (inTransaction()) {
+ numTearDownCallsInTransaction++;
+ }
+ assertNumRowsInPersonTable((inTransaction() ? 4 : 1), "after a test method");
+ }
+
+ @AfterTransaction
+ public 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/DirtiesContextTransactionalTestNGSpringContextTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/testng/DirtiesContextTransactionalTestNGSpringContextTests-context.xml
new file mode 100644
index 00000000..f1786428
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/DirtiesContextTransactionalTestNGSpringContextTests-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
+
+ <jdbc:embedded-database id="dataSource" />
+
+ <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:data-source-ref="dataSource" />
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/DirtiesContextTransactionalTestNGSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/DirtiesContextTransactionalTestNGSpringContextTests.java
new file mode 100644
index 00000000..82fb1928
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/DirtiesContextTransactionalTestNGSpringContextTests.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2012 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.testng;
+
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNotSame;
+import static org.testng.Assert.assertSame;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestContextManager;
+import org.testng.annotations.Test;
+
+/**
+ * <p>
+ * TestNG based integration test to assess the claim in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3880"
+ * target="_blank">SPR-3880</a> that a &quot;context marked dirty using
+ * {@link DirtiesContext &#064;DirtiesContext} in [a] TestNG based test is not
+ * reloaded in subsequent tests&quot;.
+ * </p>
+ * <p>
+ * After careful analysis, it turns out that the {@link ApplicationContext} was
+ * in fact reloaded; however, due to how the test instance was instrumented with
+ * the {@link TestContextManager} in {@link AbstractTestNGSpringContextTests},
+ * dependency injection was not being performed on the test instance between
+ * individual tests. DirtiesContextTransactionalTestNGSpringContextTests
+ * therefore verifies the expected behavior and correct semantics.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@ContextConfiguration
+public class DirtiesContextTransactionalTestNGSpringContextTests extends AbstractTransactionalTestNGSpringContextTests {
+
+ private ApplicationContext dirtiedApplicationContext;
+
+
+ @SuppressWarnings("deprecation")
+ private void performCommonAssertions() {
+ assertInTransaction(true);
+ assertNotNull(super.applicationContext,
+ "The application context should have been set due to ApplicationContextAware semantics.");
+ assertNotNull(super.simpleJdbcTemplate,
+ "The SimpleJdbcTemplate should have been created in setDataSource() via DI for the DataSource.");
+ }
+
+ @Test
+ @DirtiesContext
+ public void dirtyContext() {
+ performCommonAssertions();
+ this.dirtiedApplicationContext = super.applicationContext;
+ }
+
+ @Test(dependsOnMethods = { "dirtyContext" })
+ public void verifyContextWasDirtied() {
+ performCommonAssertions();
+ assertNotSame(super.applicationContext, this.dirtiedApplicationContext,
+ "The application context should have been 'dirtied'.");
+ this.dirtiedApplicationContext = super.applicationContext;
+ }
+
+ @Test(dependsOnMethods = { "verifyContextWasDirtied" })
+ public void verifyContextWasNotDirtied() {
+ assertSame(this.applicationContext, this.dirtiedApplicationContext,
+ "The application context should NOT have been 'dirtied'.");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests-context.xml
new file mode 100644
index 00000000..f1786428
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
+
+ <jdbc:embedded-database id="dataSource" />
+
+ <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:data-source-ref="dataSource" />
+
+</beans>
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests.java
new file mode 100644
index 00000000..74e8f251
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.testng;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestContext;
+import org.springframework.test.context.TestExecutionListener;
+import org.springframework.test.context.TestExecutionListeners;
+import org.springframework.test.context.support.AbstractTestExecutionListener;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import org.testng.ITestContext;
+import org.testng.ITestListener;
+import org.testng.ITestResult;
+import org.testng.TestNG;
+
+/**
+ * <p>
+ * JUnit 4 based integration test for verifying that '<i>before</i>' and '<i>after</i>'
+ * methods of {@link TestExecutionListener TestExecutionListeners} as well as
+ * {@link BeforeTransaction &#064;BeforeTransaction} and
+ * {@link AfterTransaction &#064;AfterTransaction} methods can fail a test in a
+ * TestNG environment, as requested in <a
+ * href="http://opensource.atlassian.com/projects/spring/browse/SPR-3960"
+ * target="_blank">SPR-3960</a>.
+ * </p>
+ * <p>
+ * Indirectly, this class also verifies that all {@link TestExecutionListener}
+ * lifecycle callbacks are called.
+ * </p>
+ * <p>
+ * As of Spring 3.0, this class also tests support for the new
+ * {@link TestExecutionListener#beforeTestClass(TestContext) beforeTestClass()}
+ * and {@link TestExecutionListener#afterTestClass(TestContext)
+ * afterTestClass()} lifecycle callback methods.
+ * </p>
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@RunWith(Parameterized.class)
+public class FailingBeforeAndAfterMethodsTests {
+
+ protected final Class<?> clazz;
+ protected final int expectedTestStartCount;
+ protected final int expectedTestSuccessCount;
+ protected final int expectedFailureCount;
+ protected final int expectedFailedConfigurationsCount;
+
+
+ public FailingBeforeAndAfterMethodsTests(final Class<?> clazz, final int expectedTestStartCount,
+ final int expectedTestSuccessCount, final int expectedFailureCount,
+ final int expectedFailedConfigurationsCount) {
+ this.clazz = clazz;
+ this.expectedTestStartCount = expectedTestStartCount;
+ this.expectedTestSuccessCount = expectedTestSuccessCount;
+ this.expectedFailureCount = expectedFailureCount;
+ this.expectedFailedConfigurationsCount = expectedFailedConfigurationsCount;
+ }
+
+ @Parameters
+ public static Collection<Object[]> testData() {
+ return Arrays.asList(new Object[][] {//
+ //
+ { AlwaysFailingBeforeTestClassTestCase.class, 1, 0, 0, 1 },//
+ { AlwaysFailingAfterTestClassTestCase.class, 1, 1, 0, 1 },//
+ { AlwaysFailingPrepareTestInstanceTestCase.class, 1, 0, 0, 1 },//
+ { AlwaysFailingBeforeTestMethodTestCase.class, 1, 0, 0, 1 },//
+ { AlwaysFailingAfterTestMethodTestCase.class, 1, 1, 0, 1 },//
+ { FailingBeforeTransactionTestCase.class, 1, 0, 0, 1 },//
+ { FailingAfterTransactionTestCase.class, 1, 1, 0, 1 } //
+ });
+ }
+
+ @Test
+ public void runTestAndAssertCounters() throws Exception {
+ final FailureTrackingTestListener listener = new FailureTrackingTestListener();
+ final TestNG testNG = new TestNG();
+ testNG.addListener(listener);
+ testNG.setTestClasses(new Class<?>[] { this.clazz });
+ testNG.setVerbose(0);
+ testNG.run();
+
+ assertEquals("Verifying number of test starts for test class [" + this.clazz + "].",
+ this.expectedTestStartCount, listener.testStartCount);
+ assertEquals("Verifying number of successful tests for test class [" + this.clazz + "].",
+ this.expectedTestSuccessCount, listener.testSuccessCount);
+ assertEquals("Verifying number of failures for test class [" + this.clazz + "].", this.expectedFailureCount,
+ listener.testFailureCount);
+ assertEquals("Verifying number of failed configurations for test class [" + this.clazz + "].",
+ this.expectedFailedConfigurationsCount, listener.failedConfigurationsCount);
+ }
+
+
+ static class FailureTrackingTestListener implements ITestListener {
+
+ int testStartCount = 0;
+ int testSuccessCount = 0;
+ int testFailureCount = 0;
+ int failedConfigurationsCount = 0;
+
+
+ @Override
+ public void onFinish(ITestContext testContext) {
+ this.failedConfigurationsCount += testContext.getFailedConfigurations().size();
+ }
+
+ @Override
+ public void onStart(ITestContext testContext) {
+ }
+
+ @Override
+ public void onTestFailedButWithinSuccessPercentage(ITestResult testResult) {
+ }
+
+ @Override
+ public void onTestFailure(ITestResult testResult) {
+ this.testFailureCount++;
+ }
+
+ @Override
+ public void onTestSkipped(ITestResult testResult) {
+ }
+
+ @Override
+ public void onTestStart(ITestResult testResult) {
+ this.testStartCount++;
+ }
+
+ @Override
+ public void onTestSuccess(ITestResult testResult) {
+ this.testSuccessCount++;
+ }
+ }
+
+ // -------------------------------------------------------------------
+
+ static class AlwaysFailingBeforeTestClassTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ public void beforeTestClass(TestContext testContext) {
+ org.testng.Assert.fail("always failing beforeTestClass()");
+ }
+ }
+
+ static class AlwaysFailingAfterTestClassTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ public void afterTestClass(TestContext testContext) {
+ org.testng.Assert.fail("always failing afterTestClass()");
+ }
+ }
+
+ static class AlwaysFailingPrepareTestInstanceTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ public void prepareTestInstance(TestContext testContext) throws Exception {
+ org.testng.Assert.fail("always failing prepareTestInstance()");
+ }
+ }
+
+ static class AlwaysFailingBeforeTestMethodTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ public void beforeTestMethod(TestContext testContext) {
+ org.testng.Assert.fail("always failing beforeTestMethod()");
+ }
+ }
+
+ static class AlwaysFailingAfterTestMethodTestExecutionListener extends AbstractTestExecutionListener {
+
+ @Override
+ public void afterTestMethod(TestContext testContext) {
+ org.testng.Assert.fail("always failing afterTestMethod()");
+ }
+ }
+
+ // -------------------------------------------------------------------
+
+ @TestExecutionListeners(value = {}, inheritListeners = false)
+ public static abstract class BaseTestCase extends AbstractTestNGSpringContextTests {
+
+ @org.testng.annotations.Test
+ public void testNothing() {
+ }
+ }
+
+ @TestExecutionListeners(AlwaysFailingBeforeTestClassTestExecutionListener.class)
+ public static class AlwaysFailingBeforeTestClassTestCase extends BaseTestCase {
+ }
+
+ @TestExecutionListeners(AlwaysFailingAfterTestClassTestExecutionListener.class)
+ public static class AlwaysFailingAfterTestClassTestCase extends BaseTestCase {
+ }
+
+ @TestExecutionListeners(AlwaysFailingPrepareTestInstanceTestExecutionListener.class)
+ public static class AlwaysFailingPrepareTestInstanceTestCase extends BaseTestCase {
+ }
+
+ @TestExecutionListeners(AlwaysFailingBeforeTestMethodTestExecutionListener.class)
+ public static class AlwaysFailingBeforeTestMethodTestCase extends BaseTestCase {
+ }
+
+ @TestExecutionListeners(AlwaysFailingAfterTestMethodTestExecutionListener.class)
+ public static class AlwaysFailingAfterTestMethodTestCase extends BaseTestCase {
+ }
+
+ @ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml")
+ public static class FailingBeforeTransactionTestCase extends AbstractTransactionalTestNGSpringContextTests {
+
+ @org.testng.annotations.Test
+ public void testNothing() {
+ }
+
+ @BeforeTransaction
+ public void beforeTransaction() {
+ org.testng.Assert.fail("always failing beforeTransaction()");
+ }
+ }
+
+ @ContextConfiguration("FailingBeforeAndAfterMethodsTests-context.xml")
+ public static class FailingAfterTransactionTestCase extends AbstractTransactionalTestNGSpringContextTests {
+
+ @org.testng.annotations.Test
+ public void testNothing() {
+ }
+
+ @AfterTransaction
+ public void afterTransaction() {
+ org.testng.Assert.fail("always failing afterTransaction()");
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/TimedTransactionalTestNGSpringContextTests-context.xml b/spring-test/src/test/java/org/springframework/test/context/testng/TimedTransactionalTestNGSpringContextTests-context.xml
new file mode 100644
index 00000000..bbff4c9e
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/TimedTransactionalTestNGSpringContextTests-context.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
+ http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
+
+ <jdbc:embedded-database id="dataSource" />
+
+ <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
+ p:data-source-ref="dataSource" />
+
+</beans> \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/TimedTransactionalTestNGSpringContextTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/TimedTransactionalTestNGSpringContextTests.java
new file mode 100644
index 00000000..bedd776b
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/TimedTransactionalTestNGSpringContextTests.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2012 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.testng;
+
+import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
+
+import org.springframework.test.context.ContextConfiguration;
+import org.testng.annotations.Test;
+
+/**
+ * Timed integration tests for
+ * {@link AbstractTransactionalTestNGSpringContextTests}; used to verify claim
+ * raised in <a href="http://jira.springframework.org/browse/SPR-6124"
+ * target="_blank">SPR-6124</a>.
+ *
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@ContextConfiguration
+public class TimedTransactionalTestNGSpringContextTests extends AbstractTransactionalTestNGSpringContextTests {
+
+ @Test
+ public void testWithoutTimeout() {
+ assertInTransaction(true);
+ }
+
+ // TODO Enable TestNG test with timeout once we have a solution.
+ @Test(timeOut = 10000, enabled = false)
+ public void testWithTimeout() {
+ assertInTransaction(true);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/data.sql b/spring-test/src/test/java/org/springframework/test/context/testng/data.sql
new file mode 100644
index 00000000..10d02a9c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/data.sql
@@ -0,0 +1 @@
+INSERT INTO person VALUES('bob'); \ No newline at end of file
diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/schema.sql b/spring-test/src/test/java/org/springframework/test/context/testng/schema.sql
new file mode 100644
index 00000000..d5bacef4
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/schema.sql
@@ -0,0 +1,4 @@
+CREATE TABLE person (
+ name VARCHAR(20) NOT NULL,
+ PRIMARY KEY(name)
+); \ No newline at end of file
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
new file mode 100644
index 00000000..55aa29e7
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/web/ServletTestExecutionListenerTestNGIntegrationTests.java
@@ -0,0 +1,78 @@
+/*
+ * 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.context.testng.web;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.ServletTestExecutionListener;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.testng.annotations.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * TestNG-based integration tests for {@link ServletTestExecutionListener}.
+ *
+ * @author Sam Brannen
+ * @since 3.2.9
+ * @see org.springframework.test.context.web.ServletTestExecutionListenerJUnitIntegrationTests
+ */
+@ContextConfiguration
+@WebAppConfiguration
+public class ServletTestExecutionListenerTestNGIntegrationTests extends AbstractTestNGSpringContextTests {
+
+ @Configuration
+ static class Config {
+ /* no beans required for this test */
+ }
+
+
+ @Autowired
+ private MockHttpServletRequest servletRequest;
+
+
+ /**
+ * Verifies bug fix for <a href="https://jira.spring.io/browse/SPR-11626">SPR-11626</a>.
+ *
+ * @see #ensureMocksAreReinjectedBetweenTests_2
+ */
+ @Test
+ public void ensureMocksAreReinjectedBetweenTests_1() {
+ assertInjectedServletRequestEqualsRequestInRequestContextHolder();
+ }
+
+ /**
+ * Verifies bug fix for <a href="https://jira.spring.io/browse/SPR-11626">SPR-11626</a>.
+ *
+ * @see #ensureMocksAreReinjectedBetweenTests_1
+ */
+ @Test
+ public void ensureMocksAreReinjectedBetweenTests_2() {
+ assertInjectedServletRequestEqualsRequestInRequestContextHolder();
+ }
+
+ private void assertInjectedServletRequestEqualsRequestInRequestContextHolder() {
+ assertEquals("Injected ServletRequest must be stored in the RequestContextHolder", servletRequest,
+ ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());
+ }
+
+}
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
new file mode 100644
index 00000000..05f39889
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/testng/web/TestNGSpringContextWebTests.java
@@ -0,0 +1,119 @@
+/*
+ * 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.context.testng.web;
+
+import java.io.File;
+
+import javax.servlet.ServletContext;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockHttpSession;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.web.context.ServletContextAware;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.testng.annotations.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * TestNG-based integration tests that verify support for loading a
+ * {@link WebApplicationContext} when extending {@link AbstractTestNGSpringContextTests}.
+ *
+ * @author Sam Brannen
+ * @since 3.2.7
+ */
+@ContextConfiguration
+@WebAppConfiguration
+public class TestNGSpringContextWebTests extends AbstractTestNGSpringContextTests implements ServletContextAware {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "enigma";
+ }
+ }
+
+
+ protected ServletContext servletContext;
+
+ @Autowired
+ protected WebApplicationContext wac;
+
+ @Autowired
+ protected MockServletContext mockServletContext;
+
+ @Autowired
+ protected MockHttpServletRequest request;
+
+ @Autowired
+ protected MockHttpServletResponse response;
+
+ @Autowired
+ protected MockHttpSession session;
+
+ @Autowired
+ protected ServletWebRequest webRequest;
+
+ @Autowired
+ protected String foo;
+
+
+ @Override
+ public void setServletContext(ServletContext servletContext) {
+ this.servletContext = servletContext;
+ }
+
+ @Test
+ public void basicWacFeatures() throws Exception {
+ assertNotNull("ServletContext should be set in the WAC.", wac.getServletContext());
+
+ assertNotNull("ServletContext should have been set via ServletContextAware.", servletContext);
+
+ assertNotNull("ServletContext should have been autowired from the WAC.", mockServletContext);
+ assertNotNull("MockHttpServletRequest should have been autowired from the WAC.", request);
+ assertNotNull("MockHttpServletResponse should have been autowired from the WAC.", response);
+ assertNotNull("MockHttpSession should have been autowired from the WAC.", session);
+ assertNotNull("ServletWebRequest should have been autowired from the WAC.", webRequest);
+
+ Object rootWac = mockServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
+ assertNotNull("Root WAC must be stored in the ServletContext as: "
+ + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, rootWac);
+ assertSame("test WAC and Root WAC in ServletContext must be the same object.", wac, rootWac);
+ assertSame("ServletContext instances must be the same object.", mockServletContext, wac.getServletContext());
+ assertSame("ServletContext in the WAC and in the mock request", mockServletContext, request.getServletContext());
+
+ assertEquals("Getting real path for ServletContext resource.",
+ new File("src/main/webapp/index.jsp").getCanonicalPath(), mockServletContext.getRealPath("index.jsp"));
+
+ }
+
+ @Test
+ public void fooEnigmaAutowired() {
+ assertEquals("enigma", foo);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/web/AbstractBasicWacTests.java b/spring-test/src/test/java/org/springframework/test/context/web/AbstractBasicWacTests.java
new file mode 100644
index 00000000..334983d8
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/web/AbstractBasicWacTests.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+import java.io.File;
+
+import javax.servlet.ServletContext;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockHttpSession;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.web.context.ServletContextAware;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.request.ServletWebRequest;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@WebAppConfiguration
+public abstract class AbstractBasicWacTests implements ServletContextAware {
+
+ protected ServletContext servletContext;
+
+ @Autowired
+ protected WebApplicationContext wac;
+
+ @Autowired
+ protected MockServletContext mockServletContext;
+
+ @Autowired
+ protected MockHttpServletRequest request;
+
+ @Autowired
+ protected MockHttpServletResponse response;
+
+ @Autowired
+ protected MockHttpSession session;
+
+ @Autowired
+ protected ServletWebRequest webRequest;
+
+ @Autowired
+ protected String foo;
+
+
+ @Override
+ public void setServletContext(ServletContext servletContext) {
+ this.servletContext = servletContext;
+ }
+
+ @Test
+ public void basicWacFeatures() throws Exception {
+ assertNotNull("ServletContext should be set in the WAC.", wac.getServletContext());
+
+ assertNotNull("ServletContext should have been set via ServletContextAware.", servletContext);
+
+ assertNotNull("ServletContext should have been autowired from the WAC.", mockServletContext);
+ assertNotNull("MockHttpServletRequest should have been autowired from the WAC.", request);
+ assertNotNull("MockHttpServletResponse should have been autowired from the WAC.", response);
+ assertNotNull("MockHttpSession should have been autowired from the WAC.", session);
+ assertNotNull("ServletWebRequest should have been autowired from the WAC.", webRequest);
+
+ Object rootWac = mockServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
+ assertNotNull("Root WAC must be stored in the ServletContext as: "
+ + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, rootWac);
+ assertSame("test WAC and Root WAC in ServletContext must be the same object.", wac, rootWac);
+ assertSame("ServletContext instances must be the same object.", mockServletContext, wac.getServletContext());
+ assertSame("ServletContext in the WAC and in the mock request", mockServletContext, request.getServletContext());
+
+ assertEquals("Getting real path for ServletContext resource.",
+ new File("src/main/webapp/index.jsp").getCanonicalPath(), mockServletContext.getRealPath("index.jsp"));
+
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/web/BasicAnnotationConfigWacTests.java b/spring-test/src/test/java/org/springframework/test/context/web/BasicAnnotationConfigWacTests.java
new file mode 100644
index 00000000..de4ef83c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/web/BasicAnnotationConfigWacTests.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@ContextConfiguration
+public class BasicAnnotationConfigWacTests extends AbstractBasicWacTests {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "enigma";
+ }
+ }
+
+
+ @Test
+ public void fooEnigmaAutowired() {
+ assertEquals("enigma", foo);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/web/BasicXmlWacTests.java b/spring-test/src/test/java/org/springframework/test/context/web/BasicXmlWacTests.java
new file mode 100644
index 00000000..7489209a
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/web/BasicXmlWacTests.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2012 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 static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.springframework.test.context.ContextConfiguration;
+
+/**
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@ContextConfiguration
+public class BasicXmlWacTests extends AbstractBasicWacTests {
+
+ @Test
+ public void fooBarAutowired() {
+ assertEquals("bar", foo);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/web/JUnit4SpringContextWebTests.java b/spring-test/src/test/java/org/springframework/test/context/web/JUnit4SpringContextWebTests.java
new file mode 100644
index 00000000..c1133bdc
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/web/JUnit4SpringContextWebTests.java
@@ -0,0 +1,118 @@
+/*
+ * 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.context.web;
+
+import java.io.File;
+
+import javax.servlet.ServletContext;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockHttpSession;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
+import org.springframework.web.context.ServletContextAware;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.request.ServletWebRequest;
+
+import static org.junit.Assert.*;
+
+/**
+ * JUnit-based integration tests that verify support for loading a
+ * {@link WebApplicationContext} when extending {@link AbstractJUnit4SpringContextTests}.
+ *
+ * @author Sam Brannen
+ * @since 3.2.7
+ */
+@ContextConfiguration
+@WebAppConfiguration
+public class JUnit4SpringContextWebTests extends AbstractJUnit4SpringContextTests implements ServletContextAware {
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public String foo() {
+ return "enigma";
+ }
+ }
+
+
+ protected ServletContext servletContext;
+
+ @Autowired
+ protected WebApplicationContext wac;
+
+ @Autowired
+ protected MockServletContext mockServletContext;
+
+ @Autowired
+ protected MockHttpServletRequest request;
+
+ @Autowired
+ protected MockHttpServletResponse response;
+
+ @Autowired
+ protected MockHttpSession session;
+
+ @Autowired
+ protected ServletWebRequest webRequest;
+
+ @Autowired
+ protected String foo;
+
+
+ @Override
+ public void setServletContext(ServletContext servletContext) {
+ this.servletContext = servletContext;
+ }
+
+ @Test
+ public void basicWacFeatures() throws Exception {
+ assertNotNull("ServletContext should be set in the WAC.", wac.getServletContext());
+
+ assertNotNull("ServletContext should have been set via ServletContextAware.", servletContext);
+
+ assertNotNull("ServletContext should have been autowired from the WAC.", mockServletContext);
+ assertNotNull("MockHttpServletRequest should have been autowired from the WAC.", request);
+ assertNotNull("MockHttpServletResponse should have been autowired from the WAC.", response);
+ assertNotNull("MockHttpSession should have been autowired from the WAC.", session);
+ assertNotNull("ServletWebRequest should have been autowired from the WAC.", webRequest);
+
+ Object rootWac = mockServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
+ assertNotNull("Root WAC must be stored in the ServletContext as: "
+ + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, rootWac);
+ assertSame("test WAC and Root WAC in ServletContext must be the same object.", wac, rootWac);
+ assertSame("ServletContext instances must be the same object.", mockServletContext, wac.getServletContext());
+ assertSame("ServletContext in the WAC and in the mock request", mockServletContext, request.getServletContext());
+
+ assertEquals("Getting real path for ServletContext resource.",
+ new File("src/main/webapp/index.jsp").getCanonicalPath(), mockServletContext.getRealPath("index.jsp"));
+
+ }
+
+ @Test
+ public void fooEnigmaAutowired() {
+ assertEquals("enigma", foo);
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/web/RequestAndSessionScopedBeansWacTests.java b/spring-test/src/test/java/org/springframework/test/context/web/RequestAndSessionScopedBeansWacTests.java
new file mode 100644
index 00000000..829d70ff
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/web/RequestAndSessionScopedBeansWacTests.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.web;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.tests.sample.beans.TestBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpSession;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.web.context.WebApplicationContext;
+
+/**
+ * Integration tests that verify support for request and session scoped beans
+ * in conjunction with the TestContext Framework.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@WebAppConfiguration
+public class RequestAndSessionScopedBeansWacTests {
+
+ @Autowired
+ private WebApplicationContext wac;
+
+ @Autowired
+ private MockHttpServletRequest request;
+
+ @Autowired
+ private MockHttpSession session;
+
+
+ @Test
+ public void requestScope() throws Exception {
+ final String beanName = "requestScopedTestBean";
+ final String contextPath = "/path";
+
+ assertNull(request.getAttribute(beanName));
+
+ request.setContextPath(contextPath);
+ TestBean testBean = wac.getBean(beanName, TestBean.class);
+
+ assertEquals(contextPath, testBean.getName());
+ assertSame(testBean, request.getAttribute(beanName));
+ assertSame(testBean, wac.getBean(beanName, TestBean.class));
+ }
+
+ @Test
+ public void sessionScope() throws Exception {
+ final String beanName = "sessionScopedTestBean";
+
+ assertNull(session.getAttribute(beanName));
+
+ TestBean testBean = wac.getBean(beanName, TestBean.class);
+
+ assertSame(testBean, session.getAttribute(beanName));
+ assertSame(testBean, wac.getBean(beanName, TestBean.class));
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerJUnitIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerJUnitIntegrationTests.java
new file mode 100644
index 00000000..f8dfa2c9
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerJUnitIntegrationTests.java
@@ -0,0 +1,78 @@
+/*
+ * 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.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.mock.web.MockHttpServletRequest;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import static org.junit.Assert.*;
+
+/**
+ * JUnit-based integration tests for {@link ServletTestExecutionListener}.
+ *
+ * @author Sam Brannen
+ * @since 3.2.9
+ * @see org.springframework.test.context.testng.web.ServletTestExecutionListenerTestNGIntegrationTests
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration
+@WebAppConfiguration
+public class ServletTestExecutionListenerJUnitIntegrationTests {
+
+ @Configuration
+ static class Config {
+ /* no beans required for this test */
+ }
+
+
+ @Autowired
+ private MockHttpServletRequest servletRequest;
+
+
+ /**
+ * Verifies bug fix for <a href="https://jira.spring.io/browse/SPR-11626">SPR-11626</a>.
+ *
+ * @see #ensureMocksAreReinjectedBetweenTests_2
+ */
+ @Test
+ public void ensureMocksAreReinjectedBetweenTests_1() {
+ assertInjectedServletRequestEqualsRequestInRequestContextHolder();
+ }
+
+ /**
+ * Verifies bug fix for <a href="https://jira.spring.io/browse/SPR-11626">SPR-11626</a>.
+ *
+ * @see #ensureMocksAreReinjectedBetweenTests_1
+ */
+ @Test
+ public void ensureMocksAreReinjectedBetweenTests_2() {
+ assertInjectedServletRequestEqualsRequestInRequestContextHolder();
+ }
+
+ private void assertInjectedServletRequestEqualsRequestInRequestContextHolder() {
+ assertEquals("Injected ServletRequest must be stored in the RequestContextHolder", servletRequest,
+ ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());
+ }
+
+}
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
new file mode 100644
index 00000000..54b23941
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/web/ServletTestExecutionListenerTests.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.context.web;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.context.ApplicationContext;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.test.context.TestContext;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletWebRequest;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import static org.springframework.test.context.web.ServletTestExecutionListener.*;
+
+/**
+ * Unit tests for {@link ServletTestExecutionListener}.
+ *
+ * @author Sam Brannen
+ * @since 3.2.6
+ */
+public class ServletTestExecutionListenerTests {
+
+ private static final String SET_UP_OUTSIDE_OF_STEL = "SET_UP_OUTSIDE_OF_STEL";
+
+ private final WebApplicationContext wac = mock(WebApplicationContext.class);
+ private final MockServletContext mockServletContext = new MockServletContext();
+ private final TestContext testContext = mock(TestContext.class);
+ 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() {
+ when(wac.getServletContext()).thenReturn(mockServletContext);
+ when(testContext.getApplicationContext()).thenReturn(wac);
+
+ MockHttpServletRequest request = new MockHttpServletRequest(mockServletContext);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ ServletWebRequest servletWebRequest = new ServletWebRequest(request, response);
+
+ request.setAttribute(SET_UP_OUTSIDE_OF_STEL, "true");
+
+ RequestContextHolder.setRequestAttributes(servletWebRequest);
+ assertAttributeExists();
+ }
+
+ @Test
+ public void standardApplicationContext() throws Exception {
+ Mockito.<Class<?>> when(testContext.getTestClass()).thenReturn(getClass());
+ when(testContext.getApplicationContext()).thenReturn(mock(ApplicationContext.class));
+
+ listener.beforeTestClass(testContext);
+ assertAttributeExists();
+
+ listener.prepareTestInstance(testContext);
+ assertAttributeExists();
+
+ listener.beforeTestMethod(testContext);
+ assertAttributeExists();
+
+ listener.afterTestMethod(testContext);
+ assertAttributeExists();
+ }
+
+ @Test
+ public void legacyWebTestCaseWithoutExistingRequestAttributes() throws Exception {
+ Mockito.<Class<?>> when(testContext.getTestClass()).thenReturn(LegacyWebTestCase.class);
+
+ RequestContextHolder.resetRequestAttributes();
+ assertAttributesNotAvailable();
+
+ listener.beforeTestClass(testContext);
+
+ listener.prepareTestInstance(testContext);
+ assertAttributesNotAvailable();
+ verify(testContext, times(0)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
+ when(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).thenReturn(null);
+
+ listener.beforeTestMethod(testContext);
+ assertAttributesNotAvailable();
+ 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();
+ }
+
+ @Test
+ public void legacyWebTestCaseWithPresetRequestAttributes() throws Exception {
+ Mockito.<Class<?>> when(testContext.getTestClass()).thenReturn(LegacyWebTestCase.class);
+
+ listener.beforeTestClass(testContext);
+ assertAttributeExists();
+
+ listener.prepareTestInstance(testContext);
+ assertAttributeExists();
+ verify(testContext, times(0)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
+ when(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).thenReturn(null);
+
+ listener.beforeTestMethod(testContext);
+ assertAttributeExists();
+ verify(testContext, times(0)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
+ when(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).thenReturn(null);
+
+ listener.afterTestMethod(testContext);
+ verify(testContext, times(1)).removeAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE);
+ assertAttributeExists();
+ }
+
+ @Test
+ public void atWebAppConfigTestCaseWithoutExistingRequestAttributes() throws Exception {
+ Mockito.<Class<?>> when(testContext.getTestClass()).thenReturn(AtWebAppConfigWebTestCase.class);
+
+ RequestContextHolder.resetRequestAttributes();
+ listener.beforeTestClass(testContext);
+ assertAttributesNotAvailable();
+
+ assertWebAppConfigTestCase();
+ }
+
+ @Test
+ public void atWebAppConfigTestCaseWithPresetRequestAttributes() throws Exception {
+ Mockito.<Class<?>> when(testContext.getTestClass()).thenReturn(AtWebAppConfigWebTestCase.class);
+
+ listener.beforeTestClass(testContext);
+ assertAttributesAvailable();
+
+ assertWebAppConfigTestCase();
+ }
+
+ private void assertWebAppConfigTestCase() throws Exception {
+ listener.prepareTestInstance(testContext);
+ assertAttributeDoesNotExist();
+ verify(testContext, times(1)).setAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
+ verify(testContext, times(1)).setAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE, Boolean.TRUE);
+ when(testContext.getAttribute(POPULATED_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).thenReturn(Boolean.TRUE);
+ when(testContext.getAttribute(RESET_REQUEST_CONTEXT_HOLDER_ATTRIBUTE)).thenReturn(Boolean.TRUE);
+
+ listener.beforeTestMethod(testContext);
+ assertAttributeDoesNotExist();
+ 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();
+ }
+
+
+ static class LegacyWebTestCase {
+ }
+
+ @WebAppConfiguration
+ static class AtWebAppConfigWebTestCase {
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/web/WebContextLoaderTestSuite.java b/spring-test/src/test/java/org/springframework/test/context/web/WebContextLoaderTestSuite.java
new file mode 100644
index 00000000..8ccd8b4f
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/web/WebContextLoaderTestSuite.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2012 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.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+import org.springframework.test.context.ContextLoader;
+import org.springframework.web.context.WebApplicationContext;
+
+/**
+ * Convenience test suite for integration tests that verify support for
+ * {@link WebApplicationContext} {@linkplain ContextLoader context loaders}
+ * in the TestContext framework.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+@RunWith(Suite.class)
+// Note: the following 'multi-line' layout is for enhanced code readability.
+@SuiteClasses({//
+BasicXmlWacTests.class,//
+ BasicAnnotationConfigWacTests.class,//
+ RequestAndSessionScopedBeansWacTests.class //
+})
+public class WebContextLoaderTestSuite {
+}
diff --git a/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java
new file mode 100644
index 00000000..6ebdf918
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/jdbc/JdbcTestUtilsTests.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.jdbc;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.BDDMockito.given;
+
+import java.io.LineNumberReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+/**
+ * Unit tests for {@link JdbcTestUtils}.
+ *
+ * @author Thomas Risberg
+ * @author Sam Brannen
+ * @author Phillip Webb
+ * @since 2.5.4
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class JdbcTestUtilsTests {
+
+ @Mock
+ private JdbcTemplate jdbcTemplate;
+
+ @Test
+ public void containsDelimiters() {
+ assertTrue("test with ';' is wrong", !JdbcTestUtils.containsSqlScriptDelimiters("select 1\n select ';'", ';'));
+ assertTrue("test with delimiter ; is wrong",
+ JdbcTestUtils.containsSqlScriptDelimiters("select 1; select 2", ';'));
+ assertTrue("test with '\\n' is wrong",
+ !JdbcTestUtils.containsSqlScriptDelimiters("select 1; select '\\n\n';", '\n'));
+ assertTrue("test with delimiter \\n is wrong",
+ JdbcTestUtils.containsSqlScriptDelimiters("select 1\n select 2", '\n'));
+ }
+
+ @Test
+ public void splitSqlScriptDelimitedWithSemicolon() {
+ String rawStatement1 = "insert into customer (id, name)\nvalues (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
+ String cleanedStatement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
+ String rawStatement2 = "insert into orders(id, order_date, customer_id)\nvalues (1, '2008-01-02', 2)";
+ String cleanedStatement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
+ String rawStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
+ String cleanedStatement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
+ char delim = ';';
+ String script = rawStatement1 + delim + rawStatement2 + delim + rawStatement3 + delim;
+ List<String> statements = new ArrayList<String>();
+ JdbcTestUtils.splitSqlScript(script, delim, statements);
+ assertEquals("wrong number of statements", 3, statements.size());
+ assertEquals("statement 1 not split correctly", cleanedStatement1, statements.get(0));
+ assertEquals("statement 2 not split correctly", cleanedStatement2, statements.get(1));
+ assertEquals("statement 3 not split correctly", cleanedStatement3, statements.get(2));
+ }
+
+ @Test
+ public void splitSqlScriptDelimitedWithNewLine() {
+ String statement1 = "insert into customer (id, name) values (1, 'Rod ; Johnson'), (2, 'Adrian \n Collier')";
+ String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
+ String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
+ char delim = '\n';
+ String script = statement1 + delim + statement2 + delim + statement3 + delim;
+ List<String> statements = new ArrayList<String>();
+ JdbcTestUtils.splitSqlScript(script, delim, statements);
+ assertEquals("wrong number of statements", 3, statements.size());
+ assertEquals("statement 1 not split correctly", statement1, statements.get(0));
+ assertEquals("statement 2 not split correctly", statement2, statements.get(1));
+ assertEquals("statement 3 not split correctly", statement3, statements.get(2));
+ }
+
+ @Test
+ public void readAndSplitScriptContainingComments() throws Exception {
+
+ EncodedResource resource = new EncodedResource(new ClassPathResource("test-data-with-comments.sql", getClass()));
+ LineNumberReader lineNumberReader = new LineNumberReader(resource.getReader());
+
+ String script = JdbcTestUtils.readScript(lineNumberReader);
+
+ char delim = ';';
+ List<String> statements = new ArrayList<String>();
+ JdbcTestUtils.splitSqlScript(script, delim, statements);
+
+ String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')";
+ String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
+ String statement3 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
+ // Statement 4 addresses the error described in SPR-9982.
+ String statement4 = "INSERT INTO persons( person_id , name) VALUES( 1 , 'Name' )";
+
+ assertEquals("wrong number of statements", 4, statements.size());
+ assertEquals("statement 1 not split correctly", statement1, statements.get(0));
+ assertEquals("statement 2 not split correctly", statement2, statements.get(1));
+ assertEquals("statement 3 not split correctly", statement3, statements.get(2));
+ assertEquals("statement 4 not split correctly", statement4, statements.get(3));
+ }
+
+ @Test
+ public void testDeleteNoWhere() throws Exception {
+ given(jdbcTemplate.update("DELETE FROM person")).willReturn(10);
+ int deleted = JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "person", null);
+ assertThat(deleted, equalTo(10));
+ }
+
+ @Test
+ public void testDeleteWhere() throws Exception {
+ given(jdbcTemplate.update("DELETE FROM person WHERE name = 'Bob' and age > 25")).willReturn(10);
+ int deleted = JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "person", "name = 'Bob' and age > 25");
+ assertThat(deleted, equalTo(10));
+ }
+
+ @Test
+ public void deleteWhereAndArguments() throws Exception {
+ given(jdbcTemplate.update("DELETE FROM person WHERE name = ? and age > ?", "Bob", 25)).willReturn(10);
+ int deleted = JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "person", "name = ? and age > ?", "Bob", 25);
+ assertThat(deleted, equalTo(10));
+ }
+
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/transaction/TransactionTestUtils.java b/spring-test/src/test/java/org/springframework/test/transaction/TransactionTestUtils.java
new file mode 100644
index 00000000..d5598c26
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/transaction/TransactionTestUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2012 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.transaction;
+
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+/**
+ * Collection of JDK 1.4+ utilities for tests involving transactions. Intended
+ * for internal use within the Spring testing suite.
+ *
+ * <p>All {@code assert*()} methods throw {@link AssertionError}s.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public abstract class TransactionTestUtils {
+
+ /**
+ * Convenience method for determining if a transaction is active for the
+ * current {@link Thread}.
+ * @return {@code true} if a transaction is currently active
+ */
+ public static boolean inTransaction() {
+ return TransactionSynchronizationManager.isActualTransactionActive();
+ }
+
+ /**
+ * Asserts whether or not a transaction is active for the current
+ * {@link Thread}.
+ * @param transactionExpected whether or not a transaction is expected
+ * @throws AssertionError if the supplied assertion fails
+ * @see #inTransaction()
+ */
+ public static void assertInTransaction(boolean transactionExpected) {
+ if (transactionExpected) {
+ assertCondition(inTransaction(), "The current thread should be associated with a transaction.");
+ }
+ else {
+ assertCondition(!inTransaction(), "The current thread should not be associated with a transaction");
+ }
+ }
+
+ /**
+ * Fails by throwing an {@code AssertionError} with the supplied
+ * {@code message}.
+ * @param message the exception message to use
+ * @see #assertCondition(boolean, String)
+ */
+ private static void fail(String message) throws AssertionError {
+ throw new AssertionError(message);
+ }
+
+ /**
+ * Assert the provided boolean {@code condition}, throwing
+ * {@code AssertionError} with the supplied {@code message} if
+ * the test result is {@code false}.
+ * @param condition a boolean expression
+ * @param message the exception message to use if the assertion fails
+ * @throws AssertionError if condition is {@code false}
+ * @see #fail(String)
+ */
+ private static void assertCondition(boolean condition, String message) throws AssertionError {
+ if (!condition) {
+ fail(message);
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..7e10a054
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2002-2012 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;
+
+import static org.junit.Assert.*;
+import static org.springframework.test.util.ReflectionTestUtils.*;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import org.springframework.test.AssertThrows;
+import org.springframework.test.util.subpackage.Component;
+import org.springframework.test.util.subpackage.LegacyEntity;
+import org.springframework.test.util.subpackage.Person;
+
+/**
+ * Unit tests for {@link ReflectionTestUtils}.
+ *
+ * @author Sam Brannen
+ * @author Juergen Hoeller
+ */
+@SuppressWarnings("deprecation")
+public class ReflectionTestUtilsTests {
+
+ private static final Float PI = new Float((float) 22 / 7);
+
+ private final Person person = new Person();
+ private final Component component = new Component();
+
+
+ @Test
+ public void setAndGetFields() throws Exception {
+
+ // ---------------------------------------------------------------------
+ // Standard
+
+ setField(person, "id", new Long(99), long.class);
+ setField(person, "name", "Tom");
+ setField(person, "age", new Integer(42));
+ setField(person, "eyeColor", "blue", String.class);
+ setField(person, "likesPets", Boolean.TRUE);
+ setField(person, "favoriteNumber", PI, Number.class);
+
+ 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"));
+
+ // ---------------------------------------------------------------------
+ // Null - non-primitives
+
+ setField(person, "name", null, String.class);
+ setField(person, "eyeColor", null, String.class);
+ setField(person, "favoriteNumber", null, Number.class);
+
+ assertNull("name (protected field)", person.getName());
+ assertNull("eye color (package private field)", person.getEyeColor());
+ assertNull("'favorite number' (package field)", person.getFavoriteNumber());
+
+ // ---------------------------------------------------------------------
+ // Null - primitives
+
+ new AssertThrows(IllegalArgumentException.class,
+ "Calling setField() with NULL for a primitive type should throw an IllegalArgumentException.") {
+
+ @Override
+ public void test() throws Exception {
+ setField(person, "id", null, long.class);
+ }
+ }.runTest();
+
+ new AssertThrows(IllegalArgumentException.class,
+ "Calling setField() with NULL for a primitive type should throw an IllegalArgumentException.") {
+
+ @Override
+ public void test() throws Exception {
+ setField(person, "age", null, int.class);
+ }
+ }.runTest();
+
+ new AssertThrows(IllegalArgumentException.class,
+ "Calling setField() with NULL for a primitive type should throw an IllegalArgumentException.") {
+
+ @Override
+ public void test() throws Exception {
+ setField(person, "likesPets", null, boolean.class);
+ }
+ }.runTest();
+ }
+
+ /**
+ * Verifies behavior requested in <a href="https://jira.springsource.org/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 invokeSetterAndMethods() throws Exception {
+
+ // ---------------------------------------------------------------------
+ // Standard - properties
+
+ invokeSetterMethod(person, "id", new Long(99), long.class);
+ invokeSetterMethod(person, "name", "Tom");
+ invokeSetterMethod(person, "age", new Integer(42));
+ invokeSetterMethod(person, "eyeColor", "blue", String.class);
+ invokeSetterMethod(person, "likesPets", Boolean.TRUE);
+ invokeSetterMethod(person, "favoriteNumber", PI, Number.class);
+
+ assertEquals("ID (protected method in a superclass)", 99, person.getId());
+ assertEquals("name (private method)", "Tom", person.getName());
+ assertEquals("age (protected method)", 42, person.getAge());
+ assertEquals("eye color (package private method)", "blue", person.getEyeColor());
+ assertEquals("'likes pets' flag (protected method for a boolean)", true, person.likesPets());
+ assertEquals("'favorite number' (protected method for a Number)", PI, person.getFavoriteNumber());
+
+ assertEquals(new Long(99), invokeGetterMethod(person, "id"));
+ assertEquals("Tom", invokeGetterMethod(person, "name"));
+ assertEquals(new Integer(42), invokeGetterMethod(person, "age"));
+ assertEquals("blue", invokeGetterMethod(person, "eyeColor"));
+ assertEquals(Boolean.TRUE, invokeGetterMethod(person, "likesPets"));
+ assertEquals(PI, invokeGetterMethod(person, "favoriteNumber"));
+
+ // ---------------------------------------------------------------------
+ // Standard - setter methods
+
+ invokeSetterMethod(person, "setId", new Long(1), long.class);
+ invokeSetterMethod(person, "setName", "Jerry", String.class);
+ invokeSetterMethod(person, "setAge", new Integer(33), int.class);
+ invokeSetterMethod(person, "setEyeColor", "green", String.class);
+ invokeSetterMethod(person, "setLikesPets", Boolean.FALSE, boolean.class);
+ invokeSetterMethod(person, "setFavoriteNumber", new Integer(42), Number.class);
+
+ assertEquals("ID (protected method in a superclass)", 1, person.getId());
+ assertEquals("name (private method)", "Jerry", person.getName());
+ assertEquals("age (protected method)", 33, person.getAge());
+ assertEquals("eye color (package private method)", "green", person.getEyeColor());
+ assertEquals("'likes pets' flag (protected method for a boolean)", false, person.likesPets());
+ assertEquals("'favorite number' (protected method for a Number)", new Integer(42), person.getFavoriteNumber());
+
+ assertEquals(new Long(1), invokeGetterMethod(person, "getId"));
+ assertEquals("Jerry", invokeGetterMethod(person, "getName"));
+ assertEquals(new Integer(33), invokeGetterMethod(person, "getAge"));
+ assertEquals("green", invokeGetterMethod(person, "getEyeColor"));
+ assertEquals(Boolean.FALSE, invokeGetterMethod(person, "likesPets"));
+ assertEquals(new Integer(42), invokeGetterMethod(person, "getFavoriteNumber"));
+
+ // ---------------------------------------------------------------------
+ // Null - non-primitives
+
+ invokeSetterMethod(person, "name", null, String.class);
+ invokeSetterMethod(person, "eyeColor", null, String.class);
+ invokeSetterMethod(person, "favoriteNumber", null, Number.class);
+
+ assertNull("name (private method)", person.getName());
+ assertNull("eye color (package private method)", person.getEyeColor());
+ assertNull("'favorite number' (protected method for a Number)", person.getFavoriteNumber());
+
+ // ---------------------------------------------------------------------
+ // Null - primitives
+
+ new AssertThrows(IllegalArgumentException.class,
+ "Calling invokeSetterMethod() with NULL for a primitive type should throw an IllegalArgumentException.") {
+
+ @Override
+ public void test() throws Exception {
+ invokeSetterMethod(person, "id", null, long.class);
+ }
+ }.runTest();
+
+ new AssertThrows(IllegalArgumentException.class,
+ "Calling invokeSetterMethod() with NULL for a primitive type should throw an IllegalArgumentException.") {
+
+ @Override
+ public void test() throws Exception {
+ invokeSetterMethod(person, "age", null, int.class);
+ }
+ }.runTest();
+
+ new AssertThrows(IllegalArgumentException.class,
+ "Calling invokeSetterMethod() with NULL for a primitive type should throw an IllegalArgumentException.") {
+
+ @Override
+ public void test() throws Exception {
+ invokeSetterMethod(person, "likesPets", null, boolean.class);
+ }
+ }.runTest();
+ }
+
+ @Test
+ public void invokeMethodWithAutoboxingAndUnboxing() {
+ // IntelliJ IDEA 11 won't accept int assignment here
+ Integer difference = invokeMethod(component, "subtract", 5, 2);
+ assertEquals("subtract(5, 2)", 3, difference.intValue());
+ }
+
+ @Ignore("[SPR-8644] findMethod() does not currently support var-args")
+ @Test
+ public void invokeMethodWithPrimitiveVarArgs() {
+ // IntelliJ IDEA 11 won't accept int assignment here
+ Integer sum = invokeMethod(component, "add", 1, 2, 3, 4);
+ assertEquals("add(1,2,3,4)", 10, sum.intValue());
+ }
+
+ @Test
+ public void invokeMethodWithPrimitiveVarArgsAsSingleArgument() {
+ // IntelliJ IDEA 11 won't accept int assignment here
+ Integer sum = invokeMethod(component, "add", new int[] { 1, 2, 3, 4 });
+ assertEquals("add(1,2,3,4)", 10, sum.intValue());
+ }
+
+ @Test
+ public void invokeMethodsSimulatingLifecycleEvents() {
+ assertNull("number", component.getNumber());
+ assertNull("text", component.getText());
+
+ // Simulate autowiring a configuration method
+ invokeMethod(component, "configure", new Integer(42), "enigma");
+ assertEquals("number should have been configured", new Integer(42), component.getNumber());
+ assertEquals("text should have been configured", "enigma", component.getText());
+
+ // Simulate @PostConstruct life-cycle event
+ invokeMethod(component, "init");
+ // assertions in init() should succeed
+
+ // Simulate @PreDestroy life-cycle event
+ invokeMethod(component, "destroy");
+ assertNull("number", component.getNumber());
+ assertNull("text", component.getText());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void invokeMethodWithIncompatibleArgumentTypes() {
+ invokeMethod(component, "subtract", "foo", 2.0);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void invokeInitMethodBeforeAutowiring() {
+ invokeMethod(component, "init");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void invokeMethodWithTooFewArguments() {
+ invokeMethod(component, "configure", new Integer(42));
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void invokeMethodWithTooManyArguments() {
+ invokeMethod(component, "configure", new Integer(42), "enigma", "baz", "quux");
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/Component.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/Component.java
new file mode 100644
index 00000000..edb2e719
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/Component.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2002-2011 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 javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Simple POJO representing a <em>component</em>; intended for use in
+ * unit tests.
+ *
+ * @author Sam Brannen
+ * @since 3.1
+ */
+public class Component {
+
+ private Integer number;
+ private String text;
+
+
+ public Integer getNumber() {
+ return this.number;
+ }
+
+ public String getText() {
+ return this.text;
+ }
+
+ @Autowired
+ protected void configure(Integer number, String text) {
+ this.number = number;
+ this.text = text;
+ }
+
+ @PostConstruct
+ protected void init() {
+ Assert.state(number != null, "number must not be null");
+ Assert.state(StringUtils.hasText(text), "text must not be empty");
+ }
+
+ @PreDestroy
+ protected void destroy() {
+ this.number = null;
+ this.text = null;
+ }
+
+ int subtract(int a, int b) {
+ return a - b;
+ }
+
+ int add(int... args) {
+ int sum = 0;
+ for (int i = 0; i < args.length; i++) {
+ sum += args[i];
+ }
+ return sum;
+ }
+
+ int multiply(Integer... args) {
+ int product = 1;
+ for (int i = 0; i < args.length; i++) {
+ product *= args[i];
+ }
+ return product;
+ }
+
+}
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
new file mode 100644
index 00000000..19507c93
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/LegacyEntity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * A <em>legacy entity</em> whose {@link #toString()} method has side effects;
+ * intended for use in unit tests.
+ *
+ * @author Sam Brannen
+ * @since 3.2
+ */
+public class LegacyEntity {
+
+ private Object collaborator = new Object() {
+
+ public String toString() {
+ throw new RuntimeException(
+ "Invoking toString() on the default collaborator causes an undesirable side effect");
+ };
+ };
+
+
+ public String toString() {
+ return new ToStringCreator(this)//
+ .append("collaborator", this.collaborator)//
+ .toString();
+ }
+
+}
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
new file mode 100644
index 00000000..3ff3d91d
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/PersistentEntity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2007-2011 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;
+
+/**
+ * Abstract base class for <em>persistent entities</em>; intended for use in
+ * unit tests.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+public abstract class PersistentEntity {
+
+ private long id;
+
+
+ public final long getId() {
+ return this.id;
+ }
+
+ protected final 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
new file mode 100644
index 00000000..66304779
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/Person.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2002-2012 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 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;
+ }
+
+ public String toString() {
+ 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();
+ }
+}
diff --git a/spring-test/src/test/java/org/springframework/web/multipart/MockMultipartHttpServletRequestTests.java b/spring-test/src/test/java/org/springframework/web/multipart/MockMultipartHttpServletRequestTests.java
new file mode 100644
index 00000000..029f584c
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/web/multipart/MockMultipartHttpServletRequestTests.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2002-2006 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.web.multipart;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Test;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.mock.web.MockMultipartHttpServletRequest;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * @author Juergen Hoeller
+ */
+public class MockMultipartHttpServletRequestTests {
+
+ @Test
+ public void mockMultipartHttpServletRequestWithByteArray() throws IOException {
+ MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
+ assertFalse(request.getFileNames().hasNext());
+ assertNull(request.getFile("file1"));
+ assertNull(request.getFile("file2"));
+ assertTrue(request.getFileMap().isEmpty());
+
+ request.addFile(new MockMultipartFile("file1", "myContent1".getBytes()));
+ request.addFile(new MockMultipartFile("file2", "myOrigFilename", "text/plain", "myContent2".getBytes()));
+ doTestMultipartHttpServletRequest(request);
+ }
+
+ @Test
+ public void mockMultipartHttpServletRequestWithInputStream() throws IOException {
+ MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
+ request.addFile(new MockMultipartFile("file1", new ByteArrayInputStream("myContent1".getBytes())));
+ request.addFile(new MockMultipartFile("file2", "myOrigFilename", "text/plain", new ByteArrayInputStream(
+ "myContent2".getBytes())));
+ doTestMultipartHttpServletRequest(request);
+ }
+
+ private void doTestMultipartHttpServletRequest(MultipartHttpServletRequest request) throws IOException {
+ Set<String> fileNames = new HashSet<String>();
+ Iterator<String> fileIter = request.getFileNames();
+ while (fileIter.hasNext()) {
+ fileNames.add(fileIter.next());
+ }
+ assertEquals(2, fileNames.size());
+ assertTrue(fileNames.contains("file1"));
+ assertTrue(fileNames.contains("file2"));
+ MultipartFile file1 = request.getFile("file1");
+ MultipartFile file2 = request.getFile("file2");
+ Map<String, MultipartFile> fileMap = request.getFileMap();
+ List<String> fileMapKeys = new LinkedList<String>(fileMap.keySet());
+ assertEquals(2, fileMapKeys.size());
+ assertEquals(file1, fileMap.get("file1"));
+ assertEquals(file2, fileMap.get("file2"));
+
+ assertEquals("file1", file1.getName());
+ assertEquals("", file1.getOriginalFilename());
+ assertNull(file1.getContentType());
+ assertTrue(ObjectUtils.nullSafeEquals("myContent1".getBytes(), file1.getBytes()));
+ assertTrue(ObjectUtils.nullSafeEquals("myContent1".getBytes(),
+ FileCopyUtils.copyToByteArray(file1.getInputStream())));
+ assertEquals("file2", file2.getName());
+ assertEquals("myOrigFilename", file2.getOriginalFilename());
+ assertEquals("text/plain", file2.getContentType());
+ assertTrue(ObjectUtils.nullSafeEquals("myContent2".getBytes(), file2.getBytes()));
+ assertTrue(ObjectUtils.nullSafeEquals("myContent2".getBytes(),
+ FileCopyUtils.copyToByteArray(file2.getInputStream())));
+ }
+
+}
diff --git a/spring-test/src/test/resources/log4j.xml b/spring-test/src/test/resources/log4j.xml
new file mode 100644
index 00000000..4375dcce
--- /dev/null
+++ b/spring-test/src/test/resources/log4j.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+ <!-- Appenders -->
+ <appender name="console" class="org.apache.log4j.ConsoleAppender">
+ <param name="Target" value="System.out" />
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%-5p: %c - %m%n" />
+ </layout>
+ </appender>
+
+ <appender name="file" class="org.apache.log4j.FileAppender">
+ <param name="File" value="build/spring-test.log" />
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%-5p: %c - %m%n" />
+ </layout>
+ </appender>
+
+ <logger name="org.springframework.beans">
+ <level value="warn" />
+ </logger>
+
+ <logger name="org.springframework.test.context.TestContext">
+ <level value="warn" />
+ </logger>
+ <logger name="org.springframework.test.context.ContextLoaderUtils">
+ <level value="warn" />
+ </logger>
+
+<!--
+ <logger name="org.springframework.test.context.support.DelegatingSmartContextLoader">
+ <level value="info" />
+ </logger>
+ <logger name="org.springframework.test.context.support.AbstractGenericContextLoader">
+ <level value="info" />
+ </logger>
+ <logger name="org.springframework.test.context.support.AnnotationConfigContextLoader">
+ <level value="info" />
+ </logger>
+-->
+
+ <logger name="org.springframework.test.context.transaction.TransactionalTestExecutionListener">
+ <level value="warn" />
+ </logger>
+
+ <logger name="org.springframework.test.context.web">
+ <level value="warn" />
+ </logger>
+
+ <logger name="org.springframework.test.context">
+ <level value="warn" />
+ </logger>
+
+ <!-- Root Logger -->
+ <root>
+ <priority value="error" />
+ <appender-ref ref="console" />
+ <appender-ref ref="file" />
+ </root>
+
+</log4j:configuration> \ No newline at end of file
diff --git a/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests-ChildConfig.xml b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests-ChildConfig.xml
new file mode 100644
index 00000000..2189b42f
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/SingleTestClassWithTwoLevelContextHierarchyAndMixedConfigTypesTests-ChildConfig.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="bar" class="java.lang.String" c:_="bar" />
+
+ <bean id="baz" class="java.lang.String" c:_="baz-child" />
+
+</beans>
diff --git a/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests-context.xml
new file mode 100644
index 00000000..3abc44bd
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/standard/TestHierarchyLevelTwoWithSingleLevelContextHierarchyAndMixedConfigTypesTests-context.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="foo" class="java.lang.String" c:_="foo-level-2" />
+
+ <bean id="baz" class="java.lang.String" c:_="baz" />
+
+</beans>
diff --git a/spring-test/src/test/resources/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests-context.xml
new file mode 100644
index 00000000..496fc0b0
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/context/hierarchies/web/DispatcherWacRootWacEarTests-context.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="dispatcher" class="java.lang.String" c:_="dispatcher" />
+
+</beans>
diff --git a/spring-test/src/test/resources/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests-context.xml
new file mode 100644
index 00000000..d0c28615
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/context/junit4/spr9799/Spr9799XmlConfigTests-context.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:mvc="http://www.springframework.org/schema/mvc"
+ xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <mvc:annotation-driven />
+ <mvc:default-servlet-handler />
+
+</beans>
diff --git a/spring-test/src/test/resources/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$ImproperDuplicateDefaultXmlAndConfigClassTestCase-context.xml b/spring-test/src/test/resources/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$ImproperDuplicateDefaultXmlAndConfigClassTestCase-context.xml
new file mode 100644
index 00000000..6f4ac807
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$ImproperDuplicateDefaultXmlAndConfigClassTestCase-context.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+ <!-- intentionally empty: we just need the file to be present to fail the test -->
+
+</beans>
diff --git a/spring-test/src/test/resources/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$XmlTestCase-context.xml b/spring-test/src/test/resources/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$XmlTestCase-context.xml
new file mode 100644
index 00000000..965ab428
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$XmlTestCase-context.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:c="http://www.springframework.org/schema/c"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
+
+ <bean id="foo" class="java.lang.String" c:_="foo" />
+
+</beans>
diff --git a/spring-test/src/test/resources/org/springframework/test/context/web/BasicXmlWacTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/web/BasicXmlWacTests-context.xml
new file mode 100644
index 00000000..90653e21
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/context/web/BasicXmlWacTests-context.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="foo" class="java.lang.String" c:_="bar" />
+
+</beans>
diff --git a/spring-test/src/test/resources/org/springframework/test/context/web/RequestAndSessionScopedBeansWacTests-context.xml b/spring-test/src/test/resources/org/springframework/test/context/web/RequestAndSessionScopedBeansWacTests-context.xml
new file mode 100644
index 00000000..7f2a6c5b
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/context/web/RequestAndSessionScopedBeansWacTests-context.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+ <bean id="requestScopedTestBean" class="org.springframework.tests.sample.beans.TestBean" scope="request">
+ <property name="name" value="#{request.contextPath}" />
+ </bean>
+
+ <bean id="sessionScopedTestBean" class="org.springframework.tests.sample.beans.TestBean" scope="session" />
+
+</beans>
diff --git a/spring-test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments.sql b/spring-test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments.sql
new file mode 100644
index 00000000..82483ca4
--- /dev/null
+++ b/spring-test/src/test/resources/org/springframework/test/jdbc/test-data-with-comments.sql
@@ -0,0 +1,16 @@
+-- The next comment line has no text after the '--' prefix.
+--
+-- The next comment line starts with a space.
+ -- x, y, z...
+
+insert into customer (id, name)
+values (1, 'Rod; Johnson'), (2, 'Adrian Collier');
+-- This is also a comment.
+insert into orders(id, order_date, customer_id)
+values (1, '2008-01-02', 2);
+insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2);
+INSERT INTO persons( person_id--
+ , name)
+VALUES( 1 -- person_id
+ , 'Name' --name
+);-- \ No newline at end of file