From d6bfedfccdd0e98839de2579b9951619daf622ba Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 16 Sep 2020 17:58:01 +0200 Subject: [PATCH] MP Testing (#2353) * MP Testing * support for per test method CDI container. * support custom config sources * support for constructor injection in single CDI container mode * support for `SeContainer` parameter in per test method CDI container mode * validations of annotations depending on mode Signed-off-by: Tomas Langer --- bom/pom.xml | 6 + docs/mp/testing/01_testing.adoc | 93 ++++ docs/sitegen.yaml | 8 + microprofile/fault-tolerance/pom.xml | 5 + .../faulttolerance/FaultToleranceTest.java | 34 +- microprofile/security/pom.xml | 5 + .../microprofile/security/InjectionTest.java | 20 +- .../microprofile/security/TestBean.java | 4 - .../src/test/resources/META-INF/beans.xml | 23 - microprofile/tests/junit5/pom.xml | 58 +++ .../microprofile/tests/junit5/AddBean.java | 53 +++ .../microprofile/tests/junit5/AddBeans.java | 36 ++ .../microprofile/tests/junit5/AddConfig.java | 43 ++ .../microprofile/tests/junit5/AddConfigs.java | 37 ++ .../tests/junit5/AddExtension.java | 39 ++ .../tests/junit5/AddExtensions.java | 36 ++ .../tests/junit5/Configuration.java | 45 ++ .../tests/junit5/DisableDiscovery.java | 50 ++ .../tests/junit5/HelidonJunitExtension.java | 434 ++++++++++++++++++ .../tests/junit5/HelidonTest.java | 52 +++ .../tests/junit5/package-info.java | 22 + .../tests/junit5/TestAddBean.java | 54 +++ .../tests/junit5/TestConfigSources.java | 38 ++ .../junit5/TestConstructorInjection.java | 54 +++ .../tests/junit5/TestCustomConfig.java | 54 +++ .../tests/junit5/TestCustomExtension.java | 44 ++ .../tests/junit5/TestDefaults.java | 68 +++ .../tests/junit5/TestNoDiscovery.java | 57 +++ .../tests/junit5/TestPerMethod.java | 107 +++++ .../resources/testConfigSources.properties | 17 + microprofile/tests/pom.xml | 1 + 31 files changed, 1535 insertions(+), 62 deletions(-) create mode 100644 docs/mp/testing/01_testing.adoc delete mode 100644 microprofile/security/src/test/resources/META-INF/beans.xml create mode 100644 microprofile/tests/junit5/pom.xml create mode 100644 microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddBean.java create mode 100644 microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddBeans.java create mode 100644 microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddConfig.java create mode 100644 microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddConfigs.java create mode 100644 microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddExtension.java create mode 100644 microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddExtensions.java create mode 100644 microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/Configuration.java create mode 100644 microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/DisableDiscovery.java create mode 100644 microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/HelidonJunitExtension.java create mode 100644 microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/HelidonTest.java create mode 100644 microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/package-info.java create mode 100644 microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestAddBean.java create mode 100644 microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestConfigSources.java create mode 100644 microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestConstructorInjection.java create mode 100644 microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestCustomConfig.java create mode 100644 microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestCustomExtension.java create mode 100644 microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestDefaults.java create mode 100644 microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestNoDiscovery.java create mode 100644 microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestPerMethod.java create mode 100644 microprofile/tests/junit5/src/test/resources/testConfigSources.properties diff --git a/bom/pom.xml b/bom/pom.xml index f220761081c..69558b8385d 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -835,6 +835,12 @@ helidon-messaging-kafka ${helidon.version} + + + io.helidon.microprofile.tests + helidon-microprofile-tests-junit5 + ${helidon.version} + diff --git a/docs/mp/testing/01_testing.adoc b/docs/mp/testing/01_testing.adoc new file mode 100644 index 00000000000..ed1c0901aae --- /dev/null +++ b/docs/mp/testing/01_testing.adoc @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2020 Oracle and/or its affiliates. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +/////////////////////////////////////////////////////////////////////////////// + += Testing with JUnit5 +:h1Prefix: MP +:pagename: testing +:description: Helidon Testing +:keywords: helidon, mp, test, testing + +Helidon provides built-in test support for CDI testing in JUnit5. + +== Dependency + +[source,xml] +.Maven dependency +---- + + io.helidon.microprofile.tests + helidon-microprofile-tests-junit5 + test + +---- + +== Usage - default +A test can be annotated with `io.helidon.microprofile.tests.junit5.HelidonTest` annotation to mark it as a +CDI test. This annotation will start the CDI container before any test method is invoked, and stop it after +the last method is invoked. This annotation also enables injection into the test class itself. + +In addition to this simplification, the following annotations are supported: + +- `io.helidon.microprofile.tests.junit5.AddBean` - to add one or more beans to the container + (if not part of a bean archive, or when discovery is disabled) +- `io.helidon.microprofile.tests.junit5.AddExtension` - to add one or more CDI extensions to the container + (if not added through service loader, or when discovery is disabled) +- `io.helidon.microprofile.tests.junit5.AddConfig` - to add one or more configuration properties to MicroProfile config + without the need of creating a `microprofile-config.properties` file + +[source,java] +.Code sample +---- +@HelidonTest +@DisableDiscovery +@AddBean(MyBean.class) +@AddExtension(ConfigCdiExtension.class) +@AddConfig(key = "app.greeting", value = "TestHello") +class TestNoDiscovery { + @Inject + private MyBean myBean; + + @Test + void testGreeting() { + assertThat(myBean, notNullValue()); + assertThat(myBean.greeting(), is("TestHello")); + } +} +---- + +== Usage - per method CDI container +A test can be annotated as follows: + +`@HelidonTest(resetPerTest = true)` + +This will change the behavior as follows: + +- A new CDI container is created for each test method invocation +- annotations to add config, beans and extension can be added for each method in addition to the class +- you cannot inject fields or constructor parameters of the test class itself (as a single instance is shared by more containers) +- you can add `SeContainer` as a method parameter of any test method and you will get the current container + +== Usage - configuration +In addition to the `@AddConfig` annotation, you can also use + `@Configuration`. + +This allows you to do the following: + +- when `useExisting` is set to `true`, the configuration will not be changed + and current MP configuration will be used +- you can configure additional classpath properties config sources using `configSources` \ No newline at end of file diff --git a/docs/sitegen.yaml b/docs/sitegen.yaml index 59859ec52a0..8b17b972d8c 100644 --- a/docs/sitegen.yaml +++ b/docs/sitegen.yaml @@ -373,6 +373,14 @@ backend: items: - includes: - "mp/aot/*.adoc" + - title: "Testing" + pathprefix: "/mp/testing" + glyph: + type: "icon" + value: "thumbs_up_down" + items: + - includes: + - "mp/testing/*.adoc" - title: "Additional resources" items: - title: "Javadocs" diff --git a/microprofile/fault-tolerance/pom.xml b/microprofile/fault-tolerance/pom.xml index 836b43d1cbf..0df693bfdc3 100644 --- a/microprofile/fault-tolerance/pom.xml +++ b/microprofile/fault-tolerance/pom.xml @@ -115,5 +115,10 @@ junit-jupiter-params test + + io.helidon.microprofile.tests + helidon-microprofile-tests-junit5 + test + diff --git a/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/FaultToleranceTest.java b/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/FaultToleranceTest.java index 1294287aaa0..f06b65949a2 100644 --- a/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/FaultToleranceTest.java +++ b/microprofile/fault-tolerance/src/test/java/io/helidon/microprofile/faulttolerance/FaultToleranceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,6 @@ package io.helidon.microprofile.faulttolerance; -import javax.enterprise.inject.literal.NamedLiteral; -import javax.enterprise.inject.se.SeContainer; -import javax.enterprise.inject.spi.CDI; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; @@ -30,43 +27,30 @@ import java.util.function.Supplier; import java.util.stream.Stream; -import io.helidon.microprofile.cdi.HelidonContainer; +import javax.enterprise.inject.literal.NamedLiteral; +import javax.enterprise.inject.spi.CDI; + +import io.helidon.microprofile.tests.junit5.HelidonTest; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import static org.junit.jupiter.api.Assertions.fail; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.fail; /** * Class FaultToleranceTest. */ +@HelidonTest public abstract class FaultToleranceTest { private static final long TIMEOUT = 5000; private static final TimeUnit TIMEOUT_UNITS = TimeUnit.MILLISECONDS; - - private static SeContainer cdiContainer; - private static final int NUMBER_OF_THREADS = 20; private static Executor executor = Executors.newFixedThreadPool(NUMBER_OF_THREADS); - @BeforeAll - public static void startCdiContainer() { - cdiContainer = HelidonContainer.instance().start(); - } - - @AfterAll - public static void shutDownCdiContainer() { - if (cdiContainer != null) { - cdiContainer.close(); - } - } - /** * Clears all internal handlers before running each test. Latest FT spec has * clarified that each method of each class that uses a bulkhead/breaker has @@ -88,13 +72,13 @@ protected static T newNamedBean(Class beanClass) { public static void printStatus(String message, String status) { System.out.println(message + " -> " + status + " [Thread: " - + Thread.currentThread().getName() + "]"); + + Thread.currentThread().getName() + "]"); } @SuppressWarnings("unchecked") static CompletableFuture[] getConcurrentCalls(Supplier supplier, int size) { return Stream.generate( - () -> CompletableFuture.supplyAsync(supplier, executor) + () -> CompletableFuture.supplyAsync(supplier, executor) ).limit(size).toArray(CompletableFuture[]::new); } diff --git a/microprofile/security/pom.xml b/microprofile/security/pom.xml index bc5b0017a85..9667f5ada95 100644 --- a/microprofile/security/pom.xml +++ b/microprofile/security/pom.xml @@ -89,5 +89,10 @@ mockito-core test + + io.helidon.microprofile.tests + helidon-microprofile-tests-junit5 + test + diff --git a/microprofile/security/src/test/java/io/helidon/microprofile/security/InjectionTest.java b/microprofile/security/src/test/java/io/helidon/microprofile/security/InjectionTest.java index 4b1e43ce844..85d8ecc27d8 100644 --- a/microprofile/security/src/test/java/io/helidon/microprofile/security/InjectionTest.java +++ b/microprofile/security/src/test/java/io/helidon/microprofile/security/InjectionTest.java @@ -15,9 +15,10 @@ */ package io.helidon.microprofile.security; -import javax.enterprise.inject.se.SeContainer; +import javax.inject.Inject; -import io.helidon.microprofile.cdi.HelidonContainer; +import io.helidon.microprofile.tests.junit5.AddBean; +import io.helidon.microprofile.tests.junit5.HelidonTest; import io.helidon.security.Security; import org.junit.jupiter.api.Test; @@ -25,16 +26,15 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +@HelidonTest +@AddBean(TestBean.class) class InjectionTest { + @Inject + private TestBean testBean; + @Test void testInjection() { - - try (SeContainer container = HelidonContainer.instance() - .start()) { - TestBean bean = container.select(TestBean.class) - .get(); - Security security = bean.getSecurity(); - assertThat(security, notNullValue()); - } + Security security = testBean.getSecurity(); + assertThat(security, notNullValue()); } } diff --git a/microprofile/security/src/test/java/io/helidon/microprofile/security/TestBean.java b/microprofile/security/src/test/java/io/helidon/microprofile/security/TestBean.java index f2d3402ba02..66f6f8cf09a 100644 --- a/microprofile/security/src/test/java/io/helidon/microprofile/security/TestBean.java +++ b/microprofile/security/src/test/java/io/helidon/microprofile/security/TestBean.java @@ -15,17 +15,13 @@ */ package io.helidon.microprofile.security; -import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; import io.helidon.security.Security; /** * Test bean. */ -@ApplicationScoped public class TestBean { @Inject private Security security; diff --git a/microprofile/security/src/test/resources/META-INF/beans.xml b/microprofile/security/src/test/resources/META-INF/beans.xml deleted file mode 100644 index 0f51c37778a..00000000000 --- a/microprofile/security/src/test/resources/META-INF/beans.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - diff --git a/microprofile/tests/junit5/pom.xml b/microprofile/tests/junit5/pom.xml new file mode 100644 index 00000000000..c35df2df17f --- /dev/null +++ b/microprofile/tests/junit5/pom.xml @@ -0,0 +1,58 @@ + + + + + 4.0.0 + + io.helidon.microprofile.tests + tests-project + 2.0.3-SNAPSHOT + + + helidon-microprofile-tests-junit5 + Helidon Microprofile Tests JUnit5 + + + Integration with Junit5 to support tests with CDI injection + + + + + io.helidon.microprofile.cdi + helidon-microprofile-cdi + provided + + + io.helidon.microprofile.config + helidon-microprofile-config + provided + + + org.junit.jupiter + junit-jupiter-api + provided + + + org.hamcrest + hamcrest-core + test + + + + \ No newline at end of file diff --git a/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddBean.java b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddBean.java new file mode 100644 index 00000000000..366aa63ca41 --- /dev/null +++ b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddBean.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.microprofile.tests.junit5; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.enterprise.context.ApplicationScoped; + +/** + * Add a bean. + * This is intended for test sources where we do not want to add {@code beans.xml} as this would add + * all test classes as beans. + * The bean will be added by default with {@link javax.enterprise.context.ApplicationScoped}. + * The class will be instantiated using CDI and will be available for injection into test classes and other beans. + * This annotation can be repeated. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Repeatable(AddBeans.class) +public @interface AddBean { + /** + * Class of the bean. + * @return the class of a bean + */ + Class value(); + + /** + * Scope of the bean. + * Only {@link javax.inject.Singleton}, {@link javax.enterprise.context.ApplicationScoped} + * and {@link javax.enterprise.context.RequestScoped} scopes are supported. + * + * @return scope of the bean + */ + Class scope() default ApplicationScoped.class; +} diff --git a/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddBeans.java b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddBeans.java new file mode 100644 index 00000000000..a7e3b515b97 --- /dev/null +++ b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddBeans.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.microprofile.tests.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A repeatable container for {@link io.helidon.microprofile.tests.junit5.AddBean}. + * No need to use this annotation, just repeat {@link io.helidon.microprofile.tests.junit5.AddBean} annotation + * on test class. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface AddBeans { + /** + * Beans to be added. + * @return add bean annotations + */ + AddBean[] value(); +} diff --git a/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddConfig.java b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddConfig.java new file mode 100644 index 00000000000..5c078366bb6 --- /dev/null +++ b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddConfig.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.microprofile.tests.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Add a configuration key/value pair to MicroProfile configuration. + * This annotation can be repeated. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Repeatable(AddConfigs.class) +public @interface AddConfig { + /** + * Configuration property key. + * @return key + */ + String key(); + + /** + * Configuration property value. + * @return value + */ + String value(); +} diff --git a/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddConfigs.java b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddConfigs.java new file mode 100644 index 00000000000..ce3e7dfdabe --- /dev/null +++ b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddConfigs.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.microprofile.tests.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A repeatable container for {@link io.helidon.microprofile.tests.junit5.AddConfig}. + * No need to use this annotation, just repeat {@link io.helidon.microprofile.tests.junit5.AddConfig} annotation + * on test class. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface AddConfigs { + /** + * Configuration properties to be added. + * + * @return properties + */ + AddConfig[] value(); +} diff --git a/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddExtension.java b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddExtension.java new file mode 100644 index 00000000000..e7f1bcc1fc1 --- /dev/null +++ b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddExtension.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.microprofile.tests.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.enterprise.inject.spi.Extension; + +/** + * Add a CDI extension to the test container. + * This annotation can be repeated. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Repeatable(AddExtensions.class) +public @interface AddExtension { + /** + * Class of the extension to add. The class must be public. + * @return extension class. + */ + Class value(); +} diff --git a/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddExtensions.java b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddExtensions.java new file mode 100644 index 00000000000..ff0012f68ea --- /dev/null +++ b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/AddExtensions.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.microprofile.tests.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A repeatable container for {@link io.helidon.microprofile.tests.junit5.AddExtension}. + * No need to use this annotation, just repeat {@link io.helidon.microprofile.tests.junit5.AddExtension} annotation + * on test class. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface AddExtensions { + /** + * Extensions to be added. + * @return extensions + */ + AddExtension[] value(); +} diff --git a/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/Configuration.java b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/Configuration.java new file mode 100644 index 00000000000..320b956df86 --- /dev/null +++ b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/Configuration.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.tests.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Additional configuration of config itself. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface Configuration { + /** + * If set to {@code true}, the existing (or default) MicroProfile configuration would be used. + * By default uses a configuration constructed using all {@link io.helidon.microprofile.tests.junit5.AddConfig} + * annotations and {@link #configSources()}. + * + * @return whether to use existing (or default) configuration, or customized one + */ + boolean useExisting() default false; + + /** + * Class path properties config sources to add to configuration of this test class or method. + * + * @return config sources to add + */ + String[] configSources() default {}; +} diff --git a/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/DisableDiscovery.java b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/DisableDiscovery.java new file mode 100644 index 00000000000..2f25c9412d6 --- /dev/null +++ b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/DisableDiscovery.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.tests.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Whether discovery is automated or disabled. If discovery is desired, do not annotate test + * class with this annotation. + *

+ * When discovery is enabled, the whole classpath is scanned for bean archives (jar files containing + * {@code META-INF/beans.xml}) and all beans and extensions are added automatically. + *

+ * When discovery is disabled, CDI would only contain the CDI implementation itself and beans and extensions added + * through annotations {@link io.helidon.microprofile.tests.junit5.AddBean} and + * {@link io.helidon.microprofile.tests.junit5.AddExtension} + * + * If discovery is disabled on class level and desired on method level, + * the value can be set to {@code false}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface DisableDiscovery { + /** + * By default if you annotate a class or a method, discovery gets disabled. + * If you want to override configuration on method to differ from class, you + * can configure the value to {@code false}, effectively enabling discovery. + * + * @return whether to disable discovery ({@code true}), or enable it ({@code false}). If this + * annotation is not present, discovery is enabled + */ + boolean value() default true; +} diff --git a/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/HelidonJunitExtension.java b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/HelidonJunitExtension.java new file mode 100644 index 00000000000..50507a3020b --- /dev/null +++ b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/HelidonJunitExtension.java @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.tests.junit5; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; +import javax.enterprise.inject.spi.BeforeBeanDiscovery; +import javax.enterprise.inject.spi.Extension; +import javax.inject.Inject; +import javax.inject.Singleton; + +import io.helidon.config.mp.MpConfigSources; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigBuilder; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; + +/** + * Junit5 extension to support Helidon CDI container in tests. + */ +class HelidonJunitExtension implements BeforeAllCallback, + AfterAllCallback, + BeforeEachCallback, + AfterEachCallback, + InvocationInterceptor, + ParameterResolver { + private static final Set> HELIDON_TEST_ANNOTATIONS = + Set.of(AddBean.class, AddConfig.class, AddExtension.class, Configuration.class); + + private final List classLevelExtensions = new ArrayList<>(); + private final List classLevelBeans = new ArrayList<>(); + private final ConfigMeta classLevelConfigMeta = new ConfigMeta(); + private boolean classLevelDisableDiscovery = false; + private boolean resetPerTest; + + private Class testClass; + private ConfigProviderResolver configProviderResolver; + private Config config; + private SeContainer container; + + @SuppressWarnings("unchecked") + @Override + public void beforeAll(ExtensionContext context) { + testClass = context.getRequiredTestClass(); + + AddConfig[] configs = testClass.getAnnotationsByType(AddConfig.class); + classLevelConfigMeta.addConfig(configs); + classLevelConfigMeta.configuration(testClass.getAnnotation(Configuration.class)); + configProviderResolver = ConfigProviderResolver.instance(); + + AddExtension[] extensions = testClass.getAnnotationsByType(AddExtension.class); + classLevelExtensions.addAll(Arrays.asList(extensions)); + + AddBean[] beans = testClass.getAnnotationsByType(AddBean.class); + classLevelBeans.addAll(Arrays.asList(beans)); + + HelidonTest testAnnot = testClass.getAnnotation(HelidonTest.class); + if (testAnnot != null) { + resetPerTest = testAnnot.resetPerTest(); + } + + DisableDiscovery discovery = testClass.getAnnotation(DisableDiscovery.class); + if (discovery != null) { + classLevelDisableDiscovery = discovery.value(); + } + + if (resetPerTest) { + validatePerTest(); + + return; + } + validatePerClass(); + + configure(classLevelConfigMeta); + + startContainer(classLevelBeans, classLevelExtensions, classLevelDisableDiscovery); + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + if (resetPerTest) { + Method method = context.getRequiredTestMethod(); + AddConfig[] configs = method.getAnnotationsByType(AddConfig.class); + ConfigMeta methodLevelConfigMeta = classLevelConfigMeta.nextMethod(); + methodLevelConfigMeta.addConfig(configs); + methodLevelConfigMeta.configuration(method.getAnnotation(Configuration.class)); + + configure(methodLevelConfigMeta); + + List methodLevelExtensions = new ArrayList<>(classLevelExtensions); + List methodLevelBeans = new ArrayList<>(classLevelBeans); + boolean methodLevelDisableDiscovery = classLevelDisableDiscovery; + + AddExtension[] extensions = method.getAnnotationsByType(AddExtension.class); + methodLevelExtensions.addAll(Arrays.asList(extensions)); + + AddBean[] beans = method.getAnnotationsByType(AddBean.class); + methodLevelBeans.addAll(Arrays.asList(beans)); + + DisableDiscovery discovery = method.getAnnotation(DisableDiscovery.class); + if (discovery != null) { + methodLevelDisableDiscovery = discovery.value(); + } + + startContainer(methodLevelBeans, methodLevelExtensions, methodLevelDisableDiscovery); + } + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + if (resetPerTest) { + releaseConfig(); + stopContainer(); + } + } + + private void validatePerClass() { + Method[] methods = testClass.getMethods(); + for (Method method : methods) { + if (method.getAnnotation(Test.class) != null) { + // a test method + if (hasHelidonTestAnnotation(method)) { + throw new RuntimeException("When a class is annotated with @HelidonTest, " + + "there is a single CDI container used to invoke all " + + "test methods on the class. Method " + method + + " has an annotation that modifies container behavior."); + } + } + } + + methods = testClass.getDeclaredMethods(); + for (Method method : methods) { + if (method.getAnnotation(Test.class) != null) { + // a test method + if (hasHelidonTestAnnotation(method)) { + throw new RuntimeException("When a class is annotated with @HelidonTest, " + + "there is a single CDI container used to invoke all " + + "test methods on the class. Method " + method + + " has an annotation that modifies container behavior."); + } + } + } + } + + private boolean hasHelidonTestAnnotation(AnnotatedElement element) { + for (Class aClass : HELIDON_TEST_ANNOTATIONS) { + if (element.getAnnotation(aClass) != null) { + return true; + } + } + return false; + } + + private void validatePerTest() { + Constructor[] constructors = testClass.getConstructors(); + if (constructors.length > 1) { + throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true)," + + " the class must have only a single no-arg constructor"); + } + if (constructors.length == 1) { + Constructor c = constructors[0]; + if (c.getParameterCount() > 0) { + throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true)," + + " the class must have a no-arg constructor"); + } + } + + Field[] fields = testClass.getFields(); + for (Field field : fields) { + if (field.getAnnotation(Inject.class) != null) { + throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true)," + + " injection into fields or constructor is not supported, as each" + + " test method uses a different CDI container. Field " + field + + " is annotated with @Inject"); + } + } + + fields = testClass.getDeclaredFields(); + for (Field field : fields) { + if (field.getAnnotation(Inject.class) != null) { + throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true)," + + " injection into fields or constructor is not supported, as each" + + " test method uses a different CDI container. Field " + field + + " is annotated with @Inject"); + } + } + } + + private void configure(ConfigMeta configMeta) { + if (config != null) { + configProviderResolver.releaseConfig(config); + } + if (!configMeta.useExisting) { + // only create a custom configuration if not provided by test method/class + // prepare configuration + ConfigBuilder builder = configProviderResolver.getBuilder(); + + configMeta.additionalSources.forEach(it -> { + builder.withSources(MpConfigSources.classPath(it).toArray(new ConfigSource[0])); + }); + + config = builder + .withSources(MpConfigSources.create(configMeta.additionalKeys)) + .addDefaultSources() + .addDiscoveredSources() + .addDiscoveredConverters() + .build(); + configProviderResolver.registerConfig(config, Thread.currentThread().getContextClassLoader()); + } + } + + private void releaseConfig() { + if (configProviderResolver != null && config != null) { + configProviderResolver.releaseConfig(config); + config = null; + } + } + + @SuppressWarnings("unchecked") + private void startContainer(List beanAnnotations, + List extensionAnnotations, + boolean disableDiscovery) { + + // now let's prepare the CDI bootstrapping + SeContainerInitializer initializer = SeContainerInitializer.newInstance(); + + if (disableDiscovery) { + initializer.disableDiscovery(); + } + + initializer.addExtensions(new AddBeansExtension(testClass, beanAnnotations)); + + for (AddExtension addExtension : extensionAnnotations) { + Class extensionClass = addExtension.value(); + if (Modifier.isPublic(extensionClass.getModifiers())) { + initializer.addExtensions(addExtension.value()); + } else { + throw new IllegalArgumentException("Extension classes must be public, but " + extensionClass + .getName() + " is not"); + } + } + + container = initializer.initialize(); + } + + private void stopContainer() { + if (container != null) { + container.close(); + container = null; + } + } + + @Override + public void afterAll(ExtensionContext context) { + stopContainer(); + releaseConfig(); + } + + @Override + public T interceptTestClassConstructor(Invocation invocation, + ReflectiveInvocationContext> invocationContext, + ExtensionContext extensionContext) throws Throwable { + + if (resetPerTest) { + // Junit creates test instance + return invocation.proceed(); + } + + // we need to replace instantiation with CDI lookup, to properly injection into fields (and constructors) + invocation.skip(); + + return container.select(invocationContext.getExecutable().getDeclaringClass()) + .get(); + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + + Executable executable = parameterContext.getParameter().getDeclaringExecutable(); + + if (resetPerTest) { + if (executable instanceof Constructor) { + throw new ParameterResolutionException( + "When a test class is annotated with @HelidonTest(resetPerMethod=true), constructor must not have " + + "parameters."); + } else if (executable instanceof Method) { + if (parameterContext.getParameter().getType().equals(SeContainer.class)) { + return true; + } + } + } + + if (executable instanceof Constructor) { + return !container.select(parameterContext.getParameter().getType()).isUnsatisfied(); + } + + return false; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + + if (parameterContext.getParameter().getType().equals(SeContainer.class)) { + return container; + } + // we return null, as construction of the object is done by CDI + // for primitive types we must return appropriate primitive default + Class type = parameterContext.getParameter().getType(); + if (type.isPrimitive()) { + // a hack to get to default value of a primitive type + return Array.get(Array.newInstance(type, 1), 0); + } else { + return null; + } + } + + // this is not registered as a bean - we manually register an instance + @SuppressWarnings("CdiManagedBeanInconsistencyInspection") + private static class AddBeansExtension implements Extension { + private final Class testClass; + private final List addBeans; + + private AddBeansExtension(Class testClass, List addBeans) { + this.testClass = testClass; + this.addBeans = addBeans; + } + + void registerAddedBeans(@Observes BeforeBeanDiscovery event) { + event.addAnnotatedType(testClass, "junit-" + testClass.getName()) + .add(ApplicationScoped.Literal.INSTANCE); + + for (AddBean addBean : addBeans) { + Annotation scope; + Class definedScope = addBean.scope(); + + if (definedScope.equals(ApplicationScoped.class) || definedScope.equals(Singleton.class)) { + scope = ApplicationScoped.Literal.INSTANCE; + } else if (definedScope.equals(RequestScoped.class)) { + scope = RequestScoped.Literal.INSTANCE; + } else { + throw new IllegalStateException( + "Only Singleton, ApplicationScoped and RequestScoped are allowed in tests. Scope " + definedScope + .getName() + " is not allowed for bean " + addBean.value().getName()); + } + + event.addAnnotatedType(addBean.value(), "junit-" + addBean.value().getName()) + .add(scope); + } + } + } + + private static final class ConfigMeta { + private final Map additionalKeys = new HashMap<>(); + private final List additionalSources = new ArrayList<>(); + private boolean useExisting; + + private ConfigMeta() { + additionalKeys.put("mp.initializer.allow", "true"); + additionalKeys.put("mp.initializer.no-warn", "true"); + // higher ordinal then all the defaults, system props and environment variables + additionalKeys.putIfAbsent(ConfigSource.CONFIG_ORDINAL, "1000"); + } + + private void addConfig(AddConfig[] configs) { + for (AddConfig config : configs) { + additionalKeys.put(config.key(), config.value()); + } + } + + private void configuration(Configuration config) { + if (config == null) { + return; + } + useExisting = config.useExisting(); + additionalSources.addAll(List.of(config.configSources())); + } + + public ConfigMeta nextMethod() { + ConfigMeta methodMeta = new ConfigMeta(); + + methodMeta.additionalKeys.putAll(this.additionalKeys); + methodMeta.additionalSources.addAll(this.additionalSources); + methodMeta.useExisting = this.useExisting; + + return methodMeta; + } + } +} diff --git a/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/HelidonTest.java b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/HelidonTest.java new file mode 100644 index 00000000000..40e89b901d5 --- /dev/null +++ b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/HelidonTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.microprofile.tests.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * An annotation making this test class a CDI bean with support for injection. + *

+ * There is no need to provide {@code beans.xml} (actually it is not recommended, as it would combine beans + * from all tests), instead use {@link io.helidon.microprofile.tests.junit5.AddBean}, + * {@link io.helidon.microprofile.tests.junit5.AddExtension}, and {@link io.helidon.microprofile.tests.junit5.AddConfig} + * annotations to control the shape of the container. + *

+ * To disable automated bean and extension discovery, annotate the class with + * {@link io.helidon.microprofile.tests.junit5.DisableDiscovery}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@ExtendWith(HelidonJunitExtension.class) +public @interface HelidonTest { + /** + * By default, CDI container is created once before the class is initialized and shut down + * after. All test methods run within the same container. + * + * If this is set to {@code true}, a container is created per test method invocation. + * This restricts the test in the following way: + * 1. No injection into fields + * 2. No injection into constructor + * + * @return whether to reset container per test method + */ + boolean resetPerTest() default false; +} diff --git a/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/package-info.java b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/package-info.java new file mode 100644 index 00000000000..f062b6d32ae --- /dev/null +++ b/microprofile/tests/junit5/src/main/java/io/helidon/microprofile/tests/junit5/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * JUnit5 extension to run CDI tests. + * + * @see io.helidon.microprofile.tests.junit5.HelidonTest + */ +package io.helidon.microprofile.tests.junit5; diff --git a/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestAddBean.java b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestAddBean.java new file mode 100644 index 00000000000..42573b88d86 --- /dev/null +++ b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestAddBean.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.tests.junit5; + +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.Test; + +import static io.helidon.microprofile.tests.junit5.TestDefaults.DEFAULT_VALUE; +import static io.helidon.microprofile.tests.junit5.TestDefaults.PROPERTY_NAME; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +@HelidonTest +@AddBean(TestAddBean.MyBean.class) +class TestAddBean { + @Inject + private MyBean myBean; + + @Test + void testIt() { + assertThat(myBean, notNullValue()); + assertThat(myBean.configured(), is(DEFAULT_VALUE)); + } + + static class MyBean { + private final String configured; + + @Inject + MyBean(@ConfigProperty(name = PROPERTY_NAME, defaultValue = DEFAULT_VALUE) String configured) { + this.configured = configured; + } + + String configured() { + return configured; + } + } +} \ No newline at end of file diff --git a/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestConfigSources.java b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestConfigSources.java new file mode 100644 index 00000000000..07830e79024 --- /dev/null +++ b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestConfigSources.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.tests.junit5; + +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@HelidonTest +@Configuration(configSources = "testConfigSources.properties") +public class TestConfigSources { + @Inject + @ConfigProperty(name = "some.key") + private String someKey; + + @Test + void testValue() { + assertThat(someKey, is("some.value")); + } +} diff --git a/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestConstructorInjection.java b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestConstructorInjection.java new file mode 100644 index 00000000000..095c81ff54e --- /dev/null +++ b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestConstructorInjection.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.tests.junit5; + +import javax.enterprise.inject.Produces; +import javax.inject.Inject; +import javax.inject.Named; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Test when discovery is disabled. + */ +@HelidonTest +@DisableDiscovery +@AddBean(TestConstructorInjection.MyBean.class) +public class TestConstructorInjection { + private final int currentPort; + + @Inject + public TestConstructorInjection(@Named("port") int currentPort) { + this.currentPort = currentPort; + } + + @Test + void testIt() { + assertThat(currentPort, is(423)); + } + + public static class MyBean { + @Produces + @Named("port") + public int currentPort() { + return 423; + } + } +} \ No newline at end of file diff --git a/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestCustomConfig.java b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestCustomConfig.java new file mode 100644 index 00000000000..f25632d4eb2 --- /dev/null +++ b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestCustomConfig.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.tests.junit5; + +import javax.enterprise.inject.spi.BeanManager; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.Test; + +import static io.helidon.microprofile.tests.junit5.TestCustomConfig.PROPERTY_VALUE; +import static io.helidon.microprofile.tests.junit5.TestDefaults.PROPERTY_NAME; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +@HelidonTest +@AddConfig(key = PROPERTY_NAME, value = PROPERTY_VALUE) +@AddConfig(key = "second-key", value = "test-custom-config-second-value") +class TestCustomConfig { + static final String PROPERTY_VALUE = "test-custom-config-value"; + + @Inject + private BeanManager beanManager; + + @Inject + @ConfigProperty(name = PROPERTY_NAME, defaultValue = PROPERTY_VALUE) + private String shouldExist; + + @Inject + @ConfigProperty(name = "second-key", defaultValue = PROPERTY_VALUE) + private String anotherValue; + + @Test + void testIt() { + assertThat(beanManager, notNullValue()); + assertThat(shouldExist, is(PROPERTY_VALUE)); + assertThat(anotherValue, is("test-custom-config-second-value")); + } +} \ No newline at end of file diff --git a/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestCustomExtension.java b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestCustomExtension.java new file mode 100644 index 00000000000..b80f894c47c --- /dev/null +++ b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestCustomExtension.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.tests.junit5; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Initialized; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.Extension; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@HelidonTest +@AddExtension(TestCustomExtension.MyExtension.class) +class TestCustomExtension { + @Test + void testIt() { + assertThat("Extension should have been called, as it observes application scope", MyExtension.called, is(true)); + } + + public static class MyExtension implements Extension { + private static volatile boolean called = false; + + void observer(@Observes @Initialized(ApplicationScoped.class) final Object event) { + called = true; + } + } +} \ No newline at end of file diff --git a/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestDefaults.java b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestDefaults.java new file mode 100644 index 00000000000..68d71dda3be --- /dev/null +++ b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestDefaults.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.tests.junit5; + +import javax.enterprise.inject.spi.BeanManager; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +@HelidonTest +class TestDefaults { + static final String PROPERTY_NAME = "helidon-junit-extension-test-property"; + static final String DEFAULT_VALUE = "this-should-not-be-in-config"; + + @Inject + private BeanManager beanManager; + + @Inject + @ConfigProperty(name = PROPERTY_NAME, defaultValue = DEFAULT_VALUE) + private String shouldNotExist; + + private static boolean beforeAllCalled; + private boolean beforeEachCalled; + + @BeforeAll + static void initClass() { + beforeAllCalled = true; + } + + @BeforeEach + void beforeEach() { + beforeEachCalled = true; + } + + @Test + void testIt() { + assertThat(beanManager, notNullValue()); + assertThat(shouldNotExist, is(DEFAULT_VALUE)); + } + + @Test + void testLifecycleMethodsCalled() { + // this is to validate we can still use the usual junit methods + assertThat("Before all should have been called", beforeAllCalled, is(true)); + assertThat("Before each should have been called", beforeEachCalled, is(true)); + } +} \ No newline at end of file diff --git a/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestNoDiscovery.java b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestNoDiscovery.java new file mode 100644 index 00000000000..cdcc9fe7022 --- /dev/null +++ b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestNoDiscovery.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.tests.junit5; + +import javax.enterprise.inject.spi.CDI; +import javax.inject.Inject; + +import io.helidon.microprofile.config.ConfigCdiExtension; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Test when discovery is disabled. + */ +@HelidonTest +@DisableDiscovery +@AddBean(TestNoDiscovery.MyBean.class) +class TestNoDiscovery { + @Inject + private MyBean myBean; + + @Test + void testIt() { + assertThat(myBean, notNullValue()); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> CDI.current().getBeanManager() + .getExtension(ConfigCdiExtension.class), + "Config extension should not be loaded, as we have disabled discovery"); + + assertThat("Message should contain the extension name", + exception.getMessage(), + containsString(ConfigCdiExtension.class.getName())); + } + + public static class MyBean { + } +} \ No newline at end of file diff --git a/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestPerMethod.java b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestPerMethod.java new file mode 100644 index 00000000000..23a81744286 --- /dev/null +++ b/microprofile/tests/junit5/src/test/java/io/helidon/microprofile/tests/junit5/TestPerMethod.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.microprofile.tests.junit5; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Initialized; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.spi.CDI; +import javax.enterprise.inject.spi.Extension; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +@HelidonTest(resetPerTest = true) +class TestPerMethod { + private final static List ALL_CONTAINERS = new LinkedList<>(); + + @Test + void testWithParameter(SeContainer container) { + ALL_CONTAINERS.add(container); + } + + @Test + @DisableDiscovery + void testWithParameterNoDiscovery(SeContainer container) { + ALL_CONTAINERS.add(container); + } + + @Test + @AddConfig(key = "key-1", value = "value-1") + @AddBean(TestPerMethod.MyBean.class) + void testWithAdditionalConfig() { + String configured = CDI.current() + .select(MyBean.class) + .get() + .configured(); + + assertThat(configured, is("value-1")); + } + + @Test + @AddExtension(TestPerMethod.MyExtension.class) + void testCustomExtension() { + assertThat("Extension should have been called, as it observes application scope", MyExtension.called, is(true)); + } + + @AfterAll + static void validateContainerInstances() { + Set used = new HashSet<>(); + try { + for (SeContainer container : ALL_CONTAINERS) { + if (!used.add(System.identityHashCode(container))) { + fail("Container instance used twice: " + container); + } + } + } finally { + ALL_CONTAINERS.clear(); + } + } + + static class MyBean { + private final String configured; + + @Inject + MyBean(@ConfigProperty(name = "key-1") String configured) { + this.configured = configured; + } + + String configured() { + return configured; + } + } + + public static class MyExtension implements Extension { + private static volatile boolean called = false; + + void observer(@Observes @Initialized(ApplicationScoped.class) final Object event) { + called = true; + } + } +} diff --git a/microprofile/tests/junit5/src/test/resources/testConfigSources.properties b/microprofile/tests/junit5/src/test/resources/testConfigSources.properties new file mode 100644 index 00000000000..f89ba1dd864 --- /dev/null +++ b/microprofile/tests/junit5/src/test/resources/testConfigSources.properties @@ -0,0 +1,17 @@ +# +# Copyright (c) 2020 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +some.key=some.value diff --git a/microprofile/tests/pom.xml b/microprofile/tests/pom.xml index baba5a4cfae..3eecce3c038 100644 --- a/microprofile/tests/pom.xml +++ b/microprofile/tests/pom.xml @@ -40,6 +40,7 @@ arquillian + junit5