diff --git a/andhow-shared-test-utils/src/main/java/org/yarnandtail/andhow/testutil/AndHowTestUtils.java b/andhow-shared-test-utils/src/main/java/org/yarnandtail/andhow/testutil/AndHowTestUtils.java index 4ffa9709..bf974a0c 100644 --- a/andhow-shared-test-utils/src/main/java/org/yarnandtail/andhow/testutil/AndHowTestUtils.java +++ b/andhow-shared-test-utils/src/main/java/org/yarnandtail/andhow/testutil/AndHowTestUtils.java @@ -1,5 +1,6 @@ package org.yarnandtail.andhow.testutil; +import java.util.*; import java.util.function.UnaryOperator; /** @@ -115,7 +116,7 @@ public static Object getAndHowCore() { * This method is relatively safe for use in application testing and is used by the Junit * extensions and annotations to set configurations for individual tests. * - * Note: This method will fail is AndHow is uninitialized. + * Note: This method will fail if AndHow is uninitialized. ** * @param newCore The new core to assign to the AndHow singleton which may be null but must * be of type AndHowCore. @@ -163,17 +164,27 @@ public static T setAndHowInitialization(T newInit) { } /** - * Set a locator to find AndHowConfiguration. - * - * The locator is used in AndHow.findConfig(). If no config exists, the normal path is - * for AndHow to call {@code AndHowUtil.findConfiguration(c)}, however, if a locator - * is set to nonnull, it will be used instead. + * Specify the Locator used to find a AndHowConfiguration instance. + *

+ * This can be used for testing when you want to carefully control how AndHow + * is configured and do not want AndHow to auto-locate an AndHowConfiguration + * class from the classpath. + *

+ * The locator is used when the AndHow.findConfig() method is used to find + * AndHow's configuration. If no AndHowConfiguration instance has been + * discovered up to this point in the AndHow lifecycle (or if AndHow has been + * reset to it's unconfigured state for the purposes of a test), AndHow will + * use the Locator to find an AndHowConfiguration instance. If the Locator is + * null, a default classpath search is used. *

- * The locator takes a default Configuration to return if a configuration cannot - * be found otherwise. See org.yarnandtail.AndHow#findConfig() for details. + * The Locator is a UnaryOperator that takes a default AndHowConfiguration + * instance as a default instance to return if the locator cannot find an + * instance otherwise. The Locator can choose to ignore the default and + * simply return a hard-coded AndHowConfiguration instance (which is typically + * what happens for testing). *

* Example setting to a custom locator: - *

{@code setAndHowConfigLocator(c -> return MyConfig); }
+ *
{@code setAndHowConfigLocator( (c) -> new MyAndHowConfig() ) }
* Example setting back to null: *
{@code setAndHowConfigLocator(null); }
*

@@ -192,6 +203,18 @@ public static UnaryOperator setAndHowConfigLocator(UnaryOperator newLo getAndHowClass(), "configLocator", newLocator); } + public static List> setConfigurationOverrideGroups( + Object configurationInstance, List> classList) { + + return ReflectionTestUtils.setInstanceFieldValue( + configurationInstance, "overrideGroups", classList, List.class); + } + + public static List> setConfigurationOverrideGroups( + Object configurationInstance, Class clazz) { + return setConfigurationOverrideGroups(configurationInstance, Arrays.asList(clazz)); + } + /** * Forces the AndHow inProcessConfig to a new value. * @@ -274,5 +297,4 @@ private static Class getAndHowInitializationClass() { public static Class getAndHowConfigurationClass() { return ReflectionTestUtils.getClassByName("org.yarnandtail.andhow.AndHowConfiguration"); } - } diff --git a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestClassExt.java b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestClassExt.java index 6f0db62e..16b0c277 100644 --- a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestClassExt.java +++ b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestClassExt.java @@ -17,6 +17,11 @@ public class EnableJndiForThisTestClassExt extends ExtensionBase protected final static String KEY = "KEY"; + @Override + public ExtensionType getExtensionType() { + return ExtensionType.MODIFY_ENV_ALL_TESTS; + } + /** * Enable JNDI for use in an individual test method. * @@ -33,7 +38,7 @@ public void beforeAll(ExtensionContext context) throws Exception { // // Store sys props as they were before this method - getPerTestClassStore(context).put(KEY, System.getProperties().clone()); + getStore(context).put(KEY, System.getProperties().clone()); System.setProperty("java.naming.factory.initial", "org.osjava.sj.SimpleJndiContextFactory"); System.setProperty("org.osjava.sj.delimiter", "/"); @@ -60,7 +65,7 @@ public void afterAll(ExtensionContext context) throws Exception { // // Restore Sys Props - Properties p = getPerTestClassStore(context).remove(KEY, Properties.class); + Properties p = getStore(context).remove(KEY, Properties.class); System.setProperties(p); } diff --git a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestMethodExt.java b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestMethodExt.java index 566abaeb..43ef5336 100644 --- a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestMethodExt.java +++ b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestMethodExt.java @@ -17,6 +17,11 @@ public class EnableJndiForThisTestMethodExt extends ExtensionBase protected final static String KEY = "KEY"; + @Override + public ExtensionType getExtensionType() { + return ExtensionType.MODIFY_ENV_THIS_TEST; + } + /** * Enable JNDI for use in an individual test method. * @@ -35,7 +40,7 @@ public void beforeEach(ExtensionContext context) throws Exception { // // Store sys props as they were before this method - getPerTestMethodStore(context).put(KEY, System.getProperties().clone()); + getStore(context).put(KEY, System.getProperties().clone()); System.setProperty("java.naming.factory.initial", "org.osjava.sj.SimpleJndiContextFactory"); System.setProperty("org.osjava.sj.delimiter", "/"); @@ -63,7 +68,7 @@ public void afterEach(ExtensionContext context) throws Exception { // // Restore Sys Props - Properties p = getPerTestMethodStore(context).remove(KEY, Properties.class); + Properties p = getStore(context).remove(KEY, Properties.class); System.setProperties(p); } diff --git a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ExtensionBase.java b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ExtensionBase.java index 218d47a9..04d98c3d 100644 --- a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ExtensionBase.java +++ b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ExtensionBase.java @@ -1,8 +1,41 @@ package org.yarnandtail.andhow.junit5.ext; import org.junit.jupiter.api.extension.*; +import static org.yarnandtail.andhow.junit5.ext.ExtensionType.Storage.*; -public class ExtensionBase { +public abstract class ExtensionBase { + + /** + * The ExtensionType that describes the basic behaviors and attributes of the extension. + * + * @return + */ + protected abstract ExtensionType getExtensionType(); + + + /** + * Get the appropriate Store based on the getExtensionType() value of the Extension. + * + * @param context + * @return + * @throws IllegalStateException If the subclass's getExtensionType() returns a value that has + * a storage type of Storage.MIXTURE. Those implementations must determine the appropriate + * storage themselves. + */ + protected ExtensionContext.Store getStore(ExtensionContext context) { + ExtensionType type = getExtensionType(); + + switch (type.getStorage()) { + case TEST_INSTANCE: + return getPerTestClassStore(context); + case TEST_METHOD: + return getPerTestMethodStore(context); + default: + throw new IllegalStateException("Cannot call getStore() if the getExtensionType() returns " + + "a type that doesn't use TEST_INSTANCE or TEST_METHOD storage."); + } + + } /** * Create or return a unique storage space, which is unique per the test class. @@ -13,14 +46,23 @@ public class ExtensionBase { *

  • The test class instance that is invoking this extension
  • * * - * This method should not be called for storate + retrieval related to a test method, since + * This method should not be called for storage + retrieval related to a test method, since * it will not be unique enough (other methods could overwrite its value). * * @param context The ExtensionContext passed in to one of the callback methods. * @return The store that can be used to store and retrieve values. */ protected ExtensionContext.Store getPerTestClassStore(ExtensionContext context) { - return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestClass())); + return context.getStore(getPerTestNamespace(context)); + } + + /** + * This implementation is currently wrong - should be based on test instance. + * @param context + * @return + */ + protected ExtensionContext.Namespace getPerTestNamespace(ExtensionContext context) { + return ExtensionContext.Namespace.create(getClass(), context.getRequiredTestClass()); } /** @@ -41,6 +83,10 @@ protected ExtensionContext.Store getPerTestClassStore(ExtensionContext context) * @return The store that can be used to store and retrieve values. */ protected ExtensionContext.Store getPerTestMethodStore(ExtensionContext context) { - return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestInstance(), context.getRequiredTestMethod())); + return context.getStore(getPerTestMethodNamespace(context)); + } + + protected ExtensionContext.Namespace getPerTestMethodNamespace(ExtensionContext context) { + return ExtensionContext.Namespace.create(getClass(), context.getRequiredTestInstance(), context.getRequiredTestMethod()); } } diff --git a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ExtensionType.java b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ExtensionType.java new file mode 100644 index 00000000..24159635 --- /dev/null +++ b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ExtensionType.java @@ -0,0 +1,102 @@ +package org.yarnandtail.andhow.junit5.ext; + +/** + * Rich enum to describe a JUnit extension, including key properties for how the extension + * interacts with runtime storage and how extensions interact with each other. + */ +public enum ExtensionType { + CONFIG_EACH_TEST(Storage.TEST_METHOD, Scope.EACH_TEST, Effect.CONFIGURE), + CONFIG_ALL_TESTS(Storage.TEST_INSTANCE, Scope.TEST_CLASS, Effect.CONFIGURE), + CONFIG_THIS_TEST(Storage.TEST_METHOD, Scope.SINGLE_TEST, Effect.CONFIGURE), + KILL_EACH_TEST(Storage.TEST_METHOD, Scope.EACH_TEST, Effect.KILL), + KILL_ALL_TESTS(Storage.TEST_INSTANCE, Scope.TEST_CLASS, Effect.KILL), + KILL_THIS_TEST(Storage.TEST_METHOD, Scope.SINGLE_TEST, Effect.KILL), + MODIFY_ENV_EACH_TEST(Storage.TEST_METHOD, Scope.EACH_TEST, Effect.ENVIRONMENT), + MODIFY_ENV_ALL_TESTS(Storage.TEST_INSTANCE, Scope.TEST_CLASS, Effect.ENVIRONMENT), + MODIFY_ENV_THIS_TEST(Storage.TEST_METHOD, Scope.SINGLE_TEST, Effect.ENVIRONMENT), + OTHER_EACH_TEST(Storage.TEST_METHOD, Scope.EACH_TEST, Effect.OTHER), + OTHER_ALL_TESTS(Storage.TEST_INSTANCE, Scope.TEST_CLASS, Effect.OTHER), + OTHER_THIS_TEST(Storage.TEST_METHOD, Scope.SINGLE_TEST, Effect.OTHER), + OTHER(Storage.MIXTURE, Scope.MIXTURE, Effect.OTHER), + ; + + private final Storage _storage; + private final Scope _scope; + private final Effect _effect; + + private ExtensionType(final Storage storage, final Scope scope, final Effect effect) { + _storage = storage; + _scope = scope; + _effect = effect; + } + + public Storage getStorage() { + return _storage; + } + + public Scope getScope() { + return _scope; + } + + public Effect getEffect() { + return _effect; + } + + static enum Storage { + /** State should be stored in the context of the test instance */ + TEST_INSTANCE, + /** State should be stored in the context of the test method */ + TEST_METHOD, + /** Custom storage / mixed usage - No one correct answer */ + MIXTURE + } + + /** + * The level at which this extension (and any associated annotation) operate at. + *

    + * Some extensions operate at the test class level, where they use beforeAll/afterAll + * events, others at the test method level w/ beforeEach/afterEach events. + */ + static enum Scope { + /** Uses BeforeAll and AfterAll class-level events. Associated annotation is on the class. */ + TEST_CLASS(true, false), + /** Uses BeforeEach and AfterEach events applied to all tests in the class. + * Associated annotation is on the class. */ + EACH_TEST(true, false), + /** Uses BeforeEach and AfterEach events applied to a single test method. + * Associated annotation is on the method. */ + SINGLE_TEST(false, true), + /** Uses a mixture of BeforeAll BeforeEach, etc.. Annotations may be on the class or method */ + MIXTURE(true, true); + + private final boolean _classAnnotation; + private final boolean _methodAnnotation; + + Scope(final boolean classAnnotation, final boolean methodAnnotation) { + _classAnnotation = classAnnotation; + _methodAnnotation = methodAnnotation; + } + + public boolean isClassAnnotation() { + return _classAnnotation; + } + + public boolean isMethodAnnotation() { + return _methodAnnotation; + } + } + + /** + * The effect of the extension, within the AndHow world. + */ + static enum Effect { + /** Configures AndHow */ + CONFIGURE, + /** Kills the AndHow configured state */ + KILL, + /** Affects the environment that AndHow could use for configuration */ + ENVIRONMENT, + /** Other effect that does not interact with AndHow */ + OTHER + } +} diff --git a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterEachTestExt.java b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterEachTestExt.java index af8084ab..1d9ccd8a 100644 --- a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterEachTestExt.java +++ b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterEachTestExt.java @@ -15,6 +15,11 @@ public class RestoreSysPropsAfterEachTestExt extends ExtensionBase protected final static String KEY = "KEY"; + @Override + public ExtensionType getExtensionType() { + return ExtensionType.OTHER; + } + /** * Store the original Sys Props prior to any testing. * diff --git a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterThisTestExt.java b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterThisTestExt.java index 8075b5b0..7a76dedf 100644 --- a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterThisTestExt.java +++ b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterThisTestExt.java @@ -14,6 +14,11 @@ public class RestoreSysPropsAfterThisTestExt extends ExtensionBase protected final static String KEY = "KEY"; + @Override + public ExtensionType getExtensionType() { + return ExtensionType.OTHER_THIS_TEST; + } + /** * Store the Sys Props as they were prior to this test. * @@ -25,7 +30,7 @@ public class RestoreSysPropsAfterThisTestExt extends ExtensionBase */ @Override public void beforeEach(ExtensionContext context) throws Exception { - getPerTestMethodStore(context).put(KEY, System.getProperties().clone()); + getStore(context).put(KEY, System.getProperties().clone()); } /** @@ -39,7 +44,7 @@ public void beforeEach(ExtensionContext context) throws Exception { */ @Override public void afterEach(ExtensionContext context) throws Exception { - Properties p = getPerTestMethodStore(context).remove(KEY, Properties.class); + Properties p = getStore(context).remove(KEY, Properties.class); System.setProperties(p); } diff --git a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestClassExtTest.java b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestClassExtTest.java new file mode 100644 index 00000000..6f79db77 --- /dev/null +++ b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestClassExtTest.java @@ -0,0 +1,18 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class EnableJndiForThisTestClassExtTest { + + @Test + void getExtensionType() { + EnableJndiForThisTestClassExt ext = new EnableJndiForThisTestClassExt(); + ExtensionType type = ext.getExtensionType(); + + assertEquals(ExtensionType.Storage.TEST_INSTANCE, type.getStorage()); + assertEquals(ExtensionType.Effect.ENVIRONMENT, type.getEffect()); + assertEquals(ExtensionType.Scope.TEST_CLASS, type.getScope()); + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestMethodExtTest.java b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestMethodExtTest.java new file mode 100644 index 00000000..fc6f60d9 --- /dev/null +++ b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/EnableJndiForThisTestMethodExtTest.java @@ -0,0 +1,18 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class EnableJndiForThisTestMethodExtTest { + + @Test + void getExtensionType() { + EnableJndiForThisTestMethodExt ext = new EnableJndiForThisTestMethodExt(); + ExtensionType type = ext.getExtensionType(); + + assertEquals(ExtensionType.Storage.TEST_METHOD, type.getStorage()); + assertEquals(ExtensionType.Effect.ENVIRONMENT, type.getEffect()); + assertEquals(ExtensionType.Scope.SINGLE_TEST, type.getScope()); + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ExtensionBaseTest.java b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ExtensionBaseTest.java new file mode 100644 index 00000000..a458d55e --- /dev/null +++ b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ExtensionBaseTest.java @@ -0,0 +1,162 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.*; + +class ExtensionBaseTest { + + ExtensionBase extBase; + + ExtensionContext.Store store; + + //The context object that is passed to the test extension + ExtensionContext extensionContext; + + ExtensionContext.Namespace testInstanceNamespace; + + ExtensionContext.Namespace testMethodNamespace; + + @BeforeEach + public void setUp() throws NoSuchMethodException { + + testInstanceNamespace = ExtensionContext.Namespace.create( + ExtensionBaseOTHERType.class, this.getClass() + ); + + testMethodNamespace = ExtensionContext.Namespace.create( + ExtensionBaseOTHERType.class, this, this.getClass().getMethod("setUp", null) + ); + + // + // Setup mockito for the test + + extBase = new ExtensionBaseOTHERType(); + + store = Mockito.mock(ExtensionContext.Store.class); + + extensionContext = Mockito.mock(ExtensionContext.class); + Mockito.when(extensionContext.getRequiredTestClass()).thenReturn((Class)(this.getClass())); + Mockito.when(extensionContext.getRequiredTestInstance()).thenReturn(this); + Mockito.when(extensionContext.getRequiredTestMethod()).thenReturn(this.getClass().getMethod("setUp", null)); + Mockito.when(extensionContext.getStore(ArgumentMatchers.any())).thenReturn(store); + } + + @Test + void getPerTestClassStore() { + ExtensionContext.Store myStore = extBase.getPerTestClassStore(extensionContext); + assertNotNull(myStore); + Mockito.verify(extensionContext).getStore(ArgumentMatchers.eq(testInstanceNamespace)); + } + + /** + * This test is VERIFYING THE WRONG BEHAVIOR!! + * There is a separate ticket to fix it: https://github.com/eeverman/andhow/issues/744 + */ + @Test + void getPerTestNamespace() { + ExtensionContext.Namespace sut = extBase.getPerTestNamespace(extensionContext); + + assertEquals(testInstanceNamespace, sut); + } + + @Test + void getPerTestMethodStore() { + ExtensionContext.Store myStore = extBase.getPerTestMethodStore(extensionContext); + assertNotNull(myStore); + Mockito.verify(extensionContext).getStore(ArgumentMatchers.eq(testMethodNamespace)); + } + + @Test + void getPerTestMethodNamespace() throws NoSuchMethodException { + ExtensionContext.Namespace sut = extBase.getPerTestMethodNamespace(extensionContext); + + assertEquals(testMethodNamespace, sut); + } + + @Test + void getStoreForOTHERTypeShouldThrowException() { + extBase = new ExtensionBaseOTHERType(); + + assertThrows(IllegalStateException.class, () -> extBase.getStore(extensionContext)); + } + + @Test + void getStoreForALLTypeShouldUseClassLevelStorage() { + + testInstanceNamespace = ExtensionContext.Namespace.create( + ExtensionBaseALLType.class, this.getClass() + ); + + extBase = new ExtensionBaseALLType(); + ExtensionContext.Store myStore = extBase.getStore(extensionContext); + assertNotNull(myStore); + Mockito.verify(extensionContext).getStore(ArgumentMatchers.eq(testInstanceNamespace)); + } + + @Test + void getStoreForEACHTypeShouldUseMethodLevelStorage() throws NoSuchMethodException { + + testMethodNamespace = ExtensionContext.Namespace.create( + ExtensionBaseEACHType.class, this, this.getClass().getMethod("setUp", null) + ); + + extBase = new ExtensionBaseEACHType(); + ExtensionContext.Store myStore = extBase.getStore(extensionContext); + assertNotNull(myStore); + Mockito.verify(extensionContext).getStore(ArgumentMatchers.eq(testMethodNamespace)); + } + + @Test + void getStoreForTHISTypeShouldUseClassLevelStorage() throws NoSuchMethodException { + + testMethodNamespace = ExtensionContext.Namespace.create( + ExtensionBaseTHISType.class, this, this.getClass().getMethod("setUp", null) + ); + + extBase = new ExtensionBaseTHISType(); + ExtensionContext.Store myStore = extBase.getStore(extensionContext); + assertNotNull(myStore); + Mockito.verify(extensionContext).getStore(ArgumentMatchers.eq(testMethodNamespace)); + } + + // Simple implementation to create a real subclass of ExtensionBase. + // It returns an 'OTHER' type, so the getStore() method will throw an Exception + static class ExtensionBaseOTHERType extends ExtensionBase { + @Override + public ExtensionType getExtensionType() { + return ExtensionType.OTHER; + } + } + + // Simple implementation to create a real subclass of ExtensionBase. + // It returns an 'ALL' type, so getStore() should return test-level storage + static class ExtensionBaseALLType extends ExtensionBase { + @Override + public ExtensionType getExtensionType() { + return ExtensionType.OTHER_ALL_TESTS; + } + } + + // Simple implementation to create a real subclass of ExtensionBase. + // It returns a 'THIS' type, so getStore() should return method-level storage + static class ExtensionBaseEACHType extends ExtensionBase { + @Override + public ExtensionType getExtensionType() { + return ExtensionType.OTHER_EACH_TEST; + } + } + + // Simple implementation to create a real subclass of ExtensionBase. + // It returns a 'THIS' type, so getStore() should return method-level storage + static class ExtensionBaseTHISType extends ExtensionBase { + @Override + public ExtensionType getExtensionType() { + return ExtensionType.OTHER_THIS_TEST; + } + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterEachTestExtTest.java b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterEachTestExtTest.java new file mode 100644 index 00000000..aa118cb3 --- /dev/null +++ b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterEachTestExtTest.java @@ -0,0 +1,18 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RestoreSysPropsAfterEachTestExtTest { + + @Test + void getExtensionType() { + RestoreSysPropsAfterEachTestExt ext = new RestoreSysPropsAfterEachTestExt(); + ExtensionType type = ext.getExtensionType(); + + assertEquals(ExtensionType.Storage.MIXTURE, type.getStorage()); + assertEquals(ExtensionType.Effect.OTHER, type.getEffect()); + assertEquals(ExtensionType.Scope.MIXTURE, type.getScope()); + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterThisTestExtTest.java b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterThisTestExtTest.java new file mode 100644 index 00000000..5e3b1d03 --- /dev/null +++ b/junit5-extensions/junit5-extensions-no-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/RestoreSysPropsAfterThisTestExtTest.java @@ -0,0 +1,18 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RestoreSysPropsAfterThisTestExtTest { + + @Test + void getExtensionType() { + RestoreSysPropsAfterThisTestExt ext = new RestoreSysPropsAfterThisTestExt(); + ExtensionType type = ext.getExtensionType(); + + assertEquals(ExtensionType.Storage.TEST_METHOD, type.getStorage()); + assertEquals(ExtensionType.Effect.OTHER, type.getEffect()); + assertEquals(ExtensionType.Scope.SINGLE_TEST, type.getScope()); + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/pom.xml b/junit5-extensions/junit5-extensions-with-andhow-dependency/pom.xml index 87e47bbd..6548d791 100644 --- a/junit5-extensions/junit5-extensions-with-andhow-dependency/pom.xml +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/pom.xml @@ -28,17 +28,26 @@ NOTE: Any dependency added to this list that would be needed by a user of the module must also be added as a dep' to andhow-junit5-extensions, which bundles this into a jar. --> + + org.yarnandtail + andhow-core + ${project.version} + true + ${project.groupId} andhow-shared-test-utils ${project.version} - compile + + + ${project.groupId} + andhow-test-stubs + ${project.groupId} junit5-extensions-no-andhow-dependency ${project.version} - compile true @@ -66,14 +75,6 @@ logback-classic true - - - - org.yarnandtail - andhow-core - ${project.version} - test - diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ConfigFromFileBeforeAllTests.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ConfigFromFileBeforeAllTests.java new file mode 100644 index 00000000..f3217e57 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ConfigFromFileBeforeAllTests.java @@ -0,0 +1,64 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.yarnandtail.andhow.junit5.ext.ConfigFromFileBeforeAllTestsExt; + +import java.lang.annotation.*; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({ TYPE, ANNOTATION_TYPE }) +@Retention(RUNTIME) +@Inherited +@ExtendWith(ConfigFromFileBeforeAllTestsExt.class) +public @interface ConfigFromFileBeforeAllTests { + + /** + * The path to an AndHow configuration properties file on the classpath. + *

    + * The path follows standard Java classpath syntax and can be absolute or relative. + * If the path starts with a '{@code / }', it is interpreted as an absolute classpath. + * If the path does not start with a '{@code / }', it is interpreted as relative to the current + * test class. If the file name has a 'dot', e.g. {@code myFile.props}, use slashes to separate + * parts of the path. Some examples, all assuming the test is in the {@code org.people} package: + *

    + *

    + * If specifying only the configuration properties file, the shortened annotation syntax can be used:
    + * {@code @ConfigFromFileBeforeAllTests("myFile.props")}
    + * Otherwise the full syntax must be used:
    + * {@code @ConfigFromFileBeforeAllTests(value = "myFile.props"), includeClasses = {Conf1.class, Conf2.class}}
    + *

    + * @return A string containing the path to a properties file. + */ + String value(); + + /** + * Optional array of classes that AndHow will scan for AndHow Properties. + *

    + * If testing a single class or subset of classes that use AndHow Properties within a larger + * project, you can limit the scope of which Properties AndHow will 'see' to just those needed + * for the test. All Properties in the listed classes and nested innerclasses will be scanned + * for AndHow Properties, but no others. This reduces the size of the properties files + * required for configuration and limits the effects of refactors on Property names. + *

    + * If unspecified, all AndHow configuration properties on the classpath + * will be discovered, configured and validated. Thus, any configuration Properties that + * are required to be non-null will need to have values provided via the configured properties + * file or in some other way. + *

    + * Typical usage looks like this:
    + * {@code @ConfigFromFileBeforeAllTests(value = "myFile.props"), includeClasses = {Conf1.class, Conf2.class}} + *

    + * @return An array of classes that should be scanned (along with their nested innerclasses) for + * AndHow Properties. + */ + Class[] includeClasses() default {}; +} diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ConfigFromFileBeforeEachTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ConfigFromFileBeforeEachTest.java new file mode 100644 index 00000000..792542d4 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ConfigFromFileBeforeEachTest.java @@ -0,0 +1,64 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.yarnandtail.andhow.junit5.ext.ConfigFromFileBeforeEachTestExt; + +import java.lang.annotation.*; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({ TYPE, ANNOTATION_TYPE }) +@Retention(RUNTIME) +@Inherited +@ExtendWith(ConfigFromFileBeforeEachTestExt.class) +public @interface ConfigFromFileBeforeEachTest { + + /** + * The path to an AndHow configuration properties file on the classpath. + *

    + * The path follows standard Java classpath syntax and can be absolute or relative. + * If the path starts with a '{@code / }', it is interpreted as an absolute classpath. + * If the path does not start with a '{@code / }', it is interpreted as relative to the current + * test class. If the file name has a 'dot', e.g. {@code myFile.props}, use slashes to separate + * parts of the path. Some examples, all assuming the test is in the {@code org.people} package: + *

    + *

    + * If specifying only the configuration properties file, the shortened annotation syntax can be used:
    + * {@code @ConfigFromFileBeforeAllTests("myFile.props")}
    + * Otherwise the full syntax must be used:
    + * {@code @ConfigFromFileBeforeAllTests(value = "myFile.props"), includeClasses = {Conf1.class, Conf2.class}}
    + *

    + * @return A string containing the path to a properties file. + */ + String value(); + + /** + * Optional array of classes that AndHow will scan for AndHow Properties. + *

    + * If testing a single class or subset of classes that use AndHow Properties within a larger + * project, you can limit the scope of which Properties AndHow will 'see' to just those needed + * for the test. All Properties in the listed classes and nested innerclasses will be scanned + * for AndHow Properties, but no others. This reduces the size of the properties files + * required for configuration and limits the effects of refactors on Property names. + *

    + * If unspecified, all AndHow configuration properties on the classpath + * will be discovered, configured and validated. Thus, any configuration Properties that + * are required to be non-null will need to have values provided via the configured properties + * file or in some other way. + *

    + * Typical usage looks like this:
    + * {@code @ConfigFromFileBeforeEachTest(value = "myFile.props"), includeClasses = {Conf1.class, Conf2.class}} + *

    + * @return An array of classes that should be scanned (along with their nested innerclasses) for + * AndHow Properties. + */ + Class[] includeClasses() default {}; +} diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ConfigFromFileBeforeThisTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ConfigFromFileBeforeThisTest.java new file mode 100644 index 00000000..1cc56252 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ConfigFromFileBeforeThisTest.java @@ -0,0 +1,64 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.yarnandtail.andhow.junit5.ext.ConfigFromFileBeforeThisTestExt; + +import java.lang.annotation.*; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({ METHOD, ANNOTATION_TYPE }) +@Retention(RUNTIME) +@Inherited +@ExtendWith(ConfigFromFileBeforeThisTestExt.class) +public @interface ConfigFromFileBeforeThisTest { + + /** + * The path to an AndHow configuration properties file on the classpath. + *

    + * The path follows standard Java classpath syntax and can be absolute or relative. + * If the path starts with a '{@code / }', it is interpreted as an absolute classpath. + * If the path does not start with a '{@code / }', it is interpreted as relative to the current + * test class. If the file name has a 'dot', e.g. {@code myFile.props}, use slashes to separate + * parts of the path. Some examples, all assuming the test is in the {@code org.people} package: + *

    + *

    + * If specifying only the configuration properties file, the shortened annotation syntax can be used:
    + * {@code @ConfigFromFileBeforeAllTests("myFile.props")}
    + * Otherwise the full syntax must be used:
    + * {@code @ConfigFromFileBeforeAllTests(value = "myFile.props"), includeClasses = {Conf1.class, Conf2.class}}
    + *

    + * @return A string containing the path to a properties file. + */ + String value(); + + /** + * Optional array of classes that AndHow will scan for AndHow Properties. + *

    + * If testing a single class or subset of classes that use AndHow Properties within a larger + * project, you can limit the scope of which Properties AndHow will 'see' to just those needed + * for the test. All Properties in the listed classes and nested innerclasses will be scanned + * for AndHow Properties, but no others. This reduces the size of the properties files + * required for configuration and limits the effects of refactors on Property names. + *

    + * If unspecified, all AndHow configuration properties on the classpath + * will be discovered, configured and validated. Thus, any configuration Properties that + * are required to be non-null will need to have values provided via the configured properties + * file or in some other way. + *

    + * Typical usage looks like this:
    + * {@code @ConfigFromFileBeforeThisTest(value = "myFile.props"), includeClasses = {Conf1.class, Conf2.class}} + *

    + * @return An array of classes that should be scanned (along with their nested innerclasses) for + * AndHow Properties. + */ + Class[] includeClasses() default {}; +} diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTests.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTests.java index 313d21db..9aa2513b 100644 --- a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTests.java +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTests.java @@ -3,8 +3,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.yarnandtail.andhow.junit5.ext.KillAndHowBeforeAllTestsExt; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; +import java.lang.annotation.*; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.TYPE; @@ -46,6 +45,7 @@ */ @Target({ TYPE, ANNOTATION_TYPE }) @Retention(RUNTIME) +@Inherited @ExtendWith(KillAndHowBeforeAllTestsExt.class) public @interface KillAndHowBeforeAllTests { diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTest.java index 20988b98..a9ca5ea3 100644 --- a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTest.java +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTest.java @@ -3,8 +3,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.yarnandtail.andhow.junit5.ext.KillAndHowBeforeEachTestExt; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; +import java.lang.annotation.*; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.TYPE; @@ -47,6 +46,7 @@ */ @Target({ TYPE, ANNOTATION_TYPE }) @Retention(RUNTIME) +@Inherited @ExtendWith(KillAndHowBeforeEachTestExt.class) public @interface KillAndHowBeforeEachTest { diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTest.java index 7c768a65..72a3926e 100644 --- a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTest.java +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTest.java @@ -3,8 +3,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.yarnandtail.andhow.junit5.ext.KillAndHowBeforeThisTestExt; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; +import java.lang.annotation.*; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.METHOD; @@ -43,6 +42,7 @@ */ @Target({ METHOD, ANNOTATION_TYPE }) @Retention(RUNTIME) +@Inherited @ExtendWith(KillAndHowBeforeThisTestExt.class) public @interface KillAndHowBeforeThisTest { diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBaseExt.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBaseExt.java new file mode 100644 index 00000000..ea955817 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBaseExt.java @@ -0,0 +1,295 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.extension.*; +import org.yarnandtail.andhow.*; +import org.yarnandtail.andhow.api.StandardLoader; +import org.yarnandtail.andhow.load.std.*; +import org.yarnandtail.andhow.testutil.AndHowTestUtils; +import sun.reflect.annotation.AnnotationType; + +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.util.*; + +/** + * Implementation of an ExtensionBase that configures AndHow from a single properties + * file. It is the base class to be used in one of three modes: + *

  • BeforeAll/AfterAll Registered on a test class
  • + *
  • BeforeEach/AfterEach Registered on a test class to apply to all methods
  • + *
  • BeforeEach/AfterEach Registered on a test method to apply to a single test
  • + * Mixing purposes (i.e. a single instance registered for use for more than one mode) + * can result in confused state. + * + * Note that JUnit has an unexpected handling of nested test classes when the parent class and the + * nested test class have the same annotation: JUnit creates only a single instance of an Extension. + * Thus, no state can be kept in the extension other than via the context store mechanism, or, for + * config info, read configuration from annotation parameters EACH TIME. Configuration parameters + * cannot be cached (other than on the context store) because the cached values will bleed over from + * the parent class to the child class. For instance, if the parent class is annotated with: + * @ConfigFromFileBeforeAllTests(filePath = "parent.properties") + * and the nested class is annotated with: + * @ConfigFromFileBeforeAllTests(filePath = "child.properties") + * storing the config value of the parent in the extension means that the cached value will be found + * and used when the child is configured. Read from the annotation each time! + */ +public abstract class ConfigFromFileBaseExt extends ExtensionBase { + + /** Key to store the AndHowCore (if any) of AndHow. */ + protected static final String CORE_KEY = "core_key"; + + /** Key to store the in-process configuration (if any) of the AndHow instance. + * When is the inProcessConfig non-null for AndHow? Only when findConfig() + * has been called but AndHow has not yet initialized. + */ + protected static final String CONFIG_KEY = "config_key"; + + /** The complete path to a properties file on the classpath */ + private String _classpathFile; + + /** + * This Optional has unique usage: + * null: (Optional not initialized) means that this class was constructed via + * the default constructor, which likely means it is being used via an + * annotation. The value for this config param should be discovered at runtime + * from the annotation. + * Optional.isPresent() == false: The value was set to empty in the java + * constructor. This class was constructed w/ a standard constructor that + * spec'ed this value as empty or null. DON'T TRY TO FIND A VALUE IN AN + * ANNOTATION - THERE ISN'T ONE. + * Optional.isPresent() == true: This class was constructed w/ a standard + * constructor that spec'ed this value. DON'T TRY TO FIND A VALUE IN AN + * ANNOTATION - THERE ISN'T ONE. + */ + private Optional[]> _classesInScope; + + /** The constructed config instance to be used for AndHow */ + protected AndHowConfiguration _config; + + /** + * New instance - validation of the classpathFile is deferred until use. + * @param classpathFile Complete path to a properties file on the classpath + */ + public ConfigFromFileBaseExt(String classpathFile) { + if (classpathFile == null) { + throw new IllegalArgumentException("The classpath properties file path cannot be null."); + } + _classpathFile = classpathFile; + _classesInScope = Optional.empty(); + } + + public ConfigFromFileBaseExt(String classpathFile, Class[] configClasses) { + if (classpathFile == null) { + throw new IllegalArgumentException("The classpath properties file path cannot be null."); + } + + _classpathFile = classpathFile; + _classesInScope = Optional.of(configClasses); + } + + /** + * Empty constructor used when the \@ConfigFromFile annotation is used. + * + * When the empty construct is used, the classpathFile and classesInScope are + * found via the \@ConfigFromFile annotation filePath property. + *

    + * This constructor cannot be used to create an instance of this class + * other than via the annotation mechanism. When this constructor is + * used, the class assumes it will find its configuration in an annotation, + * which will not be present other than within the context of a JUnit test. + */ + public ConfigFromFileBaseExt() { } + + /** + * When this Extension is being used in association w/ an annotation, this returns + * the filePath from the annotation. + * + * @param context + * @return + */ + protected String getFilePathFromAnnotation(ExtensionContext context) { + + Annotation a = ExtensionUtil.findAnnotation(getAssociatedAnnotation(), + this.getExtensionType().getScope(), context); + + Method method = getClasspathFileMethod(); + method.setAccessible(true); + try { + return (String)(method.invoke(a)); + } catch (Exception e) { + throw new RuntimeException("Unable to use reflection to access annotation values.", e); + } + } + + /** + * When this Extension is being used in association w/ an annotation, this returns + * the includedClasses from the annotation. + * + * @return + */ + protected Class[] getClassesInScopeFromAnnotation(ExtensionContext context) { + + Annotation a = ExtensionUtil.findAnnotation(getAssociatedAnnotation(), + this.getExtensionType().getScope(), context); + + Method method = getIncludeClassesMethod(); + method.setAccessible(true); + try { + return (Class[])(method.invoke(a)); + } catch (Exception e) { + throw new RuntimeException("Unable to use reflection to access annotation values.", e); + } + } + + /** + * The annotation associated with the implementation. + *

    + * See existing annotations w/ similar names for example, e.g. ConfigFromFileBeforeAllTests. + * The annotation must have two property methods: + *

    + * The actual names of the methods can be overridden by overriding getClasspathFileMethod() and/or + * getIncludeClassesMethod(). + * + * @return + */ + public abstract Class getAssociatedAnnotation(); + + public Method getClasspathFileMethod() { + return AnnotationType.getInstance(getAssociatedAnnotation()).members().get("value"); + } + + public Method getIncludeClassesMethod() { + return AnnotationType.getInstance(getAssociatedAnnotation()).members().get("includeClasses"); + } + + + // + //The beforeAll/beforeEach/afterAllAfter each can probably be consolidated into + //generic before and after at this point, since the storage choice is abstracted. + + + /** + * Configure AndHow for a unit test class or test method. + *

    + * The differences between class-level and method level is state storage. + * That is determined by the ExtensionType returned by getExtensionType(). + * This method does the following: + *

    + * + * @param context Passed by JUnit prior to any test in the class + * @throws Exception + */ + public void beforeAllOrEach(ExtensionContext context) throws Exception { + + // Store the old core and set the current core to null + getStore(context).put(CORE_KEY, AndHowTestUtils.setAndHowCore(null)); + + // New config instance created just as requested for testing + _config = buildConfig(context); + + // Remove current locator and replace w/ one that always returns a custom config + getStore(context).put(CONFIG_KEY, AndHowTestUtils.setAndHowInProcessConfig(_config)); + } + + /** + * Restore the state of AndHow to what it was before this test or this test class. + * @param context + * @throws Exception + */ + public void afterAllOrEach(ExtensionContext context) throws Exception { + Object core = getStore(context).remove(CORE_KEY, AndHowTestUtils.getAndHowCoreClass()); + AndHowTestUtils.setAndHowCore(core); + + AndHowConfiguration config = + getStore(context).remove(CONFIG_KEY, AndHowConfiguration.class); + AndHowTestUtils.setAndHowInProcessConfig(config); + } + + /** + * Find the user configured properties file path. + *

    + * If configured via an annotation, read the annotated value EVERY TIME, DO NOT CACHE THE VALUE. + * @param context + * @return + */ + protected String getClasspathFile(ExtensionContext context) { + if (_classpathFile == null) { + return getFilePathFromAnnotation(context); + } else { + return _classpathFile; + } + } + + /** + * Find the user configured classes in scope. + *

    + * If configured via an annotation, read the annotated value EVERY TIME, DO NOT CACHE THE VALUE. + * @param context + * @return A List that is never null but may be empty. + */ + protected List> getClassesInScope(ExtensionContext context) { + if (_classesInScope == null) { + //Not initialized, so this is annotation construction + return ExtensionUtil.stem(Arrays.asList(getClassesInScopeFromAnnotation(context))); + } else if (_classesInScope.isPresent()) { + return ExtensionUtil.stem(Arrays.asList(_classesInScope.get())); //Std java class construction was used + } else { + return Collections.emptyList(); + } + } + + /** + * Construct a new AndHowConfiguration instance created as needed for testing + * + * @param context + * @return + */ + protected AndHowConfiguration buildConfig(ExtensionContext context) { + + String fullPath = ExtensionUtil.expandPath(getClasspathFile(context), context); + if (! ExtensionUtil.isExistingResource(fullPath)) { + throw new IllegalArgumentException( + "The file '" + fullPath + "' could not be found on the classpath."); + } + List> clazzes = getClassesInScope(context); + + AndHowConfiguration config = new StdConfig.StdConfigImpl(); + removeEnvLoaders(config); + config.setClasspathPropFilePath(fullPath).classpathPropertiesRequired(); + + if (! clazzes.isEmpty()) { + AndHowTestUtils.setConfigurationOverrideGroups(config, clazzes); + } + + return config; + } + + /** + * Remove the Loaders that are 'environment' related, meaning they could be set + * outside the scope of a unit test. + *

    + * These loaders are removed to prevent environment from affecting a test. + * + * @param config The AndHowConfiguration to remove the loaders from. + */ + protected void removeEnvLoaders(AndHowConfiguration config) { + + List> loaders = config.getDefaultLoaderList(); + loaders.remove(StdSysPropLoader.class); + loaders.remove(StdEnvVarLoader.class); + loaders.remove(StdJndiLoader.class); + + config.setStandardLoaders(loaders).setStandardLoaders(loaders); + } + + +} + diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeAllTestsExt.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeAllTestsExt.java new file mode 100644 index 00000000..fc7cdf2c --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeAllTestsExt.java @@ -0,0 +1,48 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.extension.*; +import org.yarnandtail.andhow.junit5.ConfigFromFileBeforeAllTests; + +import java.lang.annotation.Annotation; + +public class ConfigFromFileBeforeAllTestsExt extends ConfigFromFileBaseExt + implements BeforeAllCallback, AfterAllCallback { + + /** + * Empty constructor used when the \@ConfigFromFileBeforeThisTest annotation is used. + * + * When the empty construct is used, the classpathFile is found via the + * \@ConfigFromFile annotation filePath property. + */ + public ConfigFromFileBeforeAllTestsExt() { + super(); + } + + /** + * New instance - validation of the classpathFile is deferred until use. + * @param classpathFile Complete path to a properties file on the classpath + */ + public ConfigFromFileBeforeAllTestsExt(String classpathFile) { + super(classpathFile); + } + + @Override + public ExtensionType getExtensionType() { + return ExtensionType.CONFIG_ALL_TESTS; + } + + @Override + public Class getAssociatedAnnotation() { + return ConfigFromFileBeforeAllTests.class; + } + + @Override + public void beforeAll(final ExtensionContext context) throws Exception { + super.beforeAllOrEach(context); + } + + @Override + public void afterAll(final ExtensionContext context) throws Exception { + super.afterAllOrEach(context); + } +} diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeEachTestExt.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeEachTestExt.java new file mode 100644 index 00000000..8682d2a4 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeEachTestExt.java @@ -0,0 +1,49 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.extension.*; +import org.yarnandtail.andhow.junit5.ConfigFromFileBeforeAllTests; +import org.yarnandtail.andhow.junit5.ConfigFromFileBeforeEachTest; + +import java.lang.annotation.Annotation; + +public class ConfigFromFileBeforeEachTestExt extends ConfigFromFileBaseExt + implements BeforeEachCallback, AfterEachCallback { + + /** + * Empty constructor used when the \@ConfigFromFileBeforeThisTest annotation is used. + * + * When the empty construct is used, the classpathFile is found via the + * \@ConfigFromFile annotation filePath property. + */ + public ConfigFromFileBeforeEachTestExt() { + super(); + } + + /** + * New instance - validation of the classpathFile is deferred until use. + * @param classpathFile Complete path to a properties file on the classpath + */ + public ConfigFromFileBeforeEachTestExt(String classpathFile) { + super(classpathFile); + } + + @Override + public ExtensionType getExtensionType() { + return ExtensionType.CONFIG_EACH_TEST; + } + + @Override + public Class getAssociatedAnnotation() { + return ConfigFromFileBeforeEachTest.class; + } + + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + super.beforeAllOrEach(context); + } + + @Override + public void afterEach(final ExtensionContext context) throws Exception { + super.afterAllOrEach(context); + } +} diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeThisTestExt.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeThisTestExt.java new file mode 100644 index 00000000..0de97915 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeThisTestExt.java @@ -0,0 +1,50 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.extension.*; +import org.yarnandtail.andhow.junit5.*; +import sun.reflect.annotation.AnnotationType; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class ConfigFromFileBeforeThisTestExt extends ConfigFromFileBaseExt + implements BeforeEachCallback, AfterEachCallback { + + /** + * Empty constructor used when the \@ConfigFromFileBeforeThisTest annotation is used. + * + * When the empty construct is used, the classpathFile is found via the + * \@ConfigFromFile annotation filePath property. + */ + public ConfigFromFileBeforeThisTestExt() { + super(); + } + + /** + * New instance - validation of the classpathFile is deferred until use. + * @param classpathFile Complete path to a properties file on the classpath + */ + public ConfigFromFileBeforeThisTestExt(String classpathFile) { + super(classpathFile); + } + + @Override + public ExtensionType getExtensionType() { + return ExtensionType.CONFIG_THIS_TEST; + } + + public Class getAssociatedAnnotation() { + return ConfigFromFileBeforeThisTest.class; + } + + @Override + public void beforeEach(final ExtensionContext context) throws Exception { + super.beforeAllOrEach(context); + } + + @Override + public void afterEach(final ExtensionContext context) throws Exception { + super.afterAllOrEach(context); + } +} diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ExtensionUtil.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ExtensionUtil.java new file mode 100644 index 00000000..646b75ee --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/ExtensionUtil.java @@ -0,0 +1,151 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; +import java.util.*; + +public class ExtensionUtil { + + private ExtensionUtil() { + /* NO OP - no instances */ + } + + + /** + * Copied from JUnit AnnotationUtils + * + * ref: AnnotationUtils.findAnnotation(Class clazz, Class annotationType, + * boolean searchEnclosingClasses) + * @param clazz + * @param annotationType + * @return + * @param + */ + public static Optional findAnnotation(Class clazz, Class annotationType) { + + Class candidate = clazz; + while (candidate != null) { + Optional annotation = AnnotationSupport.findAnnotation(candidate, annotationType); + if (annotation.isPresent()) { + return annotation; + } + candidate = (isInnerClass(candidate) ? candidate.getEnclosingClass() : null); + } + return Optional.empty(); + } + + /** + * Is this a non-static innerClass? + * + * @param clazz + * @return + */ + public static boolean isInnerClass(Class clazz) { + return !(Modifier.isStatic(clazz.getModifiers())) && clazz.isMemberClass(); + } + + /** + * Throws an exception if the passed classpath does not exist + * @param classpath + */ + public static boolean isExistingResource(String classpath) { + return ExtensionUtil.class.getResource(classpath) != null; + } + + /** + * Generate a comprehensive list of classes that includes inner class which might contain + * AndHow properties. + */ + public static List> stem(List> clazzes) { + final List> stemmed = new ArrayList<>(); + stemmed.addAll(clazzes); + + int index = 0; + + while (index < stemmed.size()) { + Class c = stemmed.get(index); + + Arrays.stream(c.getDeclaredClasses()).forEach( cc -> { + if (Modifier.isStatic(cc.getModifiers())) { + stemmed.add(cc); + } + }); + + index++; + } + + return stemmed; + } + + + public static A findAnnotation(Class annotationClass, + ExtensionType.Scope scope, ExtensionContext context) { + + if (scope.isClassAnnotation()) { + + //This method will hunt nested classes, and because the AndHow annotations are marked for + //inheritance, the superclass ones as well. + + //Note: Could use the JUnit AnnotationSupport.findAnnotation(Class, Class, SearchOption) + //method here, but its experimental and only added in recent versions of Junit. + Optional ann = ExtensionUtil.findAnnotation(context.getRequiredTestClass(), annotationClass); + + if (!ann.isPresent()) { + throw new IllegalStateException("Expected the @" + annotationClass.getName() + " annotation on the '" + + context.getRequiredTestClass() + "' class, superclass or a parent class for a @Nested test."); + } + + return ann.get(); + } else if (scope.isMethodAnnotation()) { + // Operating at the method level, so the annotation should be in the current test method + Optional ann = AnnotationSupport.findAnnotation(context.getElement(), annotationClass); + + if (!ann.isPresent()) { + throw new IllegalStateException("Expected the @" + annotationClass.getName() + " annotation on the '" + + context.getRequiredTestMethod().getName() + "' test method of " + context.getRequiredTestClass()); + } + return ann.get(); + } else { + throw new IllegalStateException("Cannot call findAnnotation() if the getExtensionType() returns " + + "a type that doesn't use TEST_CLASS, EACH_TEST or SINGLE_TEST scope."); + } + } + + /** + * Expand the passed classpath to be an absolute classpath if it was a relative one. + *

    + * A classpath is determined to be a relative path if it does not start with a slash. + * In that case, the package of the path is expanded to include the package of the test + * on which this extension is used. + * If the path contains a slash, but it does not start with a slash, it is an illegal argument. + * Other non-allowed characters in a single classpath are illegal, including spaces and commas. + * All paths are trimmed of whitespace before processing. + * Null paths will throw a NullPointer. + * @param classpath The classpath to the properties file the user wants to use to configure AndHow + * @param context The test context this extension is being run within + * @return + */ + public static String expandPath(String classpath, ExtensionContext context) { + String fullPath = classpath.trim(); + + if (! fullPath.startsWith("/")) { + + String pkgName = ""; //empty is correct for default pkg + + if (context.getRequiredTestClass().getPackage() != null) { + //getPackage() returns null for the default pkg + pkgName = context.getRequiredTestClass().getPackage().getName(); + } + + String pkgPath = pkgName.replace(".", "/"); + if (pkgPath.length() > 0) pkgPath = "/" + pkgPath; + + fullPath = pkgPath + "/" + fullPath; + } + + return fullPath; + } +} diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeAllTestsExt.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeAllTestsExt.java index 6fef9815..71d46e83 100644 --- a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeAllTestsExt.java +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeAllTestsExt.java @@ -37,6 +37,11 @@ public class KillAndHowBeforeAllTestsExt extends ExtensionBase protected static final String CORE_KEY = "core_key"; + @Override + public ExtensionType getExtensionType() { + return ExtensionType.KILL_ALL_TESTS; + } + /** * Store the state of AndHow before any test is run, then destroy the state * so AndHow is unconfigured. diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeEachTestExt.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeEachTestExt.java index 57e95dea..3c44b85f 100644 --- a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeEachTestExt.java +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeEachTestExt.java @@ -36,6 +36,13 @@ public class KillAndHowBeforeEachTestExt extends KillAndHowBeforeAllTestsExt implements BeforeEachCallback { + // This isn't really the correct type, but based on the super class it works for now. + // See https://github.com/eeverman/andhow/issues/745 + @Override + public ExtensionType getExtensionType() { + return ExtensionType.KILL_ALL_TESTS; + } + /** * Destroy the AndHow state before each test so that each starts with AndHow unconfigured. * @param context diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeThisTestExt.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeThisTestExt.java index bdc76d63..ed03dd4c 100644 --- a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeThisTestExt.java +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/main/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeThisTestExt.java @@ -29,6 +29,11 @@ public class KillAndHowBeforeThisTestExt extends ExtensionBase public static final String CORE_KEY = "core_key"; + @Override + public ExtensionType getExtensionType() { + return ExtensionType.KILL_THIS_TEST; + } + /** * Store the state of AndHow before this test, then destroy the state so AndHow is unconfigured. * @param context @@ -36,7 +41,7 @@ public class KillAndHowBeforeThisTestExt extends ExtensionBase */ @Override public void beforeEach(ExtensionContext context) throws Exception { - getPerTestMethodStore(context).put(CORE_KEY, AndHowTestUtils.setAndHowCore(null)); + getStore(context).put(CORE_KEY, AndHowTestUtils.setAndHowCore(null)); } /** @@ -46,7 +51,7 @@ public void beforeEach(ExtensionContext context) throws Exception { */ @Override public void afterEach(ExtensionContext context) throws Exception { - Object core = getPerTestMethodStore(context).remove(CORE_KEY, AndHowTestUtils.getAndHowCoreClass()); + Object core = getStore(context).remove(CORE_KEY, AndHowTestUtils.getAndHowCoreClass()); AndHowTestUtils.setAndHowCore(core); } diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/ExtensionUtilDefaultPackageTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/ExtensionUtilDefaultPackageTest.java new file mode 100644 index 00000000..2b12bfac --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/ExtensionUtilDefaultPackageTest.java @@ -0,0 +1,60 @@ +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mockito.*; +import org.yarnandtail.andhow.junit5.ext.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Duplicates some tests from org.yarnandtail.andhow.junit5.ConfigFromFileExtTest, + * but does it using a similated Test class that is in the default package. + * In newer JDKs it is not possible to import a class in the default package, + * so the only way to refer to such is a class is to also be in the default + * package, thus this test class. + */ +class ExtensionUtilDefaultPackageTest { + + //The context object that is passed to the test extension + ExtensionContext extensionContext; + + + @BeforeEach + void setUp() { + + // + // Setup mockito for the test + extensionContext = Mockito.mock(ExtensionContext.class); + Mockito.when(extensionContext.getRequiredTestClass()).thenReturn((Class)(MyClassWithNoPackage.class)); + } + + @Test + public void expandPathShouldExpandRelativePaths() { + + assertEquals("/myFile.props", + ExtensionUtil.expandPath("myFile.props", extensionContext)); + + assertEquals("/sub/myFile.props", + ExtensionUtil.expandPath("sub/myFile.props", extensionContext)); + + //Need to test a class at the root somehow (pkg is empty) + } + + @Test + public void expandPathShouldNotExpandAbsPaths() { + + assertEquals("/myFile.props", + ExtensionUtil.expandPath("/myFile.props", extensionContext)); + + assertEquals("/myFile", + ExtensionUtil.expandPath("/myFile", extensionContext)); + + assertEquals("/sub/myFile.props", + ExtensionUtil.expandPath("/sub/myFile.props", extensionContext)); + + assertEquals("/sub/myFile", + ExtensionUtil.expandPath("/sub/myFile", extensionContext)); + + //Need to test a class at the root somehow (pkg is empty) + } + +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/MyClassWithNoPackage.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/MyClassWithNoPackage.java new file mode 100644 index 00000000..cb74097e --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/MyClassWithNoPackage.java @@ -0,0 +1,5 @@ +/** + * This class is used by some classes to test classpaths for classes with no package. + */ +public class MyClassWithNoPackage { +} diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeAllTestsExtUnitTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeAllTestsExtUnitTest.java new file mode 100644 index 00000000..43a891e5 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeAllTestsExtUnitTest.java @@ -0,0 +1,18 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ConfigFromFileBeforeAllTestsExtUnitTest { + + @Test + void getExtensionType() { + ConfigFromFileBeforeAllTestsExt ext = new ConfigFromFileBeforeAllTestsExt(); + ExtensionType type = ext.getExtensionType(); + + assertEquals(ExtensionType.Storage.TEST_INSTANCE, type.getStorage()); + assertEquals(ExtensionType.Effect.CONFIGURE, type.getEffect()); + assertEquals(ExtensionType.Scope.TEST_CLASS, type.getScope()); + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeAllTestsExtUsageTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeAllTestsExtUsageTest.java new file mode 100644 index 00000000..690058e8 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeAllTestsExtUsageTest.java @@ -0,0 +1,100 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.*; +import org.junit.jupiter.api.parallel.Execution; +import org.yarnandtail.andhow.AndHow; +import org.yarnandtail.andhow.property.StrProp; +import org.yarnandtail.andhow.testutil.AndHowTestUtils; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; + +/** + * This class shares a static var 'extensionContextDuringTest' which is the JUnit ExtensionContext + * used during a test. Multiple threads executing the test would break this, thus SAME_THREAD. + */ +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Execution(SAME_THREAD) +class ConfigFromFileBeforeAllTestsExtUsageTest extends InterceptorTestBase { + + @RegisterExtension + static ConfigFromFileBeforeAllTestsExt _configFromFileBaseExt = new ConfigFromFileBeforeAllTestsExt("MyPropFile.properties"); + + /** test1 will set this val. All following tests should share if working as expected. */ + private static Object coreFoundInTest1; + + @Order(1) + @Test + public void test1() throws NoSuchMethodException { + + assertFalse(AndHow.isInitialized()); + assertNotNull(extensionContextDuringTest); + + ExtensionContext.Namespace expectedNamespace = ExtensionContext.Namespace.create( + ConfigFromFileBeforeAllTestsExt.class, (Class)(this.getClass()), + getClass().getMethod("test1", null)); + + ExtensionContext.Store store = extensionContextDuringTest.getStore(expectedNamespace); + assertNotNull(store); + + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileBeforeAllTestsExtUsageTest.Config.class); + + + assertEquals("Bob", Config.MY_PROP.getValue()); + + coreFoundInTest1 = AndHowTestUtils.getAndHowCore(); //Only non-null after value access above. + } + + @Order(2) + @Test + public void test2() throws NoSuchMethodException { + assertTrue(AndHow.isInitialized()); // Single initialization for entire class + assertSame(coreFoundInTest1, AndHowTestUtils.getAndHowCore()); + } + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + @Order(1) + class Nest1 { + + // The nested class inherits the registered ConfigFromFileBeforeAllTestsExt + // extension and it is invoked again, newly creating configuration based on + // "MyPropFile.properties". + @Test + @Order(1) + @ExtendWith(TestInterceptor.class) + public void test1() throws NoSuchMethodException { + + assertFalse(AndHow.isInitialized()); + assertNotNull(extensionContextDuringTest); + + ExtensionContext.Namespace expectedNamespace = ExtensionContext.Namespace.create( + ConfigFromFileBeforeAllTestsExt.class, (Class)(this.getClass()), + getClass().getMethod("test1", null)); + + ExtensionContext.Store store = extensionContextDuringTest.getStore(expectedNamespace); + assertNotNull(store); + + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileBeforeAllTestsExtUsageTest.Config.class); + + assertEquals("Bob", Config.MY_PROP.getValue()); + + coreFoundInTest1 = AndHowTestUtils.getAndHowCore(); //Only non-null after value access above. + + } + + @Order(2) + @Test + public void test2() throws NoSuchMethodException { + assertTrue(AndHow.isInitialized()); // Single initialization for entire class + assertSame(coreFoundInTest1, AndHowTestUtils.getAndHowCore()); + } + } + + /** Simple config for use in this test */ + static interface Config { + StrProp MY_PROP = StrProp.builder().aliasInAndOut("MY_PROP").build(); + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeEachTestExtUnitTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeEachTestExtUnitTest.java new file mode 100644 index 00000000..9c6195ae --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeEachTestExtUnitTest.java @@ -0,0 +1,18 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ConfigFromFileBeforeEachTestExtUnitTest { + + @Test + void getExtensionType() { + ConfigFromFileBeforeEachTestExt ext = new ConfigFromFileBeforeEachTestExt(); + ExtensionType type = ext.getExtensionType(); + + assertEquals(ExtensionType.Storage.TEST_METHOD, type.getStorage()); + assertEquals(ExtensionType.Effect.CONFIGURE, type.getEffect()); + assertEquals(ExtensionType.Scope.EACH_TEST, type.getScope()); + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeEachTestExtUsageTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeEachTestExtUsageTest.java new file mode 100644 index 00000000..b593afd9 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeEachTestExtUsageTest.java @@ -0,0 +1,99 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.*; +import org.junit.jupiter.api.parallel.Execution; +import org.yarnandtail.andhow.AndHow; +import org.yarnandtail.andhow.property.StrProp; +import org.yarnandtail.andhow.testutil.AndHowTestUtils; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; + +/** + * This class shares a static var 'extensionContextDuringTest' which is the JUnit ExtensionContext + * used during a test. Multiple threads executing the test would break this, thus SAME_THREAD. + */ +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Execution(SAME_THREAD) +class ConfigFromFileBeforeEachTestExtUsageTest extends InterceptorTestBase { + + @RegisterExtension + static ConfigFromFileBeforeEachTestExt _configFromFileBaseExt = new ConfigFromFileBeforeEachTestExt("MyPropFile.properties"); + + @Order(1) + @Test + public void test1() throws NoSuchMethodException { + + assertFalse(AndHow.isInitialized()); + assertNotNull(extensionContextDuringTest); + + ExtensionContext.Namespace expectedNamespace = ExtensionContext.Namespace.create( + ConfigFromFileBeforeEachTestExt.class, (Class)(this.getClass()), + getClass().getMethod("test1", null)); + + ExtensionContext.Store store = extensionContextDuringTest.getStore(expectedNamespace); + assertNotNull(store); + + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileBeforeEachTestExtUsageTest.Config.class); + + assertEquals("Bob", Config.MY_PROP.getValue()); + } + + // test2 is the same as test1 - They should each have a newly configured state + // based on "MyPropFile.properties" + @Order(2) + @Test + public void test2() throws NoSuchMethodException { + + assertFalse(AndHow.isInitialized()); + assertNotNull(extensionContextDuringTest); + + ExtensionContext.Namespace expectedNamespace = ExtensionContext.Namespace.create( + ConfigFromFileBeforeEachTestExt.class, (Class)(this.getClass()), + getClass().getMethod("test1", null)); + + ExtensionContext.Store store = extensionContextDuringTest.getStore(expectedNamespace); + assertNotNull(store); + + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileBeforeEachTestExtUsageTest.Config.class); + + assertEquals("Bob", Config.MY_PROP.getValue()); + } + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + @Order(1) + class Nest1 { + + // The nested class inherits the registered ConfigFromFileBeforeEachTestExt + // extension and it is invoked again, newly creating configuration based on + // "MyPropFile.properties". + @Test + @Order(1) + @ExtendWith(TestInterceptor.class) + public void test1() throws NoSuchMethodException { + + assertFalse(AndHow.isInitialized()); + assertNotNull(extensionContextDuringTest); + + ExtensionContext.Namespace expectedNamespace = ExtensionContext.Namespace.create( + ConfigFromFileBeforeEachTestExt.class, (Class)(this.getClass()), + getClass().getMethod("test1", null)); + + ExtensionContext.Store store = extensionContextDuringTest.getStore(expectedNamespace); + assertNotNull(store); + + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileBeforeEachTestExtUsageTest.Config.class); + + assertEquals("Bob", Config.MY_PROP.getValue()); + + } + } + + /** Simple config for use in this test */ + static interface Config { + StrProp MY_PROP = StrProp.builder().aliasInAndOut("MY_PROP").build(); + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeThisTestExtUnitTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeThisTestExtUnitTest.java new file mode 100644 index 00000000..52be27b9 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileBeforeThisTestExtUnitTest.java @@ -0,0 +1,18 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ConfigFromFileBeforeThisTestExtUnitTest { + + @Test + void getExtensionType() { + ConfigFromFileBeforeThisTestExt ext = new ConfigFromFileBeforeThisTestExt(); + ExtensionType type = ext.getExtensionType(); + + assertEquals(ExtensionType.Storage.TEST_METHOD, type.getStorage()); + assertEquals(ExtensionType.Effect.CONFIGURE, type.getEffect()); + assertEquals(ExtensionType.Scope.SINGLE_TEST, type.getScope()); + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileExtUnitTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileExtUnitTest.java new file mode 100644 index 00000000..265a1150 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ConfigFromFileExtUnitTest.java @@ -0,0 +1,312 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mockito.*; +import org.yarnandtail.andhow.*; +import org.yarnandtail.andhow.junit5.ConfigFromFileBeforeAllTests; +import org.yarnandtail.andhow.testutil.AndHowTestUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.times; + +class ConfigFromFileExtUnitTest { + + //Store for state variables within the context + ExtensionContext.Store store; + + //The context object that is passed to the test extension + ExtensionContext extensionContext; + + + @BeforeEach + public void setUp() throws NoSuchMethodException { + + Method thisMethod = getClass().getMethod("setUp", null); + + // + // Setup mockito for the test + + store = Mockito.mock(ExtensionContext.Store.class); + + extensionContext = Mockito.mock(ExtensionContext.class); + Mockito.when(extensionContext.getRequiredTestClass()).thenReturn((Class)(this.getClass())); + Mockito.when(extensionContext.getRequiredTestInstance()).thenReturn(this); + Mockito.when(extensionContext.getRequiredTestMethod()).thenReturn(thisMethod); + Mockito.when(extensionContext.getStore(ArgumentMatchers.any())).thenReturn(store); + } + + @AfterEach + void tearDown() { + AndHowTestUtils.setAndHowCore(null); + } + + + /* NOTE: Testing building correct paths with the default package are handled + by a separate test in the default package. */ + + /** + * This test scenario (non-null core and null inProcessConfig) is the case + * when AndHow has been initialized. + * @throws Exception + */ + @Test + void completeWorkflowWithNonNullCoreAndNullConfig() throws Exception { + + Object andHowCoreCreatedDuringTest; + + /// Setup Mocks /// + /// AndHow will be initialized (non-null core), the locator will be null + assertNull(AndHowTestUtils.getAndHowCore(), + "Just checking - no test should leave AndHow initialized"); + + AndHowTestUtils.invokeAndHowInstance(); //force creation + + andHowCoreCreatedDuringTest = AndHowTestUtils.getAndHowCore(); + + assertNotNull(andHowCoreCreatedDuringTest, "Should be non-null because we forced creation"); + + // + // Setup mockito for the test + + Mockito.when(store.remove(ArgumentMatchers.matches( + ConfigFromFileBeforeAllExtSimple.getCoreKey()), ArgumentMatchers.any()) + ).thenReturn(andHowCoreCreatedDuringTest); + + Mockito.when(store.remove(ArgumentMatchers.matches( + ConfigFromFileBeforeAllExtSimple.getConfigKey()), ArgumentMatchers.any()) + ).thenReturn(null); + + /// /// /// /// + + String cp = "MyPropFile.properties"; + + ConfigFromFileBeforeAllExtSimple theExt = new ConfigFromFileBeforeAllExtSimple(cp, new Class[] {this.getClass()}); + + // The initial event called on extension by JUnit + theExt.beforeAllOrEach(extensionContext); + + assertNull(AndHowTestUtils.getAndHowCore(), + "Extension should have killed the core"); + + assertSame(theExt.getCreatedConfig(), AndHow.findConfig()); + + // TODO: This test doesn't actually test that the created Core is as we want it. + + // The final event called on the extension by Junit + theExt.afterAllOrEach(extensionContext); + + // + // Verify the overall outcome + assertEquals(andHowCoreCreatedDuringTest, AndHowTestUtils.getAndHowCore(), + "Extension should have reinstated the same core instance created in setup"); + + + // + // Verify actions on the store + ArgumentCaptor keyForCorePut = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor keyForConfigPut = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor keyForCoreRemove = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor keyForConfigRemove = ArgumentCaptor.forClass(Object.class); + + // In order, there should be 2x puts and 2x removes - can't be more specific on order than that. + InOrder orderedStoreCalls = Mockito.inOrder(store); + orderedStoreCalls.verify(store, times(2)).put(ArgumentMatchers.any(), ArgumentMatchers.any()); + orderedStoreCalls.verify(store, times(2)).remove(ArgumentMatchers.any(), ArgumentMatchers.any()); + Mockito.verifyNoMoreInteractions(store); //Really don't want any other interaction w/ the store + + + // Now verify actual arguments to the store (can't assume in the order above) + Mockito.verify(store).put(keyForCorePut.capture(), ArgumentMatchers.eq(andHowCoreCreatedDuringTest)); + Mockito.verify(store).put(keyForConfigPut.capture(), ArgumentMatchers.isNull()); // Initial config was null, so getting it back here + Mockito.verify(store).remove(keyForCoreRemove.capture(), ArgumentMatchers.eq(AndHowTestUtils.getAndHowCoreClass())); + Mockito.verify(store).remove(keyForConfigRemove.capture(), ArgumentMatchers.eq(AndHowConfiguration.class)); + + assertEquals(keyForCorePut.getValue(), keyForCoreRemove.getValue(), + "The keys used for put & remove the core should be the same"); + + assertEquals(keyForConfigPut.getValue(), keyForConfigRemove.getValue(), + "The keys used for put & remove the locator should be the same"); + + // + // Verify actions on the ExtensionContext + ArgumentCaptor namespace = + ArgumentCaptor.forClass(ExtensionContext.Namespace.class); + + Mockito.verify(extensionContext, Mockito.atLeast(2)).getStore(namespace.capture()); + + //Verify the namespace used + // The namespace is an implementation detail, but must include the Extension class and the + // Tested class (this class in this case). There isn't an easy way to test that minimum spec, + // so here just check for a specific namespace. + //The expected namespace used to store values within the Extension + ExtensionContext.Namespace expectedNamespace = ExtensionContext.Namespace.create( + ConfigFromFileBeforeAllExtSimple.class, + (Class)(this.getClass())); + assertEquals(expectedNamespace, namespace.getValue()); + + } + + + /** + * This scenario (null core and non-null config) is the case when findConfig() has been called + * but AndHow has not yet been initialized. + * @throws Exception + */ + @Test + void completeWorkflowWithNullCoreAndNonNullConfig() throws Exception { + + /// Setup Mocks /// + /// AndHow will be un-initialized (null core), the locator will be non-null + assertNull(AndHowTestUtils.getAndHowCore(), + "Just checking - no test should leave AndHow initialized"); + + AndHowConfiguration existingConfig = AndHow.findConfig(); + assertNotNull(existingConfig); + + // + // Setup mockito for the test + AndHowConfiguration newConfig = StdConfig.instance(); + + Mockito.when(store.remove(ArgumentMatchers.matches( + ConfigFromFileBeforeAllExtSimple.getCoreKey()), ArgumentMatchers.any()) + ).thenReturn(null); + + Mockito.when(store.remove(ArgumentMatchers.matches( + ConfigFromFileBeforeAllExtSimple.getConfigKey()), ArgumentMatchers.any()) + ).thenReturn(existingConfig); + + /// /// /// /// + + + String cp = "MyPropFile.properties"; + + ConfigFromFileBeforeEachExtSimple theExt = new ConfigFromFileBeforeEachExtSimple(cp, new Class[] {this.getClass()}); + + // The initial event called on extension by JUnit + theExt.beforeAllOrEach(extensionContext); + + assertNull(AndHowTestUtils.getAndHowCore(), + "Should still be uninitialized"); + + assertSame(theExt.getCreatedConfig(), AndHow.findConfig()); + + + // The final event called on the extension by Junit + theExt.afterAllOrEach(extensionContext); + + // + // Verify the overall outcome + assertNull(AndHowTestUtils.getAndHowCore(), + "Extension should be null after the test"); + + + // + // Verify actions on the store + ArgumentCaptor keyForCorePut = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor keyForConfigPut = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor keyForCoreRemove = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor keyForConfigRemove = ArgumentCaptor.forClass(Object.class); + + // In order, there should be 2x puts and 2x removes - can't be more specific on order than that. + InOrder orderedStoreCalls = Mockito.inOrder(store); + orderedStoreCalls.verify(store, times(2)).put(ArgumentMatchers.any(), ArgumentMatchers.any()); + orderedStoreCalls.verify(store, times(2)).remove(ArgumentMatchers.any(), ArgumentMatchers.any()); + Mockito.verifyNoMoreInteractions(store); //Really don't want any other interaction w/ the store + + + // Now verify actual arguments to the store (can't assume in the order above) + Mockito.verify(store).put(keyForCorePut.capture(), ArgumentMatchers.isNull()); + Mockito.verify(store).put(keyForConfigPut.capture(), ArgumentMatchers.eq(existingConfig)); // Initial config AndHow had + Mockito.verify(store).remove(keyForCoreRemove.capture(), ArgumentMatchers.eq(AndHowTestUtils.getAndHowCoreClass())); + Mockito.verify(store).remove(keyForConfigRemove.capture(), ArgumentMatchers.eq(AndHowConfiguration.class)); + + assertEquals(keyForCorePut.getValue(), keyForCoreRemove.getValue(), + "The keys used for put & remove the core should be the same"); + + assertEquals(keyForConfigPut.getValue(), keyForConfigRemove.getValue(), + "The keys used for put & remove the locator should be the same"); + + // + // Verify actions on the ExtensionContext + ArgumentCaptor namespace = + ArgumentCaptor.forClass(ExtensionContext.Namespace.class); + + Mockito.verify(extensionContext, Mockito.atLeast(2)).getStore(namespace.capture()); + + //Verify the namespace used + // The namespace is an implementation detail, but must include the Extension class and the + // Tested class (this class in this case). There isn't an easy way to test that minimum spec, + // so here just check for a specific namespace. + ExtensionContext.Namespace expectedNamespace = ExtensionContext.Namespace.create( + ConfigFromFileBeforeEachExtSimple.class, + this, getClass().getMethod("setUp", null)); + assertEquals(expectedNamespace, namespace.getValue()); + + } + + /* Simple subclass to test protected methods */ + public static class ConfigFromFileBeforeAllExtSimple extends ConfigFromFileBaseExt { + + // Only works in limited contexts where the full class is not being invoked + public ConfigFromFileBeforeAllExtSimple() { + super(""); + } + + public ConfigFromFileBeforeAllExtSimple(String classpathFile, Class[] classesInScope) { + super(classpathFile, classesInScope); + } + + @Override + protected String getFilePathFromAnnotation(final ExtensionContext context) { return null; } + + @Override + protected Class[] getClassesInScopeFromAnnotation(final ExtensionContext context) { + return new Class[0]; + } + + @Override + public Class getAssociatedAnnotation() { + return ConfigFromFileBeforeAllTests.class; + } + + public static String getCoreKey() { + return ConfigFromFileBaseExt.CORE_KEY; + } + + public static String getConfigKey() { + return ConfigFromFileBaseExt.CONFIG_KEY; + } + + public AndHowConfiguration getCreatedConfig() { + return _config; + } + + + @Override + protected ExtensionType getExtensionType() { + return ExtensionType.CONFIG_ALL_TESTS; + } + } + + public static class ConfigFromFileBeforeEachExtSimple extends ConfigFromFileBeforeAllExtSimple { + + public ConfigFromFileBeforeEachExtSimple() { + super(); + } + + public ConfigFromFileBeforeEachExtSimple(String classpathFile, Class[] classesInScope) { + super(classpathFile, classesInScope); + } + + @Override + protected ExtensionType getExtensionType() { + return ExtensionType.CONFIG_EACH_TEST; + } + } + + +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ExtensionUtilTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ExtensionUtilTest.java new file mode 100644 index 00000000..1243ea40 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/ExtensionUtilTest.java @@ -0,0 +1,103 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mockito.Mockito; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.*; + +class ExtensionUtilTest { + + + //The context object that is passed to the test extension + ExtensionContext extensionContext; + + + @BeforeEach + public void setUp() throws NoSuchMethodException { + + Method thisMethod = getClass().getMethod("setUp", null); + + // + // Setup mockito for the test + + extensionContext = Mockito.mock(ExtensionContext.class); + Mockito.when(extensionContext.getRequiredTestClass()).thenReturn((Class)(this.getClass())); + Mockito.when(extensionContext.getRequiredTestInstance()).thenReturn(this); + Mockito.when(extensionContext.getRequiredTestMethod()).thenReturn(thisMethod); + } + + @Test + void findAnnotation() { + } + + @Test + void isInnerClass() { + } + + @Test + void isExistingResource() { + } + + @Test + void stem() { + } + + @Test + void testFindAnnotation() { + } + + + @Test + public void expandPathShouldExpandRelativePaths() { + + assertEquals("/org/yarnandtail/andhow/junit5/ext/myFile.props", + ExtensionUtil.expandPath("myFile.props", extensionContext)); + + assertEquals("/org/yarnandtail/andhow/junit5/ext/sub/myFile.props", + ExtensionUtil.expandPath("sub/myFile.props", extensionContext)); + + assertEquals("/org/yarnandtail/andhow/junit5/ext/../myFile.props", + ExtensionUtil.expandPath("../myFile.props", extensionContext)); + + assertEquals("/org/yarnandtail/andhow/junit5/ext/../sub/myFile.props", + ExtensionUtil.expandPath("../sub/myFile.props", extensionContext)); + + + //Testing w/ no package (default pkg) is in a different test class + } + + @Test + public void expandPathShouldNotExpandAbsPaths() { + + assertEquals("/myFile.props", + ExtensionUtil.expandPath("/myFile.props", extensionContext)); + + assertEquals("/myFile", + ExtensionUtil.expandPath("/myFile", extensionContext)); + + assertEquals("/sub/myFile.props", + ExtensionUtil.expandPath("/sub/myFile.props", extensionContext)); + + assertEquals("/sub/myFile", + ExtensionUtil.expandPath("/sub/myFile", extensionContext)); + + //Testing w/ no package (default pkg) is in a different test class + } + + @Test + public void expandPathShouldUsePackageOfTopLevelClassForInnerClasses() { + //Set mock test class to an inner class + Mockito.when(extensionContext.getRequiredTestClass()) + .thenReturn((Class)(ConfigFromFileExtUnitTest.ConfigFromFileBeforeAllExtSimple.class)); + + assertEquals("/org/yarnandtail/andhow/junit5/ext/myFile.props", + ExtensionUtil.expandPath("myFile.props", extensionContext)); + + assertEquals("/org/yarnandtail/andhow/junit5/ext/subpkg/myFile.props", + ExtensionUtil.expandPath("subpkg/myFile.props", extensionContext)); + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/InterceptorTestBase.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/InterceptorTestBase.java new file mode 100644 index 00000000..3626711d --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/InterceptorTestBase.java @@ -0,0 +1,46 @@ +package org.yarnandtail.andhow.junit5.ext; + +import org.junit.jupiter.api.extension.*; +import java.lang.reflect.Method; + +/** + * This class shares a static var 'extensionContextDuringTest' which is the JUnit ExtensionContext + * used during a test. Multiple threads executing the test would break this, thus SAME_THREAD. + */ +@ExtendWith(ConfigFromFileBeforeAllTestsExtUsageTest.TestInterceptor.class) +public class InterceptorTestBase { + + protected static ExtensionContext extensionContextDuringTest; + + /** + * This Interceptor should capture the ExtensionContext being used during the test and store it + * to the parent class's extensionContextDuringTest static var. Within a @Test method annotated + * w/ @ExtendWith(TestInterceptor.class), the extensionContextDuringTest will contain the extension + * context of the test. + * + * Note that '@ExtendWith(TestInterceptor.class)' should be the first ExtendWith annotation to + * ensure it executes first. + */ + public static class TestInterceptor implements InvocationInterceptor { + + @Override + public void interceptTestMethod(final Invocation invocation, final ReflectiveInvocationContext invocationContext, final ExtensionContext extensionContext) throws Throwable { + + Throwable throwable = null; + + try { + extensionContextDuringTest = extensionContext; + invocation.proceed(); + extensionContextDuringTest = null; + } catch (Throwable t) { + throwable = t; + } + + if (throwable != null) { + throw throwable; + } + } + + } + +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeAllTestsExtTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeAllTestsExtUnitTest.java similarity index 90% rename from junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeAllTestsExtTest.java rename to junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeAllTestsExtUnitTest.java index 9c7988a2..b5d1c4aa 100644 --- a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeAllTestsExtTest.java +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeAllTestsExtUnitTest.java @@ -12,7 +12,7 @@ * Beyond unit testing, the extension is used in many of the simulated app tests * (see that module for usage examples). */ -class KillAndHowBeforeAllTestsExtTest { +class KillAndHowBeforeAllTestsExtUnitTest { Object andHowCoreCreatedDuringTest; @@ -55,6 +55,16 @@ void tearDown() { AndHowTestUtils.setAndHowCore(null); } + @Test + void getExtensionType() { + KillAndHowBeforeAllTestsExt ext = new KillAndHowBeforeAllTestsExt(); + ExtensionType type = ext.getExtensionType(); + + assertEquals(ExtensionType.Storage.TEST_INSTANCE, type.getStorage()); + assertEquals(ExtensionType.Effect.KILL, type.getEffect()); + assertEquals(ExtensionType.Scope.TEST_CLASS, type.getScope()); + } + @Test void completeWorkflow() throws Exception { diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeEachTestExtensionTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeEachTestExtensionUnitTest.java similarity index 88% rename from junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeEachTestExtensionTest.java rename to junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeEachTestExtensionUnitTest.java index 89fb2272..00a715db 100644 --- a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeEachTestExtensionTest.java +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeEachTestExtensionUnitTest.java @@ -12,7 +12,7 @@ * Beyond unit testing, the extension is used in many of the simulated app tests * (see that module for usage examples). */ -class KillAndHowBeforeEachTestExtensionTest { +class KillAndHowBeforeEachTestExtensionUnitTest { Object andHowCoreCreatedDuringTest; @@ -55,6 +55,18 @@ void tearDown() { AndHowTestUtils.setAndHowCore(null); } + // This isn't really the correct type, but based on the super class it works for now. + // See https://github.com/eeverman/andhow/issues/745 + @Test + void getExtensionType() { + KillAndHowBeforeEachTestExt ext = new KillAndHowBeforeEachTestExt(); + ExtensionType type = ext.getExtensionType(); + + assertEquals(ExtensionType.Storage.TEST_INSTANCE, type.getStorage()); + assertEquals(ExtensionType.Effect.KILL, type.getEffect()); + assertEquals(ExtensionType.Scope.TEST_CLASS, type.getScope()); + } + @Test void completeWorkflow() throws Exception { diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeThisTestExtensionTest.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeThisTestExtensionUnitTest.java similarity index 91% rename from junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeThisTestExtensionTest.java rename to junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeThisTestExtensionUnitTest.java index 78f8b439..9c8b3052 100644 --- a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeThisTestExtensionTest.java +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/ext/KillAndHowBeforeThisTestExtensionUnitTest.java @@ -12,7 +12,7 @@ * Beyond unit testing, the extension is used in many of the simulated app tests * (see that module for usage examples). */ -class KillAndHowBeforeThisTestExtensionTest { +class KillAndHowBeforeThisTestExtensionUnitTest { Object andHowCoreCreatedDuringTest; @@ -56,6 +56,16 @@ void tearDown() { AndHowTestUtils.setAndHowCore(null); } + @Test + void getExtensionType() { + KillAndHowBeforeThisTestExt ext = new KillAndHowBeforeThisTestExt(); + ExtensionType type = ext.getExtensionType(); + + assertEquals(ExtensionType.Storage.TEST_METHOD, type.getStorage()); + assertEquals(ExtensionType.Effect.KILL, type.getEffect()); + assertEquals(ExtensionType.Scope.SINGLE_TEST, type.getScope()); + } + @Test public void completeWorkflow() throws Exception { diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/Conf1.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/Conf1.java new file mode 100644 index 00000000..d0444d0d --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/Conf1.java @@ -0,0 +1,12 @@ +package org.yarnandtail.andhow.junit5.usagetests; + +import org.yarnandtail.andhow.property.StrProp; + +public interface Conf1 { + + StrProp MY_PROP = StrProp.builder().aliasInAndOut("CONF1_MY_PROP").build(); + + static interface Inner1 { + StrProp MY_PROP = StrProp.builder().aliasInAndOut("CONF1_INNER1_MY_PROP").build(); + } +} diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/Conf2.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/Conf2.java new file mode 100644 index 00000000..06a3fc9d --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/Conf2.java @@ -0,0 +1,17 @@ +package org.yarnandtail.andhow.junit5.usagetests; + +import org.yarnandtail.andhow.property.StrProp; + +public interface Conf2 { + + StrProp MY_PROP = StrProp.builder().aliasInAndOut("CONF2.MY.PROP").build(); + + static interface Inner1 { + // Required! + StrProp MY_PROP = StrProp.builder().aliasInAndOut("CONF2_INNER1_MY_PROP").notNull().build(); + } + + static interface Inner2 { + // I have no config properties... + } +} diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/Conf3.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/Conf3.java new file mode 100644 index 00000000..25477292 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/Conf3.java @@ -0,0 +1,5 @@ +package org.yarnandtail.andhow.junit5.usagetests; + +public interface Conf3 { + // I have no configuration properties... +} diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/ConfigFromFileMixedUsage1Test.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/ConfigFromFileMixedUsage1Test.java new file mode 100644 index 00000000..07eedb9e --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/ConfigFromFileMixedUsage1Test.java @@ -0,0 +1,257 @@ +package org.yarnandtail.andhow.junit5.usagetests; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.*; +import org.junit.jupiter.api.parallel.Execution; +import org.yarnandtail.andhow.AndHow; +import org.yarnandtail.andhow.junit5.*; +import org.yarnandtail.andhow.junit5.ext.*; +import org.yarnandtail.andhow.property.StrProp; +import org.yarnandtail.andhow.testutil.AndHowTestUtils; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; + +/** + * Features of this test: + * - Uses relative (../ext/...) classpaths to locate properties files in a different pkg. + * - Uses @Nested test classes where some override the parent @ConfigFromFileBeforeAllTests config + * + * This class shares a static var 'extensionContextDuringTest' which is the JUnit ExtensionContext + * used during a test. Multiple threads executing the test would break this, thus SAME_THREAD. + */ +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Execution(SAME_THREAD) +@ConfigFromFileBeforeAllTests("../ext/MyPropFile.properties") +class ConfigFromFileMixedUsage1Test extends InterceptorTestBase { + + private static Object coreFoundInTest1; + + @Order(1) + @Test + public void test1() { + + assertFalse(AndHow.isInitialized()); + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileMixedUsage1Test.Config.class); + + assertEquals("Bob", Config.MY_PROP.getValue()); + + coreFoundInTest1 = AndHowTestUtils.getAndHowCore(); //Only non-null after value access above. + } + + @Order(2) + @Test + public void test2() { + assertTrue(AndHow.isInitialized()); // Single initialization for entire class + assertSame(coreFoundInTest1, AndHowTestUtils.getAndHowCore()); + assertEquals("Bob", Config.MY_PROP.getValue()); + } + + @Order(3) + @Test + @ConfigFromFileBeforeThisTest("../ext/MyPropFile2.properties") + public void test3() throws NoSuchMethodException { + + assertFalse(AndHow.isInitialized()); + + ExtensionContext.Namespace expectedNamespace = ExtensionContext.Namespace.create( + ConfigFromFileBeforeThisTestExt.class, this, + getClass().getMethod("test3", null)); + + ExtensionContext.Store store = extensionContextDuringTest.getStore(expectedNamespace); + assertNotNull(store); + assertSame(coreFoundInTest1, store.get("core_key"), + "The stored core should be the same one created via BeforeAll in test1 "); + + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileMixedUsage1Test.Config.class); + + assertEquals("Bob2", Config.MY_PROP.getValue()); + } + + @Order(4) + @Test + public void test4() { + assertTrue(AndHow.isInitialized()); + assertSame(coreFoundInTest1, AndHowTestUtils.getAndHowCore()); + assertEquals("Bob", Config.MY_PROP.getValue()); + } + + + /** + * A nested test inherits the ConfigFromFileBeforeAllTests from the parent class, + * BUT IT RE-INSTANTIATES IT! So, AndHow starts over, uninitialized. If any modifications were + * made to the config, it is lost in the inner class. Also, the @BeforeAll of the parent class + * is NOT called again for the Nested class, so it is not safe to modify config in there if using + * inner classes. + */ + @Nested + @Order(1) + class NestA { + + //static method in inner class not allowed in 1.8 +// @BeforeAll +// static public void beforeAll() { +// System.out.println("NestA BeforeAll"); +// } + + /** + * AndHow is uninitialized, but will initialize to see the result of the parent class's + * @ConfigFromFileBeforeAllTests annotation. + */ + @Test + @Order(1) + public void test1() { + assertFalse(AndHow.isInitialized()); + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileMixedUsage1Test.Config.class); + assertEquals("Bob", Config.MY_PROP.getValue()); + } + + @Order(2) + @Test + public void test2() { + assertTrue(AndHow.isInitialized()); + assertEquals("Bob", Config.MY_PROP.getValue()); + } + + @Nested + @Order(1) + class NestAA { + @Test + public void test1() { + assertFalse(AndHow.isInitialized()); + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileMixedUsage1Test.Config.class); + assertEquals("Bob", Config.MY_PROP.getValue()); + } + } + + + @Nested + @Order(2) + @ConfigFromFileBeforeAllTests(value = "../ext/MyPropFileNest1.properties") + class NestAB { + @Test + @Order(1) + public void test1() { + assertFalse(AndHow.isInitialized()); + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileMixedUsage1Test.Config.class); + assertEquals("BobNest1", Config.MY_PROP.getValue()); + } + + @Order(2) + @Test + public void test2() { + assertTrue(AndHow.isInitialized()); + assertEquals("BobNest1", Config.MY_PROP.getValue()); + } + } + + + @Nested + @Order(3) + class NestAC { + @Test + public void test1() { + assertFalse(AndHow.isInitialized()); + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileMixedUsage1Test.Config.class); + assertEquals("Bob", Config.MY_PROP.getValue()); + } + } + + /** + * Nested and using ConfigFromFileBeforeEachTest + */ + @Nested + @Order(4) + @ConfigFromFileBeforeEachTest("../ext/MyPropFileNest2.properties") + class NestAD { + + @Test + @Order(1) + public void test1() { + assertFalse(AndHow.isInitialized()); + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileMixedUsage1Test.Config.class); + assertEquals("BobNest2", Config.MY_PROP.getValue()); + } + + @Order(2) + @Test + public void test2() { + assertFalse(AndHow.isInitialized()); + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileMixedUsage1Test.Config.class); + assertEquals("BobNest2", Config.MY_PROP.getValue()); + } + } + + @Nested + @Order(5) + class NestAE { + @Test + public void test1() { + assertFalse(AndHow.isInitialized()); + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileMixedUsage1Test.Config.class); + assertEquals("Bob", Config.MY_PROP.getValue()); + } + } + } + + @Nested + @Order(2) + @ExtendWith(TestInterceptor.class) + @ConfigFromFileBeforeAllTests(value = "../ext/MyPropFileNest1.properties") + class Nest1 { + + @Test + @Order(1) + public void test1() throws NoSuchMethodException { + + assertFalse(AndHow.isInitialized()); + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileMixedUsage1Test.Config.class); + + assertEquals("BobNest1", Config.MY_PROP.getValue()); + + coreFoundInTest1 = AndHowTestUtils.getAndHowCore(); //Only non-null after value access above. + } + + @Order(2) + @Test + public void test2() { + assertTrue(AndHow.isInitialized()); // Single initialization for entire class + assertSame(coreFoundInTest1, AndHowTestUtils.getAndHowCore()); + assertEquals("BobNest1", Config.MY_PROP.getValue()); + } + + + @Order(3) + @Test + @ConfigFromFileBeforeThisTest(value = "../ext/MyPropFile2.properties") + public void test3() throws NoSuchMethodException { + + assertFalse(AndHow.isInitialized()); + + ExtensionContext.Namespace expectedNamespace = ExtensionContext.Namespace.create( + ConfigFromFileBeforeThisTestExt.class, (Class)(this.getClass()), + getClass().getMethod("test2", null)); + + ExtensionContext.Store store = extensionContextDuringTest.getStore(expectedNamespace); + assertNotNull(store); + assertNotSame(coreFoundInTest1, store.get("core_key")); + + AndHowTestUtils.setConfigurationOverrideGroups(AndHow.findConfig(), ConfigFromFileMixedUsage1Test.Config.class); + + assertEquals("Bob2", Config.MY_PROP.getValue()); + } + + @Order(4) + @Test + public void test4() { + assertTrue(AndHow.isInitialized()); // Single initialization for entire class + assertEquals("BobNest1", Config.MY_PROP.getValue()); + } + } + + /** Simple config for use in this test */ + static interface Config { + StrProp MY_PROP = StrProp.builder().aliasInAndOut("MY_PROP").build(); + } +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/ConfigFromFileMixedUsage2Test.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/ConfigFromFileMixedUsage2Test.java new file mode 100644 index 00000000..1ebc147a --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/ConfigFromFileMixedUsage2Test.java @@ -0,0 +1,70 @@ +package org.yarnandtail.andhow.junit5.usagetests; + +import org.junit.jupiter.api.*; +import org.yarnandtail.andhow.AndHow; +import org.yarnandtail.andhow.api.AppFatalException; +import org.yarnandtail.andhow.api.Problem; +import org.yarnandtail.andhow.internal.RequirementProblem; +import org.yarnandtail.andhow.junit5.ConfigFromFileBeforeAllTests; +import org.yarnandtail.andhow.junit5.ConfigFromFileBeforeThisTest; + +import static org.junit.jupiter.api.Assertions.*; + + +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@ConfigFromFileBeforeAllTests(value = "Conf1And2AsBob.properties", includeClasses = {Conf1.class, Conf2.class, Conf3.class}) +class ConfigFromFileMixedUsage2Test { + + @Order(1) + @Test + public void test1() { + assertFalse(AndHow.isInitialized(), "Shouldn't be init before 1st test"); + assertEquals("Bob", Conf1.MY_PROP.getValue()); + assertEquals("Bob", Conf1.Inner1.MY_PROP.getValue()); + assertEquals("Bob", Conf2.MY_PROP.getValue()); + assertEquals("Bob", Conf2.Inner1.MY_PROP.getValue()); + } + + @Order(2) + @Test + @ConfigFromFileBeforeThisTest(value = "Conf1OnlyAsDeb.properties", includeClasses = {Conf1.class}) + public void test2() throws NoSuchMethodException { + + assertFalse(AndHow.isInitialized(), "Shouldn't be init bc this test forces a new config"); + + assertEquals("Deb", Conf1.MY_PROP.getValue()); + assertEquals("Deb", Conf1.Inner1.MY_PROP.getValue()); + assertThrows(RuntimeException.class, () -> Conf2.MY_PROP.getValue()); + assertThrows(RuntimeException.class, () -> Conf2.Inner1.MY_PROP.getValue()); + } + + @Order(3) + @Test + @ConfigFromFileBeforeThisTest(value = "Conf1OnlyAsDeb.properties", includeClasses = {Conf1.class, Conf2.class}) + public void puttingUnconfiguredRequiredPropertiesInScopeShouldError() { + + assertFalse(AndHow.isInitialized(), "Shouldn't be init bc this test forces a new config"); + + AppFatalException e = assertThrows(AppFatalException.class, () -> Conf1.MY_PROP.getValue()); + + assertEquals(1, e.getProblems().size()); + Problem p = e.getProblems().get(0); + assertTrue(p instanceof RequirementProblem.NonNullPropertyProblem); + RequirementProblem.NonNullPropertyProblem nnp = (RequirementProblem.NonNullPropertyProblem)p; + assertSame(Conf2.Inner1.MY_PROP, nnp.getPropertyCoord().getProperty()); + } + + @Order(4) + @Test + public void test4() { + + assertTrue(AndHow.isInitialized(), "Should be init bc the config state should be restore to post-test1"); + + assertEquals("Bob", Conf1.MY_PROP.getValue()); + assertEquals("Bob", Conf1.Inner1.MY_PROP.getValue()); + assertEquals("Bob", Conf2.MY_PROP.getValue()); + assertEquals("Bob", Conf2.Inner1.MY_PROP.getValue()); + } + +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/ConfigFromFileMixedUsage3Test.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/ConfigFromFileMixedUsage3Test.java new file mode 100644 index 00000000..df2ccc02 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/ConfigFromFileMixedUsage3Test.java @@ -0,0 +1,87 @@ +package org.yarnandtail.andhow.junit5.usagetests; + +import org.junit.jupiter.api.*; +import org.yarnandtail.andhow.junit5.*; + +import static org.junit.jupiter.api.Assertions.*; + + +/** + * This classes annotation BeforeAll...Deb should effectively be ignored b/c the superclass + * BeforeEach will re-initialize AndHow before each method. + */ +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@ConfigFromFileBeforeAllTests(value = "Conf1OnlyAsDeb.properties", includeClasses = {Conf1.class}) +class ConfigFromFileMixedUsage3Test extends EachAsBobBase { + + @BeforeAll + public static void setUp() { + // There is a brief window between BeforeAll and BeforeEach where AndHow is configured for 'Deb' + assertEquals("Deb", Conf1.MY_PROP.getValue()); + } + + @Order(1) + @Test + public void test1() { + assertEquals("Bob", Conf1.MY_PROP.getValue()); + } + + // This test does NOT pick up the annotations of the test it overrides + @Order(2) + @Test + @Override + public void test2() { + assertEquals("Bob", Conf1.MY_PROP.getValue()); + } + + @Order(4) + @Test + public void test4() { + assertEquals("Bob", Conf1.MY_PROP.getValue()); + } + + @Nested + class Inner1 { + + @Test + public void test1() { + assertEquals("Bob", Conf1.MY_PROP.getValue()); + } + } + + @Nested + class Inner2 extends EachAsCarlBase { + + @Test + public void test1() { + assertEquals("Carl", Conf1.MY_PROP.getValue()); + } + } + + /** + * Effectively override the 'Each' inherited from the parent. + */ + @ConfigFromFileBeforeEachTest(value = "Conf1OnlyAsDeb.properties", includeClasses = {Conf1.class}) + @Nested + class Inner3 extends EachAsCarlBase { + + @Test + public void test1() { + assertEquals("Deb", Conf1.MY_PROP.getValue()); + } + } + + /** + * This BeforeAll is effectively ignored just like the one on the parent class + */ + @ConfigFromFileBeforeAllTests(value = "Conf1OnlyAsDeb.properties", includeClasses = {Conf1.class}) + @Nested + class Inner4 { + + @Test + public void test1() { + assertEquals("Bob", Conf1.MY_PROP.getValue()); + } + } + +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/EachAsBobBase.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/EachAsBobBase.java new file mode 100644 index 00000000..8abc0968 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/EachAsBobBase.java @@ -0,0 +1,18 @@ +package org.yarnandtail.andhow.junit5.usagetests; + + +import org.yarnandtail.andhow.junit5.*; + +/** + * A base class for the 'Usage3' test to test the interaction of tests w/ superclasses. + */ +@ConfigFromFileBeforeEachTest(value = "Conf1And2AsBob.properties", includeClasses = {Conf1.class, Conf2.class, Conf3.class}) +class EachAsBobBase { + + //Overriding this method does not cause the annotation to be applied to the overridden method + @ConfigFromFileBeforeThisTest(value = "Conf1OnlyAsDeb.properties", includeClasses = {Conf1.class}) + public void test2() { + + } + +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/EachAsCarlBase.java b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/EachAsCarlBase.java new file mode 100644 index 00000000..d177085f --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/java/org/yarnandtail/andhow/junit5/usagetests/EachAsCarlBase.java @@ -0,0 +1,13 @@ +package org.yarnandtail.andhow.junit5.usagetests; + + +import org.yarnandtail.andhow.junit5.ConfigFromFileBeforeEachTest; +import org.yarnandtail.andhow.junit5.ConfigFromFileBeforeThisTest; + +/** + * A base class for the 'Usage3' test to test the interaction of tests w/ superclasses. + */ +@ConfigFromFileBeforeEachTest(value = "Conf1And2AsCarl.properties", includeClasses = {Conf1.class, Conf2.class, Conf3.class}) +class EachAsCarlBase { + +} \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFile.properties b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFile.properties new file mode 100644 index 00000000..b7813761 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFile.properties @@ -0,0 +1 @@ +MY_PROP: Bob \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFile2.properties b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFile2.properties new file mode 100644 index 00000000..3091981b --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFile2.properties @@ -0,0 +1 @@ +MY_PROP: Bob2 \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFileNest1.properties b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFileNest1.properties new file mode 100644 index 00000000..33f9012a --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFileNest1.properties @@ -0,0 +1 @@ +MY_PROP: BobNest1 \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFileNest2.properties b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFileNest2.properties new file mode 100644 index 00000000..a994333c --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/ext/MyPropFileNest2.properties @@ -0,0 +1 @@ +MY_PROP: BobNest2 \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/Conf1And2AsBob.properties b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/Conf1And2AsBob.properties new file mode 100644 index 00000000..830b9c93 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/Conf1And2AsBob.properties @@ -0,0 +1,4 @@ +CONF1_MY_PROP: Bob +CONF1_INNER1_MY_PROP: Bob +CONF2.MY.PROP: Bob +CONF2_INNER1_MY_PROP: Bob \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/Conf1And2AsCarl.properties b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/Conf1And2AsCarl.properties new file mode 100644 index 00000000..01ecc89f --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/Conf1And2AsCarl.properties @@ -0,0 +1,4 @@ +CONF1_MY_PROP: Carl +CONF1_INNER1_MY_PROP: Carl +CONF2.MY.PROP: Carl +CONF2_INNER1_MY_PROP: Carl \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/Conf1OnlyAsDeb.properties b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/Conf1OnlyAsDeb.properties new file mode 100644 index 00000000..c3533e12 --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/Conf1OnlyAsDeb.properties @@ -0,0 +1,2 @@ +CONF1_MY_PROP: Deb +CONF1_INNER1_MY_PROP: Deb \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/MyPropFile2.properties b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/MyPropFile2.properties new file mode 100644 index 00000000..3091981b --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/MyPropFile2.properties @@ -0,0 +1 @@ +MY_PROP: Bob2 \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/MyPropFileNest1.properties b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/MyPropFileNest1.properties new file mode 100644 index 00000000..33f9012a --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/MyPropFileNest1.properties @@ -0,0 +1 @@ +MY_PROP: BobNest1 \ No newline at end of file diff --git a/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/MyPropFileNest2.properties b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/MyPropFileNest2.properties new file mode 100644 index 00000000..a994333c --- /dev/null +++ b/junit5-extensions/junit5-extensions-with-andhow-dependency/src/test/resources/org/yarnandtail/andhow/junit5/usagetests/MyPropFileNest2.properties @@ -0,0 +1 @@ +MY_PROP: BobNest2 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 93f01e36..d75082f9 100644 --- a/pom.xml +++ b/pom.xml @@ -72,6 +72,8 @@ UTF-8 + 1.8 + 1.8 none true true @@ -106,22 +108,11 @@ - org.junit.platform - junit-platform-launcher - 1.7.2 - test - - - org.junit.jupiter - junit-jupiter-api - 5.7.2 - test - - - org.junit.jupiter - junit-jupiter-engine - 5.7.2 - test + org.junit + junit-bom + 5.9.1 + pom + import org.hamcrest @@ -140,12 +131,6 @@ 4.13.1 test - - org.junit.vintage - junit-vintage-engine - 5.7.2 - test - org.springframework @@ -205,14 +190,17 @@ org.junit.platform junit-platform-launcher + test org.junit.jupiter junit-jupiter-engine + test org.junit.jupiter junit-jupiter-api + test org.hamcrest