Created
April 10, 2020 15:45
-
-
Save matthias-dirickx/77beee2d2da758386dbe7f6a012f046c to your computer and use it in GitHub Desktop.
Guard test context over classes. Designed to be thread-safe, but not guarnateed to be threadsafe. In response to the need to having a flexible test context without adding DI (which might often be the better option if I have to believe the google search results to this issue).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package be.mdi.test.utils; | |
import java.util.HashMap; | |
public class TestContext { | |
/* | |
* TestContext Class Methods | |
* | |
*/ | |
/** | |
* Variable holding the map of maps for the stored variables. | |
*/ | |
private ScenarioVariableHolder scenarioVariableHolder; | |
/** | |
* <p>Private constructor.</P> | |
* <p>Used by the singleton implementation only.</p> | |
* | |
* <p>The sole responsibility is to instantiate the ScenarioVariableHolder object into the created singleton.</p> | |
*/ | |
private TestContext() { | |
scenarioVariableHolder = new ScenarioVariableHolder(); | |
} | |
/** | |
* <p>Implemented for testing purposes mainly.</p> | |
* <p>Reset is a blunt method that initializes the ScenarioVariableHolder again.</p> | |
* <p>In real-life cases this may be a bad idea because it may be reset by one tread while another thread still depends on it.</p> | |
*/ | |
private void reset() { | |
scenarioVariableHolder = new ScenarioVariableHolder(); | |
} | |
/* | |
* Singleton access methods. | |
* | |
* Bill Pugh Singleton should be thread-safe for parallel access in multicore environments. | |
* | |
*/ | |
/** | |
* Private static inner class that holds the singleton. | |
* | |
* @author Matthias.Dirickx | |
*/ | |
private static class TestContextSingleton { | |
private static final TestContext instance = new TestContext(); | |
} | |
/** | |
* <p>Add a variable to the collection.</p> | |
* <p>The first identifier is for the Scenario or Test Case.</p> | |
* <p>The two other variables are for the variable identifier (the name), | |
* and the variable value (the data).</p> | |
* <p>The {@link Scenario} object is called with the {@link Scenario#getId()} method | |
* to get the Id. The id is stored in the map rather than the scenario object | |
* because the id is thought to be immutable during execution | |
* (path + line number of scenario name). The scenario will be updated | |
* and will likely not be equal ( == ) to the stored scenario object.</p> | |
* | |
* | |
* @param scenario <p>{@link Scenario} - must be taken from the step defs to pass on. | |
* @param name <p>{@link String} - must be unique. Duplicate name for a scenario will throw an IllegalArgumentException.</p> | |
* @param data <p>{@link Object} - any object. Note that this means that you can pass Integer, but not the primitive int as that is not an object.</p> | |
*/ | |
public static void add(String identifier, String name, Object data) { | |
TestContextSingleton.instance.scenarioVariableHolder.add(identifier, name, data); | |
} | |
/** | |
* <p>Get a variable with name {@code name} from Scenario {@code scenario} | |
* as type {@code clazz}.</p> | |
* <p>The generic implemntation allows to store random objects and to retreive them this way. | |
* This does put the responsibility of knowing which type it is with the client.</p> | |
* <p>This is not outrageous as the code retrieving the variable should know what it's going to get.</p> | |
* | |
* @param clazz <p>{@link Class<T>} - A class object with the requested return type.</p> | |
* @param scenario <p>{@link Scenario} - The Scenario you request the variable for. </p> | |
* @param name <p>{@link String} - The name of the variable.</p> | |
* @return T <p>Returns an object cast to Class T. This allows for generically returning variables as long a the client knows what he's doing.</p> | |
*/ | |
public static <T> T get(Class<T> clazz, String identifier, String name) { | |
return (T)TestContextSingleton.instance.scenarioVariableHolder.get(clazz, identifier, name); | |
} | |
/** | |
* Returns the number of scenario's stored. | |
* | |
* @return {@link int} | |
*/ | |
public static int getScenarioCount() { | |
return TestContextSingleton.instance.scenarioVariableHolder.getScenarioCount(); | |
} | |
/** | |
* <p>Returns the number of variables stored for a given scenario.</p> | |
* | |
* @param scenario <p>{@link Scenario} - The scenario to get the count for.</p> | |
* @return {@link int} | |
*/ | |
public static int getVariableCount(String identifier) { | |
return TestContextSingleton.instance.scenarioVariableHolder.getVarCount(identifier); | |
} | |
/** | |
* <p>Remove all variables for a given scenario as well as the scenario itself form the map.</p> | |
* @param scenario <p>{@link Scenario} - Scenario to delete.</p> | |
*/ | |
public static void flush(String identifier) { | |
TestContextSingleton.instance.scenarioVariableHolder.flush(identifier); | |
} | |
/** | |
* <p>Implemented for testing purposes.</p> | |
* <p>This resets the singleton's variable used to store the data.</p> | |
* <p>Unless it makes sense (like in testing this class where you need | |
* to reset the instance between cases) this should not be used</p> | |
*/ | |
public static void resetSingleton() { | |
TestContextSingleton.instance.reset(); | |
} | |
/* | |
* Structural Inner Classes: | |
* - ScenarioVariableHolder: a Map of Maps that links a scenario object to a variables. | |
* - SceanarioVariables: a Map of String to Object with the String representing the name. | |
* | |
* The name must be unique within the map of the scenario. | |
* The update with the put is checked with a if same value expression returning an error. | |
* | |
*/ | |
/** | |
* <p>Store for a defined map of maps.</p> | |
* | |
* <p>The storage is a map of String (as key) to {@link ScenarioVariables}, | |
* holding the key/value pairs for the actual variables for the scenario.</p> | |
* | |
* @author Matthias.Dirickx | |
* | |
*/ | |
private class ScenarioVariableHolder { | |
private HashMap<String, ScenarioVariables> scenarioVarCollection; | |
ScenarioVariableHolder() { | |
scenarioVarCollection = new HashMap<String, ScenarioVariables>(); | |
} | |
private void add(String id, String name, Object data) { | |
if(scenarioVarCollection.containsKey(id)) { | |
scenarioVarCollection.get(id).add(name, data); | |
} else { | |
scenarioVarCollection.put(id, new ScenarioVariables().add(name, data)); | |
} | |
} | |
private <T> T get(Class<T> clazz, String id, String name) { | |
return (T)scenarioVarCollection.get(id).get(clazz, name); | |
} | |
private void flush(String id) { | |
if(scenarioVarCollection.containsKey(id)) { | |
scenarioVarCollection.remove(id); | |
} | |
} | |
/* | |
* Utility functions for testing purposes. | |
*/ | |
private int getScenarioCount() { | |
return scenarioVarCollection.size(); | |
} | |
private int getVarCount(String id) { | |
return scenarioVarCollection.get(id).getCount(); | |
} | |
} | |
/** | |
* A defined map of String to object to hold a list of uniquely defined variables. | |
* It is the client responsibility to manage unique names per scenario. | |
* @author Matthias.Dirickx | |
* | |
*/ | |
private class ScenarioVariables { | |
private HashMap<String, Object> vars; | |
ScenarioVariables() { | |
vars = new HashMap<String, Object>(); | |
} | |
public ScenarioVariables add(String name, Object data) throws IllegalArgumentException { | |
if(vars.containsKey(name)) { | |
throw new IllegalArgumentException( | |
String | |
.format( | |
"The map for the scenario already contains the key '{}'", | |
name) | |
); | |
} | |
vars.put(name, data); | |
return this; | |
} | |
@SuppressWarnings("unchecked") | |
public <T> T get(Class<T> clazz, String name) { | |
return (T)vars.get(name); | |
} | |
/* | |
* Utility function for testing purposes. | |
*/ | |
public int getCount() { | |
return vars.size(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment