Repository: authorjapps/zerocode
Pull request #24 (2018-02-03 21:27:32)

Issue 23 env prop


Patch:
diff --git a/pom.xml b/pom.xml
index 253c807b..ffd0ad8b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -149,11 +149,6 @@
             <artifactId>guava</artifactId>
             <version>${guava.version}</version>
         </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-databind</artifactId>
-            <version>${jackson-databind.version}</version>
-        </dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-annotations</artifactId>
diff --git a/src/main/java/org/jsmart/zerocode/core/domain/EnvProperty.java b/src/main/java/org/jsmart/zerocode/core/domain/EnvProperty.java
new file mode 100644
index 00000000..b55387a2
--- /dev/null
+++ b/src/main/java/org/jsmart/zerocode/core/domain/EnvProperty.java
@@ -0,0 +1,12 @@
+package org.jsmart.zerocode.core.domain;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface EnvProperty {
+    String value();
+}
diff --git a/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodePackageRunner.java b/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodePackageRunner.java
index 8bd6f4a5..62fe7f25 100644
--- a/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodePackageRunner.java
+++ b/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodePackageRunner.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 
 import static com.google.inject.Guice.createInjector;
+import static org.jsmart.zerocode.core.utils.RunnerUtils.getEnvSpecificConfigFile;
 
 public class ZeroCodePackageRunner extends ParentRunner<ScenarioSpec> {
     private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ZeroCodePackageRunner.class);
@@ -160,6 +161,8 @@ public class ZeroCodePackageRunner extends ParentRunner<ScenarioSpec> {
         final TargetEnv envAnnotation = testClass.getAnnotation(TargetEnv.class);
         String serverEnv = envAnnotation != null? envAnnotation.value() : "config_hosts.properties";
 
+        serverEnv = getEnvSpecificConfigFile(serverEnv, testClass);
+
         final UseHttpClient runtimeClientAnnotated = testClass.getAnnotation(UseHttpClient.class);
         Class<? extends BasicHttpClient> runtimeHttpClient = runtimeClientAnnotated != null ? runtimeClientAnnotated.value() : RestEasyDefaultHttpClient.class;
 
diff --git a/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeUnitRunner.java b/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeUnitRunner.java
index 946d09a4..94419a59 100644
--- a/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeUnitRunner.java
+++ b/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeUnitRunner.java
@@ -4,6 +4,7 @@ import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.util.Modules;
 
+import org.jsmart.zerocode.core.domain.EnvProperty;
 import org.jsmart.zerocode.core.engine.listener.ZeroCodeTestReportListener;
 import org.junit.runner.Description;
 import org.junit.runner.notification.Failure;
@@ -26,12 +27,18 @@ import org.jsmart.zerocode.core.report.ZeroCodeReportGenerator;
 import org.jsmart.zerocode.core.utils.SmartUtils;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
+import static org.jsmart.zerocode.core.utils.RunnerUtils.getEnvSpecificConfigFile;
+import static org.jsmart.zerocode.core.utils.SmartUtils.getAllTokens;
+import static org.jsmart.zerocode.core.utils.SmartUtils.getEnvPropertyValue;
+
 public class ZeroCodeUnitRunner extends BlockJUnit4ClassRunner {
-    private static final Logger logger = LoggerFactory.getLogger(ZeroCodeUnitRunner.class);
-    
+    private static final Logger LOGGER = LoggerFactory.getLogger(ZeroCodeUnitRunner.class);
+
     static int i = 1;
     protected boolean passed;
     protected boolean testRunCompleted;
@@ -39,7 +46,7 @@ public class ZeroCodeUnitRunner extends BlockJUnit4ClassRunner {
     private final Class<?> testClass;
     Injector injector;
     SmartUtils smartUtils;
-    
+
     private HostProperties hostProperties;
     private String host;
     private String context;
@@ -52,27 +59,27 @@ public class ZeroCodeUnitRunner extends BlockJUnit4ClassRunner {
      */
     List<String> smartTestCaseNames = new ArrayList<>();
     String currentTestCase;
-    
+
     public ZeroCodeUnitRunner(Class<?> klass) throws InitializationError {
         super(klass);
-        
+
         this.testClass = klass;
         this.smartUtils = getInjectedSmartUtilsClass();
-        
+
         smartTestCaseNames = getSmartChildrenList();
 
         /*
          * Read the host, port, context etc from the inline annotation instead of a properties file
          */
         hostProperties = testClass.getAnnotation(HostProperties.class);
-        
+
         if (hostProperties != null) {
             host = hostProperties.host();
             port = hostProperties.port();
             context = hostProperties.context();
         }
     }
-    
+
     private List<String> getSmartChildrenList() {
         List<FrameworkMethod> children = getChildren();
         children.forEach(
@@ -85,42 +92,42 @@ public class ZeroCodeUnitRunner extends BlockJUnit4ClassRunner {
                             }
                         }
                         );
-        
+
         return smartTestCaseNames;
     }
-    
+
     @Override
     public void run(RunNotifier notifier) {
         notifier.addListener(new ZeroCodeTestReportListener(smartUtils.getMapper(), getInjectedReportGenerator()));
         super.run(notifier);
     }
-    
+
     @Override
     protected void runChild(FrameworkMethod method, RunNotifier notifier) {
-        
+
         final Description description = describeChild(method);
-        
+
         if (isIgnored(method)) {
             notifier.fireTestIgnored(description);
         } else {
             JsonTestCase annotation = method.getMethod().getAnnotation(JsonTestCase.class);
-            
+
             if (annotation != null) {
                 currentTestCase = annotation.value();
             } else {
                 currentTestCase = method.getName();
             }
-            
+
             notifier.fireTestStarted(description);
-            
-            logger.debug("### Running currentTestCase : " + currentTestCase);
-            
+
+            LOGGER.debug("### Running currentTestCase : " + currentTestCase);
+
             ScenarioSpec child = null;
             try {
                 child = smartUtils.jsonFileToJava(currentTestCase, ScenarioSpec.class);
-                
-                logger.debug("### Found currentTestCase : -" + child);
-                
+
+                LOGGER.debug("### Found currentTestCase : -" + child);
+
                 final ZeroCodeMultiStepsScenarioRunner multiStepsRunner = getInjectedMultiStepsRunner();
 
             /*
@@ -133,60 +140,62 @@ public class ZeroCodeUnitRunner extends BlockJUnit4ClassRunner {
                     ((ZeroCodeMultiStepsScenarioRunnerImpl) multiStepsRunner).overrideApplicationContext(context);
                 }
                 passed = multiStepsRunner.runScenario(child, notifier, description);
-                
+
             } catch (Exception ioEx) {
                 ioEx.printStackTrace();
                 notifier.fireTestFailure(new Failure(description, ioEx));
             }
-            
+
             testRunCompleted = true;
-            
+
             if (passed) {
-                logger.info(String.format("\n**FINISHED executing all Steps for [%s] **.\nSteps were:%s",
+                LOGGER.info(String.format("\n**FINISHED executing all Steps for [%s] **.\nSteps were:%s",
                                 child.getScenarioName(),
                                 child.getSteps().stream().map(step -> step.getName()).collect(Collectors.toList())));
             }
-            
+
             notifier.fireTestFinished(description);
-    
+
         }
-        
+
     }
-    
+
     public List<String> getSmartTestCaseNames() {
         return smartTestCaseNames;
     }
-    
+
     public String getCurrentTestCase() {
         return currentTestCase;
     }
-    
+
     private ZeroCodeMultiStepsScenarioRunner getInjectedMultiStepsRunner() {
         zeroCodeMultiStepsScenarioRunner = getMainModuleInjector().getInstance(ZeroCodeMultiStepsScenarioRunner.class);
         return zeroCodeMultiStepsScenarioRunner;
     }
-    
+
     public Injector getMainModuleInjector() {
         // TODO: Synchronise this with an object lock e.g. synchronized (IptSmartRunner.class) {}
         final TargetEnv envAnnotation = testClass.getAnnotation(TargetEnv.class);
         String serverEnv = envAnnotation != null ? envAnnotation.value() : "config_hosts.properties";
-        
+
+        serverEnv = getEnvSpecificConfigFile(serverEnv, testClass);
+
         final UseHttpClient httpClientAnnotated = testClass.getAnnotation(UseHttpClient.class);
         Class<? extends BasicHttpClient> runtimeHttpClient =
                         httpClientAnnotated != null ? httpClientAnnotated.value() : RestEasyDefaultHttpClient.class;
-        
+
         injector = Guice.createInjector(Modules.override(new ApplicationMainModule(serverEnv))
                         .with(new RuntimeHttpClientModule(runtimeHttpClient)));
-        
+
         return injector;
     }
-    
+
     protected SmartUtils getInjectedSmartUtilsClass() {
         return getMainModuleInjector().getInstance(SmartUtils.class);
     }
-    
+
     protected ZeroCodeReportGenerator getInjectedReportGenerator() {
         return getMainModuleInjector().getInstance(ZeroCodeReportGenerator.class);
     }
-    
+
 }
diff --git a/src/main/java/org/jsmart/zerocode/core/utils/RunnerUtils.java b/src/main/java/org/jsmart/zerocode/core/utils/RunnerUtils.java
new file mode 100644
index 00000000..5a48b2ff
--- /dev/null
+++ b/src/main/java/org/jsmart/zerocode/core/utils/RunnerUtils.java
@@ -0,0 +1,67 @@
+package org.jsmart.zerocode.core.utils;
+
+import org.jsmart.zerocode.core.domain.EnvProperty;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.jsmart.zerocode.core.utils.SmartUtils.getAllTokens;
+import static org.jsmart.zerocode.core.utils.SmartUtils.getEnvPropertyValue;
+
+/**
+ * This code is used by Unit and Package runner.
+ * Hence coded at a single place here.
+ */
+public class RunnerUtils {
+    private static final Logger LOGGER = LoggerFactory.getLogger(RunnerUtils.class);
+
+    public static String getEnvSpecificConfigFile(String serverEnv, Class<?> testClass) {
+        LOGGER.info("### testClass : " + testClass);
+
+        final EnvProperty envProperty = testClass.getAnnotation(EnvProperty.class);
+
+        if(envProperty == null){
+            return serverEnv;
+        }
+
+        String envPropNameWithPrefix = envProperty.value();
+
+        List<String> allTokens = getAllTokens(envPropNameWithPrefix);
+
+        if(allTokens.size() >= 1 && null != getEnvPropertyValue(allTokens.get(0))){
+
+            final String propertyKey = allTokens.get(0);
+            final String propertyValue = getEnvPropertyValue(propertyKey);
+
+            Map<String, String> paramMap = new HashMap<>();
+            paramMap.put(propertyKey, propertyValue);
+
+            final String resolvedEnvPropNameWithPrefix = SmartUtils.resolveToken(envPropNameWithPrefix, paramMap);
+
+            serverEnv = suffixEnvValue(serverEnv, resolvedEnvPropNameWithPrefix);
+
+            LOGGER.info("Found env specific property: '{}={}', Hence using: '{}'", propertyKey, propertyValue, serverEnv);
+
+        } else if(allTokens.size() >= 1) {
+
+            final String propertyKey = allTokens.get(0);
+
+            LOGGER.info("Could not find env value for env property '{}', So using '{}'", propertyKey, serverEnv);
+
+        } else {
+
+            LOGGER.info("Could not find env specific property, So using '{}'", serverEnv);
+
+        }
+
+        return serverEnv;
+    }
+
+    public static String suffixEnvValue(String serverEnv, String resolvedEnvPropNameWithPrefix) {
+        final String DOT_PROPERTIES = ".properties";
+        return serverEnv.replace(DOT_PROPERTIES, resolvedEnvPropNameWithPrefix + DOT_PROPERTIES);
+    }
+}
diff --git a/src/main/java/org/jsmart/zerocode/core/utils/SmartUtils.java b/src/main/java/org/jsmart/zerocode/core/utils/SmartUtils.java
index 85c4d459..218d68dc 100644
--- a/src/main/java/org/jsmart/zerocode/core/utils/SmartUtils.java
+++ b/src/main/java/org/jsmart/zerocode/core/utils/SmartUtils.java
@@ -9,20 +9,23 @@ import com.google.classpath.RegExpResourceFilter;
 import com.google.common.io.Resources;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import org.apache.commons.lang.text.StrSubstitutor;
 import org.jsmart.zerocode.core.di.ObjectMapperProvider;
 import org.jsmart.zerocode.core.domain.ScenarioSpec;
-import org.jsmart.zerocode.core.engine.executor.JsonServiceExecutorImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import static java.nio.charset.Charset.defaultCharset;
@@ -148,4 +151,38 @@ public class SmartUtils {
     public ObjectMapper getMapper() {
         return mapper;
     }
+
+    /**
+     * This method was introduced later,
+     * But Framework uses--> ZeroCodeJsonTestProcesorImpl#getAllTokens(java.lang.String)
+     */
+    public static List<String> getAllTokens(String aString) {
+
+        Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}");
+        Matcher matcher = pattern.matcher(aString);
+
+        List<String> keyTokens = new ArrayList<>();
+
+        while (matcher.find()) {
+            keyTokens.add(matcher.group(1));
+        }
+
+        return keyTokens;
+    }
+
+    public static String resolveToken(String stringWithToken, Map<String, String> paramMap) {
+        StrSubstitutor sub = new StrSubstitutor(paramMap);
+        return sub.replace(stringWithToken);
+    }
+
+    public static String getEnvPropertyValue(String envPropertyKey){
+
+        final String propertyValue = System.getProperty(envPropertyKey);
+
+        if (propertyValue != null) {
+            return propertyValue;
+        } else {
+            return System.getenv(envPropertyKey);
+        }
+    }
 }
diff --git a/src/main/java/org/jsmart/zerocode/core/zzignored/env/EnvVarReader.java b/src/main/java/org/jsmart/zerocode/core/zzignored/env/EnvVarReader.java
new file mode 100644
index 00000000..8b642ac2
--- /dev/null
+++ b/src/main/java/org/jsmart/zerocode/core/zzignored/env/EnvVarReader.java
@@ -0,0 +1,24 @@
+package org.jsmart.zerocode.core.zzignored.env;
+
+import java.util.Map;
+
+public class EnvVarReader {
+
+    public static void main(String[] args) {
+        final Map<String, String> envMap = System.getenv();
+        System.out.println("### getEnv ENV_NAME: " + System.getenv("ENV_NAME"));
+        System.out.println("### getProp ENV_NAME: " + System.getProperty("ENV_NAME"));
+
+        System.out.println("envMap: " + envMap);
+
+        System.out.println("getProperty : " + System.getProperty("JAVA_HOME"));
+        System.out.println("getEnv : " + System.getenv("JAVA_HOME"));
+        System.out.println("getEnv : " + System.getenv("XYXYXY"));
+
+
+        System.setProperty("NEW_VAR", "aNewValue");
+        System.out.println("\n\ngetProperty : " + System.getProperty("NEW_VAR"));
+        System.out.println("getEnv : " + System.getenv("NEW_VAR"));
+
+    }
+}


Test patch:
diff --git a/src/main/resources/01_verification_test_cases/04_custom_runtime_http_client.json b/src/main/resources/01_verification_test_cases/04_custom_runtime_http_client.json
index 210e4e88..7fd5843c 100644
--- a/src/main/resources/01_verification_test_cases/04_custom_runtime_http_client.json
+++ b/src/main/resources/01_verification_test_cases/04_custom_runtime_http_client.json
@@ -3,7 +3,7 @@
   "steps": [
     {
       "name": "getbathroom1",
-      "url": "http://localhost:9998/home/bathroom/1",
+      "url": "http://localhost:9998/home/bathroom/1", //<-- hard coded here to 9998, it doesnt pick from the properties file
       "operation": "GET",
       "request": {
         "headers": {
diff --git a/src/main/resources/01_verification_test_cases/09_host_port_annotated_bu_failed_assertion.json b/src/main/resources/01_verification_test_cases/09_host_port_annotated_bu_failed_assertion.json
index a73415c2..182ce680 100644
--- a/src/main/resources/01_verification_test_cases/09_host_port_annotated_bu_failed_assertion.json
+++ b/src/main/resources/01_verification_test_cases/09_host_port_annotated_bu_failed_assertion.json
@@ -2,7 +2,7 @@
   "scenarioName": "09_host_port_annotated_bu_failed_assertion.json",
   "steps": [
     {
-      "name": "get_hotel_resource", //A hotel resource
+      "name": "get_hotel_resource",
       "url": "/home/bathroom/google_id_01",
       "operation": "GET",
       "request": {
@@ -10,9 +10,9 @@
       "assertions": {
         "status": 200,
         "body": {
-          "id": 2,
-          "name": "No Shower",
-          "availability": false
+          "id" : 1,
+          "name" : "G Hotel Shower",
+          "availability" : true
         }
       }
     }
diff --git a/src/test/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesorImplTest.java b/src/test/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesorImplTest.java
index 00258878..0c67ff85 100644
--- a/src/test/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesorImplTest.java
+++ b/src/test/java/org/jsmart/zerocode/core/engine/preprocessor/ZeroCodeJsonTestProcesorImplTest.java
@@ -49,6 +49,21 @@ public class ZeroCodeJsonTestProcesorImplTest {
 
     }
 
+    @Test
+    public void willEvaluatePlaceHolder() throws Exception {
+
+        String aString = "Hello_${WORLD}";
+        List<String> placeHolders = jsonPreProcessor.getAllTokens(aString);
+        assertThat(placeHolders.size(), is(1));
+        assertThat(placeHolders.get(0), is("WORLD"));
+
+        aString = "Hello_${$.step_name}";
+        placeHolders = jsonPreProcessor.getAllTokens(aString);
+        assertThat(placeHolders.size(), is(1));
+        assertThat(placeHolders.get(0), is("$.step_name"));
+
+    }
+    
     @Test
     public void willResolveWithParamMap() throws Exception {
         ScenarioSpec scenarioSpec = smartUtils.jsonFileToJava("09_test_engine/01_request_with_place_holders.json", ScenarioSpec.class);
diff --git a/src/test/java/org/jsmart/zerocode/core/envprop/suite/NewPortTestZeroCodeUnitRunner.java b/src/test/java/org/jsmart/zerocode/core/envprop/suite/NewPortTestZeroCodeUnitRunner.java
new file mode 100644
index 00000000..3dac4aa5
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/core/envprop/suite/NewPortTestZeroCodeUnitRunner.java
@@ -0,0 +1,22 @@
+package org.jsmart.zerocode.core.envprop.suite;
+
+import org.jsmart.simulator.main.SimpleRestJsonSimulatorsMain;
+import org.jsmart.zerocode.core.runner.ZeroCodePackageRunner;
+import org.jsmart.zerocode.core.utils.SmartUtils;
+import org.junit.runners.model.InitializationError;
+
+public class NewPortTestZeroCodeUnitRunner extends ZeroCodePackageRunner {
+    SimpleRestJsonSimulatorsMain simulator ;
+    public static final int PORT = 9997;
+
+    public NewPortTestZeroCodeUnitRunner(Class<?> testClass) throws InitializationError {
+        super(testClass);
+        simulator = new SimpleRestJsonSimulatorsMain(PORT);
+        simulator.start();
+    }
+
+    public NewPortTestZeroCodeUnitRunner(Class<?> testClass, SmartUtils smartUtils) throws InitializationError {
+        super(testClass, smartUtils);
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/jsmart/zerocode/core/envprop/suite/ZeroCodeEnvPropertyReaderPackageTest.java b/src/test/java/org/jsmart/zerocode/core/envprop/suite/ZeroCodeEnvPropertyReaderPackageTest.java
new file mode 100644
index 00000000..91a811ef
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/core/envprop/suite/ZeroCodeEnvPropertyReaderPackageTest.java
@@ -0,0 +1,18 @@
+package org.jsmart.zerocode.core.envprop.suite;
+
+import org.jsmart.zerocode.core.domain.EnvProperty;
+import org.jsmart.zerocode.core.domain.TargetEnv;
+import org.jsmart.zerocode.core.domain.TestPackageRoot;
+import org.junit.runner.RunWith;
+
+// see "ENV_NAME=ci" in .bash_profile file or pass via 'mvn -DENV_NAME=ci'.
+// If not found, then defaults to "app_config.properties"
+
+@EnvProperty("_${ENV_NAME}")
+@TargetEnv("package_properties/app_config.properties")
+@TestPackageRoot("14_env_prop")
+@RunWith(NewPortTestZeroCodeUnitRunner.class)
+//@RunWith(ZeroCodePackageRunner.class)
+public class ZeroCodeEnvPropertyReaderPackageTest {
+
+}
diff --git a/src/test/java/org/jsmart/zerocode/core/envprop/unit/PropertiesFileFromEnvTest.java b/src/test/java/org/jsmart/zerocode/core/envprop/unit/PropertiesFileFromEnvTest.java
new file mode 100644
index 00000000..381fb944
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/core/envprop/unit/PropertiesFileFromEnvTest.java
@@ -0,0 +1,24 @@
+package org.jsmart.zerocode.core.envprop.unit;
+
+import org.jsmart.zerocode.core.domain.EnvProperty;
+import org.jsmart.zerocode.core.domain.JsonTestCase;
+import org.jsmart.zerocode.core.domain.TargetEnv;
+import org.jsmart.zerocode.core.domain.UseHttpClient;
+import org.jsmart.zerocode.core.httpclient.CustomRuntimeTestHttpClient;
+import org.jsmart.zerocode.core.tests.customrunner.TestOnlyZeroCodeUnitRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@UseHttpClient(CustomRuntimeTestHttpClient.class) //<--- This sets some header also eg "key1"
+@TargetEnv("config_hosts_test.properties")
+@EnvProperty("_${env_property_key_name}")
+@RunWith(TestOnlyZeroCodeUnitRunner.class)
+public class PropertiesFileFromEnvTest {
+
+    @Test
+    @JsonTestCase("01_verification_test_cases/04_custom_runtime_http_client.json")
+    public void testASmartTestCase_createUpdate() throws Exception {
+
+    }
+}
+
diff --git a/src/test/java/org/jsmart/zerocode/core/envprop/unit/ZeroCodeEnvPropertyReaderTest.java b/src/test/java/org/jsmart/zerocode/core/envprop/unit/ZeroCodeEnvPropertyReaderTest.java
new file mode 100644
index 00000000..5521c10b
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/core/envprop/unit/ZeroCodeEnvPropertyReaderTest.java
@@ -0,0 +1,20 @@
+package org.jsmart.zerocode.core.envprop.unit;
+
+import org.jsmart.zerocode.core.domain.EnvProperty;
+import org.jsmart.zerocode.core.domain.JsonTestCase;
+import org.jsmart.zerocode.core.domain.TargetEnv;
+import org.jsmart.zerocode.core.tests.customrunner.TestOnlyZeroCodeUnitRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@TargetEnv("app_config.properties")
+@EnvProperty("_${ENV_NAME}") // see "ENV_NAME=ci" in .bash_profile file. If not found, then defaults to "app_config.properties"
+@RunWith(TestOnlyZeroCodeUnitRunner.class)
+public class ZeroCodeEnvPropertyReaderTest {
+
+    @Test
+    @JsonTestCase("14_env_prop/22_env_property_dynamic_runtime.json")
+    public void testRunAgainstConfigPropertySetViaJenkins() throws Exception {
+        
+    }
+}
diff --git a/src/test/java/org/jsmart/zerocode/core/tests/customrunner/TestOnlyZeroCodePackageRunner.java b/src/test/java/org/jsmart/zerocode/core/tests/customrunner/TestOnlyZeroCodePackageRunner.java
index a7197e63..6ffbeadb 100644
--- a/src/test/java/org/jsmart/zerocode/core/tests/customrunner/TestOnlyZeroCodePackageRunner.java
+++ b/src/test/java/org/jsmart/zerocode/core/tests/customrunner/TestOnlyZeroCodePackageRunner.java
@@ -3,6 +3,9 @@ package org.jsmart.zerocode.core.tests.customrunner;
 import org.jsmart.simulator.main.SimpleRestJsonSimulatorsMain;
 import org.jsmart.zerocode.core.runner.ZeroCodePackageRunner;
 import org.jsmart.zerocode.core.utils.SmartUtils;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Test;
 import org.junit.runners.model.InitializationError;
 
 public class TestOnlyZeroCodePackageRunner extends ZeroCodePackageRunner {
@@ -19,4 +22,10 @@ public class TestOnlyZeroCodePackageRunner extends ZeroCodePackageRunner {
         super(testClass, smartUtils);
     }
 
+    @AfterClass
+    public void tearDown() throws Exception {
+
+        System.out.println("################# finished 2");
+
+    }
 }
diff --git a/src/test/java/org/jsmart/zerocode/core/tests/customrunner/TestOnlyZeroCodeUnitRunner.java b/src/test/java/org/jsmart/zerocode/core/tests/customrunner/TestOnlyZeroCodeUnitRunner.java
index b7f4e432..fd6dbf68 100644
--- a/src/test/java/org/jsmart/zerocode/core/tests/customrunner/TestOnlyZeroCodeUnitRunner.java
+++ b/src/test/java/org/jsmart/zerocode/core/tests/customrunner/TestOnlyZeroCodeUnitRunner.java
@@ -9,6 +9,9 @@ public class TestOnlyZeroCodeUnitRunner extends ZeroCodeUnitRunner {
     public static final int PORT = 9998;
 
     static {
+
+        System.setProperty("env_property_key_name", "ci"); //<--- See log n verify
+
         simulator = new SimpleRestJsonSimulatorsMain(PORT);
         simulator.start();
     }
diff --git a/src/test/java/org/jsmart/zerocode/core/utils/RunnerUtilsTest.java b/src/test/java/org/jsmart/zerocode/core/utils/RunnerUtilsTest.java
new file mode 100644
index 00000000..9c67f2c8
--- /dev/null
+++ b/src/test/java/org/jsmart/zerocode/core/utils/RunnerUtilsTest.java
@@ -0,0 +1,25 @@
+package org.jsmart.zerocode.core.utils;
+
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+public class RunnerUtilsTest {
+
+
+    @Test
+    public void testSuffixEnvValue() throws Exception {
+
+        assertThat(RunnerUtils.suffixEnvValue("abcd.properties", "_ci"), is("abcd_ci.properties"));
+
+        assertThat(RunnerUtils.suffixEnvValue("my_conf.properties", "_ci"), is("my_conf_ci.properties"));
+
+        assertThat(RunnerUtils.suffixEnvValue("myfolder/myapp.properties", "_ci"), is("myfolder/myapp_ci.properties"));
+
+        assertThat(RunnerUtils.suffixEnvValue("/myfolder/myapp.properties", "_ci"), is("/myfolder/myapp_ci.properties"));
+
+        assertThat(RunnerUtils.suffixEnvValue("/myfolder/myapp.properties", ""), is("/myfolder/myapp.properties"));
+
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java b/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java
index 9aeeda02..5510311c 100644
--- a/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java
+++ b/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java
@@ -6,17 +6,21 @@ import org.jsmart.zerocode.core.domain.ScenarioSpec;
 import org.jsmart.zerocode.core.domain.Step;
 import org.jukito.JukitoRunner;
 import org.jukito.TestModule;
+import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNull.nullValue;
 
 @RunWith(JukitoRunner.class)
 //@UseModules(ApplicationMainModule.class) //<--- Only if you dont pass any value to it's constructor
@@ -90,4 +94,53 @@ public class SmartUtilsTest {
         expectedException.expectMessage("Oops! Can not run with multiple Scenarios with same name.");
         smartUtils.checkDuplicateScenarios("04_test_flow_cases");
     }
+
+    @Test
+    public void willEvaluatePlaceHolder() throws Exception {
+
+        String aString = "Hello_${WORLD}";
+        List<String> placeHolders = SmartUtils.getAllTokens(aString);
+        Assert.assertThat(placeHolders.size(), is(1));
+        Assert.assertThat(placeHolders.get(0), is("WORLD"));
+
+        aString = "Hello_${$.step_name}";
+        placeHolders = SmartUtils.getAllTokens(aString);
+        Assert.assertThat(placeHolders.size(), is(1));
+        Assert.assertThat(placeHolders.get(0), is("$.step_name"));
+
+    }
+
+    @Test
+    public void testNullOrEmptyString_withPlaceHolders() throws Exception {
+
+        String aString = "";
+        List<String> placeHolders = SmartUtils.getAllTokens(aString);
+        Assert.assertThat(placeHolders.size(), is(0));
+
+        aString = "Hello_";
+        placeHolders = SmartUtils.getAllTokens(aString);
+        Assert.assertThat(placeHolders.size(), is(0));
+    }
+
+    @Test
+    public void testReplaceTokensOrPlaceHolders() throws Exception {
+        String aString = "_${ENV_PROPERTY_NAME}";
+
+        Map<String, String> paramMap = new HashMap<>();
+        paramMap.put("ENV_PROPERTY_NAME", "ci2");
+
+        final String resolvedString = SmartUtils.resolveToken(aString, paramMap);
+
+        assertThat(resolvedString, is("_ci2"));
+    }
+
+    @Test
+    public void testEnvValue() throws Exception {
+
+        final String javaHomeValue = SmartUtils.getEnvPropertyValue("JAVA_HOME");
+        assertThat(javaHomeValue, notNullValue());
+
+        assertThat(SmartUtils.getEnvPropertyValue("WRONG_XYZ_INVALID"), nullValue());
+
+    }
 }
\ No newline at end of file
diff --git a/src/test/resources/14_env_prop/22_env_property_dynamic_runtime.json b/src/test/resources/14_env_prop/22_env_property_dynamic_runtime.json
new file mode 100644
index 00000000..5b4659d2
--- /dev/null
+++ b/src/test/resources/14_env_prop/22_env_property_dynamic_runtime.json
@@ -0,0 +1,20 @@
+{
+  "scenarioName": "22_env_property_dynamic_runtime",
+  "steps": [
+    {
+      "name": "get_hotel_resource for ci jenkins",
+      "url": "/home/bathroom/google_id_01",
+      "operation": "GET",
+      "request": {
+      },
+      "assertions": {
+        "status": 200,
+        "body": {
+          "id" : 1,
+          "name" : "G Hotel Shower",
+          "availability" : true
+        }
+      }
+    }
+  ]
+}
diff --git a/src/test/resources/app_config.properties b/src/test/resources/app_config.properties
new file mode 100755
index 00000000..cc4273bd
--- /dev/null
+++ b/src/test/resources/app_config.properties
@@ -0,0 +1,6 @@
+#
+restful.application.endpoint.host=http://localhost
+restful.application.endpoint.port=9998
+
+# Google Map Service Context
+restful.application.endpoint.context=/google-map-services
diff --git a/src/test/resources/app_config_ci.properties b/src/test/resources/app_config_ci.properties
new file mode 100755
index 00000000..cc4273bd
--- /dev/null
+++ b/src/test/resources/app_config_ci.properties
@@ -0,0 +1,6 @@
+#
+restful.application.endpoint.host=http://localhost
+restful.application.endpoint.port=9998
+
+# Google Map Service Context
+restful.application.endpoint.context=/google-map-services
diff --git a/src/test/resources/config_hosts_test_ci.properties b/src/test/resources/config_hosts_test_ci.properties
new file mode 100755
index 00000000..671b5929
--- /dev/null
+++ b/src/test/resources/config_hosts_test_ci.properties
@@ -0,0 +1,6 @@
+#
+restful.application.endpoint.host=http://localhost-test
+restful.application.endpoint.port=88200
+
+# Google Map Service Context
+restful.application.endpoint.context=/google-map-services
diff --git a/src/test/resources/package_properties/app_config.properties b/src/test/resources/package_properties/app_config.properties
new file mode 100755
index 00000000..50c01f16
--- /dev/null
+++ b/src/test/resources/package_properties/app_config.properties
@@ -0,0 +1,6 @@
+#
+restful.application.endpoint.host=http://localhost
+restful.application.endpoint.port=9997
+
+# Google Map Service Context
+restful.application.endpoint.context=/google-map-services
diff --git a/src/test/resources/package_properties/app_config_ci.properties b/src/test/resources/package_properties/app_config_ci.properties
new file mode 100755
index 00000000..50c01f16
--- /dev/null
+++ b/src/test/resources/package_properties/app_config_ci.properties
@@ -0,0 +1,6 @@
+#
+restful.application.endpoint.host=http://localhost
+restful.application.endpoint.port=9997
+
+# Google Map Service Context
+restful.application.endpoint.context=/google-map-services