Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue736 unit testing w prop files take2 #742

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.yarnandtail.andhow.testutil;

import java.util.*;
import java.util.function.UnaryOperator;

/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -163,17 +164,27 @@ public static <T> 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* The locator takes a default Configuration to return if a configuration cannot
* be found otherwise. See <code></code>org.yarnandtail.AndHow#findConfig()</code> 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).
* <p>
* Example setting to a custom locator:
* <pre>{@code setAndHowConfigLocator(c -> return MyConfig); }</pre>
* <pre>{@code setAndHowConfigLocator( (c) -> new MyAndHowConfig() ) }</pre>
* Example setting back to null:
* <pre>{@code setAndHowConfigLocator(null); }</pre>
* <p>
Expand All @@ -192,6 +203,18 @@ public static <T> UnaryOperator<T> setAndHowConfigLocator(UnaryOperator<T> newLo
getAndHowClass(), "configLocator", newLocator);
}

public static List<Class<?>> setConfigurationOverrideGroups(
Object configurationInstance, List<Class<?>> classList) {

return ReflectionTestUtils.setInstanceFieldValue(
configurationInstance, "overrideGroups", classList, List.class);
}

public static List<Class<?>> setConfigurationOverrideGroups(
Object configurationInstance, Class<?> clazz) {
return setConfigurationOverrideGroups(configurationInstance, Arrays.asList(clazz));
}

/**
* Forces the AndHow inProcessConfig to a new value.
*
Expand Down Expand Up @@ -274,5 +297,4 @@ private static Class<?> getAndHowInitializationClass() {
public static Class<?> getAndHowConfigurationClass() {
return ReflectionTestUtils.getClassByName("org.yarnandtail.andhow.AndHowConfiguration");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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", "/");
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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", "/");
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -13,14 +46,23 @@ public class ExtensionBase {
* <li>The test class instance that is invoking this extension</li>
* </ul>
*
* 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());
}

/**
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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());
}

/**
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading