diff --git a/core/citrus-spring/src/main/java/org/citrusframework/common/SpringXmlTestLoader.java b/core/citrus-spring/src/main/java/org/citrusframework/common/SpringXmlTestLoader.java index b1ddb971d0..39fd1f456f 100644 --- a/core/citrus-spring/src/main/java/org/citrusframework/common/SpringXmlTestLoader.java +++ b/core/citrus-spring/src/main/java/org/citrusframework/common/SpringXmlTestLoader.java @@ -19,6 +19,8 @@ import java.io.File; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; import org.citrusframework.CitrusSpringContext; import org.citrusframework.DefaultTestCaseRunner; import org.citrusframework.TestCase; @@ -49,8 +51,8 @@ protected void doLoad() { try { testCase = ctx.getBean(testName, TestCase.class); - if (runner instanceof DefaultTestCaseRunner) { - ((DefaultTestCaseRunner) runner).setTestCase(testCase); + if (runner instanceof DefaultTestCaseRunner defaultTestCaseRunner) { + defaultTestCaseRunner.setTestCase(testCase); } configurer.forEach(handler -> handler.accept(testCase)); @@ -68,13 +70,14 @@ protected void doLoad() { */ private ApplicationContext loadApplicationContext() { try { - configureCustomParsers(); + ApplicationContext parentApplicationContext = getParentApplicationContext(); + configureCustomParsers(parentApplicationContext); return new ClassPathXmlApplicationContext( new String[]{ getSource(), "org/citrusframework/spring/annotation-config-ctx.xml"}, - true, getParentApplicationContext()); + true, parentApplicationContext); } catch (Exception e) { throw citrusContext.getTestContextFactory().getObject() .handleError(testName, packageName, "Failed to load test case", e); @@ -84,9 +87,11 @@ private ApplicationContext loadApplicationContext() { /** * Configures the CitrusNamespaceParserRegistry with custom parsers */ - private void configureCustomParsers() { - SpringXmlTestLoaderConfiguration loaderConfiguration = testClass.getAnnotation(SpringXmlTestLoaderConfiguration.class); - if (loaderConfiguration != null) { + private void configureCustomParsers(ApplicationContext parentApplicationContext) { + List beanDefinitionParserConfigurationList = retrieveSpringXmlTestLoaderConfigurations( + parentApplicationContext); + + for (SpringXmlTestLoaderConfiguration loaderConfiguration : beanDefinitionParserConfigurationList) { for (BeanDefinitionParserConfiguration beanDefinitionParserConfiguration : loaderConfiguration.parserConfigurations()) { Class parserClass = beanDefinitionParserConfiguration.parser(); try { @@ -99,11 +104,57 @@ private void configureCustomParsers() { } } } + + } + + /** + * Retrieves a collection of optional SpringXmlTestLoaderConfigurations. This collection is composed of: + * + * + * @param applicationContext the application context that optionally provides {@link SpringXmlTestLoaderConfigurer} beans + * @return a collection of SpringXmlTestLoaderConfigurations + * + * @see SpringXmlTestLoaderConfigurer + */ + private List retrieveSpringXmlTestLoaderConfigurations( + ApplicationContext applicationContext) { + List beanDefinitionParserConfigurationList = new ArrayList<>(); + + addOptionalTestClassLevelAnnotation( + testClass, + beanDefinitionParserConfigurationList); + addOptionalConfigurerClassLevelAnnotations(applicationContext, + beanDefinitionParserConfigurationList); + + return beanDefinitionParserConfigurationList; + } + + private void addOptionalConfigurerClassLevelAnnotations(ApplicationContext applicationContext, + List beanDefinitionParserConfigurationList) { + if (applicationContext != null) { + applicationContext.getBeansOfType( + SpringXmlTestLoaderConfigurer.class).values().forEach(configurer -> + addOptionalTestClassLevelAnnotation(configurer.getClass(), + beanDefinitionParserConfigurationList) + ); + } + } + + private void addOptionalTestClassLevelAnnotation(Class testClass, + List beanDefinitionParserConfigurationList) { + + SpringXmlTestLoaderConfiguration loaderConfiguration = testClass.getAnnotation(SpringXmlTestLoaderConfiguration.class); + if (loaderConfiguration != null) { + beanDefinitionParserConfigurationList.add(loaderConfiguration); + } } private ApplicationContext getParentApplicationContext() { - if (citrusContext instanceof CitrusSpringContext) { - return ((CitrusSpringContext) citrusContext).getApplicationContext(); + if (citrusContext instanceof CitrusSpringContext citrusSpringContext) { + return citrusSpringContext.getApplicationContext(); } return null; diff --git a/core/citrus-spring/src/main/java/org/citrusframework/common/SpringXmlTestLoaderConfigurer.java b/core/citrus-spring/src/main/java/org/citrusframework/common/SpringXmlTestLoaderConfigurer.java new file mode 100644 index 0000000000..10cc5346af --- /dev/null +++ b/core/citrus-spring/src/main/java/org/citrusframework/common/SpringXmlTestLoaderConfigurer.java @@ -0,0 +1,18 @@ +package org.citrusframework.common; + +/** + * A marker interface for identifying beans that supply a {@link SpringXmlTestLoaderConfiguration} + * annotation to configure the bean definition parser for SpringXmlTest parsing. + *

+ * To define specific bean definition parser configurations, you can implement this interface + * along with the corresponding {@link SpringXmlTestLoaderConfiguration} annotation and provide it + * as a bean through the parent {@link org.springframework.context.ApplicationContext} used for + * loading SpringXmlTest. + * + * + * @author Thorsten Schlathoelter + * @since 4.0 + * @see SpringXmlTestLoader + */ +public interface SpringXmlTestLoaderConfigurer { +} diff --git a/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringBeanXml_IT.java b/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringBeanXml_IT.java index b65fec5148..5b3b1a157f 100644 --- a/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringBeanXml_IT.java +++ b/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringBeanXml_IT.java @@ -38,26 +38,26 @@ */ @CitrusSpringSupport @ContextConfiguration(classes = {CitrusSpringConfig.class}) -public class SpringBeanXml_IT { +class SpringBeanXml_IT { @Test @DisplayName("SpringBeanXml_IT") @CitrusXmlTest(name = "SpringBeanXml_IT") - public void SpringBeanXml_0_IT() { + void SpringBeanXml_0_IT() { } @Test @CitrusTestSource(type = TestLoader.GROOVY, name = "echo.test", packageName = "org.citrusframework.junit.jupiter.simple") - public void SpringGroovy_IT() { + void SpringGroovy_IT() { } @Test @CitrusXmlTest(name = "SampleIT") - public void SpringBeanXml_1_IT() { + void SpringBeanXml_1_IT() { } @CitrusSpringXmlTestFactory - public Stream SpringBeanXml_2_IT() { + Stream SpringBeanXml_2_IT() { return Stream.of( CitrusTestFactorySupport.springXml().dynamicTest("org.citrusframework.junit.jupiter.integration.actions", "EchoActionIT"), CitrusTestFactorySupport.springXml().dynamicTest("org.citrusframework.junit.jupiter.integration.actions", "FailActionIT"), @@ -66,12 +66,12 @@ public Stream SpringBeanXml_2_IT() { } @CitrusSpringXmlTestFactory - public Stream SpringBeanXml_3_IT() { + Stream SpringBeanXml_3_IT() { return CitrusTestFactorySupport.springXml().packageScan("org.citrusframework.junit.jupiter.simple"); } @Test @CitrusXmlTest(sources = "classpath:org/citrusframework/junit/jupiter/integration/spring/SampleIT.xml") - public void SpringBeanXml_4_IT() { + void SpringBeanXml_4_IT() { } } diff --git a/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringBean_IT.java b/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringBean_IT.java index 9d776273b1..5cdfda4a30 100644 --- a/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringBean_IT.java +++ b/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringBean_IT.java @@ -47,7 +47,7 @@ */ @CitrusSpringSupport @ContextConfiguration(classes = {CitrusSpringConfig.class, SpringBean_IT.EndpointConfig.class}) -public class SpringBean_IT { +class SpringBean_IT { @Autowired private DirectEndpoint direct; @@ -65,7 +65,7 @@ void springBeanTest(@CitrusResource TestActionRunner actions) { } @Configuration - public static class EndpointConfig { + static class EndpointConfig { @Bean public BeforeTest beforeTest() { diff --git a/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringXmlTestLoader_IT.java b/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringXmlTestLoader_IT.java index 9cba0f81a2..72b9728ccf 100644 --- a/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringXmlTestLoader_IT.java +++ b/runtime/citrus-junit5/src/test/java/org/citrusframework/junit/jupiter/integration/spring/SpringXmlTestLoader_IT.java @@ -7,13 +7,17 @@ import org.citrusframework.annotations.CitrusXmlTest; import org.citrusframework.common.BeanDefinitionParserConfiguration; import org.citrusframework.common.SpringXmlTestLoaderConfiguration; +import org.citrusframework.common.SpringXmlTestLoaderConfigurer; import org.citrusframework.config.CitrusSpringConfig; import org.citrusframework.config.xml.BaseTestCaseMetaInfoParser; import org.citrusframework.config.xml.BaseTestCaseParser; +import org.citrusframework.junit.jupiter.integration.spring.SpringXmlTestLoader_IT.CustomConfiguration; import org.citrusframework.junit.jupiter.spring.CitrusSpringSupport; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; @@ -22,34 +26,33 @@ * @author Thorsten Schlathoelter */ @CitrusSpringSupport -@ContextConfiguration(classes = {CitrusSpringConfig.class}) +@ContextConfiguration(classes = {CitrusSpringConfig.class, CustomConfiguration.class}) @SpringXmlTestLoaderConfiguration( parserConfigurations = { @BeanDefinitionParserConfiguration(name = "testcase", parser = SpringXmlTestLoader_IT.CustomTestCaseParser.class), - @BeanDefinitionParserConfiguration(name = "meta-info", parser = SpringXmlTestLoader_IT.CustomTestCaseMetaInfoParser.class) }) -public class SpringXmlTestLoader_IT { +class SpringXmlTestLoader_IT { @Test @CitrusXmlTest(name="SpringXmlTestLoader_IT") - public void SpringXmlTestLoaderIT_0_IT(@CitrusResource TestCaseRunner runner) { + void SpringXmlTestLoaderIT_0_IT(@CitrusResource TestCaseRunner runner) { Assertions.assertNotNull(runner.getTestCase()); TestCaseMetaInfo metaInfo = runner.getTestCase().getMetaInfo(); Assertions.assertTrue(metaInfo instanceof CustomTestCaseMetaInfo); - Assertions.assertEquals(((CustomTestCaseMetaInfo)metaInfo).getDescription(), "Foo bar: F#!$§ed up beyond all repair"); + Assertions.assertEquals( "Foo bar: F#!$§ed up beyond all repair", ((CustomTestCaseMetaInfo)metaInfo).getDescription()); } /** * A custom test case implementation that should be created by the loader */ - public static class CustomTestCase extends DefaultTestCase { + static class CustomTestCase extends DefaultTestCase { } /** * A custom test case meta info that should be created by the loader */ - public static class CustomTestCaseMetaInfo extends TestCaseMetaInfo { + static class CustomTestCaseMetaInfo extends TestCaseMetaInfo { private String description; @@ -91,8 +94,29 @@ protected void parseAdditionalProperties(Element metaInfoElement, BeanDefinition metaInfoBuilder.addPropertyValue("description", description); } } + } + /** + * A loader configuration that can be added as a bean and that will be picked up for configuration + * of any XML test case. + */ + @SpringXmlTestLoaderConfiguration( + parserConfigurations = { + @BeanDefinitionParserConfiguration(name = "meta-info", parser = SpringXmlTestLoader_IT.CustomTestCaseMetaInfoParser.class) + }) + static class CustomTestLoaderConfigurer implements SpringXmlTestLoaderConfigurer { } + /** + * A configuration that provides the CustomTestLoaderConfigurer bean for configuration of + * all SpringXmlTest cases. + */ + @Configuration + static class CustomConfiguration { + @Bean + public CustomTestLoaderConfigurer customTestLoaderConfigurer() { + return new CustomTestLoaderConfigurer(); + } + } }