Repository: authorjapps/zerocode
Pull request #354 (2020-01-03 12:00:47)

ISS-353 validators help n example in README
# <Feature Title>

PR Branch
**_#ADD LINK TO THE PR BRANCH_**

## Motivation and Context

## Checklist:

* [ ] Unit tests added?

* [ ] Integration tests added?

* [ ] Test names are meaningful?

* [ ] Feature manually tested?

* [ ] Branch build passed?

* [ ] No 'package.*' in the imports

* [ ] Relevant Wiki page updated with clear instruction for the end user

* [ ] Http test added to `http-testing` module(if applicable) ?

* [ ] Kafka test added to `kafka-testing` module(if applicable) ?



Patch:
diff --git a/README.md b/README.md
index 1609cc53..5086b032 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Automated API testing has never been so easy
 **Gitter IM:** [Gitter](https://gitter.im/zerocode-testing/help-and-usage) <br/>
 
 
-Zerocode makes it easy to create and maintain automated tests with absolute minimum overhead for [REST](https://github.com/authorjapps/zerocode/wiki/User-journey:-Create,-Update-and-GET-Employee-Details),[SOAP](https://github.com/authorjapps/zerocode/blob/master/README.md#soap-method-invocation-example-with-xml-input), [Kafka](https://github.com/authorjapps/zerocode/wiki/Kafka-Testing-Introduction), [DB services](https://github.com/authorjapps/zerocode/wiki/Sample-DB-SQL-Executor) and more. It has the best of best ideas and practices from the community to keep it super simple, and the adoption is rapidly growing among the developer/tester community.
+Zerocode makes it easy to create and maintain automated tests with absolute minimum overhead for [REST](https://github.com/authorjapps/zerocode/wiki/User-journey:-Create,-Update-and-GET-Employee-Details),[SOAP](https://github.com/authorjapps/zerocode/blob/master/README.md#soap-method-invocation-example-with-xml-input), [Kafka Real Time Data Streams](https://github.com/authorjapps/zerocode/wiki/Kafka-Testing-Introduction) and much more. It has the best of best ideas and practices from the community to keep it super simple, and the adoption is rapidly growing among the developer/tester community.
 
 Quick Links
 ===
@@ -56,9 +56,66 @@ then, we can easily validate the above API using `Zerocode` like below.
 
 + Using YAML described as below,
 
-> _The beauty here is, we can use the payload/headers structure as it is without any manipulation._
+> _The beauty here is, we can use the payload/headers structure for validation as it is without any manipulation or use a flat JSON path to skip the hassles of the entire object hierarchies._
+
+## Validators
 
 ```yaml
+
+---
+url: api/v1/customers/123
+method: GET
+request:
+  headers:
+    Content-Type: application/json
+retry:
+  max: 3
+  delay: 1000
+validators:
+- field: "$.status"
+  value: 200
+- field: "$.body.type"
+  value: Premium Visa
+- field: "$.body.addresses[0].line1"
+  value: 10 Random St
+```
+
+or
+
+```
+{
+  "url": "api/v1/customers/123",
+  "method": "GET",
+  "request": {
+    "headers": {
+      "Content-Type": "application/json"
+    }
+  },
+  "retry": {
+    "max": 3,
+    "delay": 1000
+  },
+  "validators": [
+    {
+      "field": "$.status",
+      "value": 200
+    },
+    {
+      "field": "$.body.type",
+      "value": "Premium Visa"
+    },
+    {
+      "field": "$.body.addresses[0].line1",
+      "value": "10 Random St"
+    }
+  ]
+}
+```
+
+## Matchers
+
+```yaml
+
 ---
 url: api/v1/customers/123
 method: GET
@@ -131,6 +188,6 @@ and run it simply by pointing to the above JSON/YAML file from a JUnit `@Test` m
 
 Looks simple n easy? Why not give it a try? Visit the [quick-start guide](https://github.com/authorjapps/zerocode/wiki/Getting-Started) or [user's guide](https://github.com/authorjapps/zerocode/wiki#developer-guide) for more insight.
 
-Zerocode is used by many companies such as Vocalink, HSBC, HomeOffice(Gov) and [many others](https://github.com/authorjapps/zerocode/wiki#smart-projects-using-zerocode) to achieve accurate production drop of their microservices. 
+Zerocode is used by many companies such as Vocalink, HSBC, HomeOffice(Gov) and [many others](https://github.com/authorjapps/zerocode/wiki#smart-projects-using-zerocode) to achieve accurate production drop of their microservices. Learn here about [Validators Vs Matchers](https://github.com/authorjapps/zerocode/wiki).
 
 Happy testing! <g-emoji class="g-emoji" alias="panda_face" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f43c.png">🐼</g-emoji>
diff --git a/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java b/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java
index 9bf5685d..b561cf2c 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/di/main/ApplicationMainModule.java
@@ -21,6 +21,8 @@ import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeExternalFileProcesso
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeExternalFileProcessorImpl;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeParameterizedProcessor;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeParameterizedProcessorImpl;
+import org.jsmart.zerocode.core.engine.validators.ZeroCodeValidator;
+import org.jsmart.zerocode.core.engine.validators.ZeroCodeValidatorImpl;
 import org.jsmart.zerocode.core.report.ZeroCodeReportGenerator;
 import org.jsmart.zerocode.core.report.ZeroCodeReportGeneratorImpl;
 import org.jsmart.zerocode.core.runner.ZeroCodeMultiStepsScenarioRunner;
@@ -59,6 +61,7 @@ public class ApplicationMainModule extends AbstractModule {
         bind(HttpApiExecutor.class).to(HttpApiExecutorImpl.class);
         bind(JavaMethodExecutor.class).to(JavaMethodExecutorImpl.class);
         bind(ZeroCodeAssertionsProcessor.class).to(ZeroCodeAssertionsProcessorImpl.class);
+        bind(ZeroCodeValidator.class).to(ZeroCodeValidatorImpl.class);
         bind(ZeroCodeReportGenerator.class).to(ZeroCodeReportGeneratorImpl.class);
         bind(ZeroCodeExternalFileProcessor.class).to(ZeroCodeExternalFileProcessorImpl.class);
         bind(ZeroCodeParameterizedProcessor.class).to(ZeroCodeParameterizedProcessorImpl.class);
diff --git a/core/src/main/java/org/jsmart/zerocode/core/domain/Step.java b/core/src/main/java/org/jsmart/zerocode/core/domain/Step.java
index ac653177..0ac29945 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/domain/Step.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/domain/Step.java
@@ -20,6 +20,7 @@ public class Step {
     private final String operation;
     private final String url;
     private final JsonNode request;
+    private final List<Validator> validators;
     private final JsonNode assertions;
     private final String verifyMode;
     private final JsonNode verify;
@@ -57,6 +58,10 @@ public class Step {
         return request;
     }
 
+    public List<Validator> getValidators() {
+        return validators;
+    }
+
     public JsonNode getAssertions() {
         return assertions;
     }
@@ -114,12 +119,14 @@ public class Step {
             @JsonProperty("method") String method,
             @JsonProperty("url") String url,
             @JsonProperty("request") JsonNode request,
+            @JsonProperty("validators") List<Validator> validators,
             @JsonProperty("assertions") JsonNode assertions,
             @JsonProperty("verify") JsonNode verify,
-            @JsonProperty("verifyMode")String verifyMode) {
+            @JsonProperty("verifyMode") String verifyMode) {
         this.loop = loop;
         this.retry = retry;
         this.name = name;
+        this.validators = validators;
         this.verifyMode = verifyMode;
         this.operation = operation != null? operation : method;
         this.method = method != null? method : operation;
@@ -139,6 +146,7 @@ public class Step {
                 ", operation='" + operation + '\'' +
                 ", url='" + url + '\'' +
                 ", request=" + request +
+                ", validators=" + validators +
                 ", assertions=" + assertions +
                 ", verifyMode=" + verifyMode +
                 ", verify=" + verify +
diff --git a/core/src/main/java/org/jsmart/zerocode/core/domain/Validator.java b/core/src/main/java/org/jsmart/zerocode/core/domain/Validator.java
new file mode 100644
index 00000000..d10aaeeb
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/domain/Validator.java
@@ -0,0 +1,34 @@
+package org.jsmart.zerocode.core.domain;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class Validator {
+    private final String field;
+    private final JsonNode value;
+
+    public Validator(
+            @JsonProperty("field") String field,
+            @JsonProperty("value") JsonNode value) {
+        this.field = field;
+        this.value = value;
+    }
+
+    public String getField() {
+        return field;
+    }
+
+    public JsonNode getValue() {
+        return value;
+    }
+
+    @Override
+    public String toString() {
+        return "Validator{" +
+                "field='" + field + '\'' +
+                ", value=" + value +
+                '}';
+    }
+}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidator.java b/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidator.java
new file mode 100644
index 00000000..b8887fca
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidator.java
@@ -0,0 +1,15 @@
+package org.jsmart.zerocode.core.engine.validators;
+
+import java.util.List;
+import org.jsmart.zerocode.core.domain.Step;
+import org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher;
+
+public interface ZeroCodeValidator {
+
+    List<FieldAssertionMatcher> validateFlat(Step thisStep, String actualResult);
+
+    List<FieldAssertionMatcher> validateStrict(String expectedResult, String actualResult);
+
+    List<FieldAssertionMatcher> validateLenient(String expectedResult, String actualResult);
+
+}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImpl.java b/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImpl.java
new file mode 100644
index 00000000..a8dfca8a
--- /dev/null
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImpl.java
@@ -0,0 +1,63 @@
+package org.jsmart.zerocode.core.engine.validators;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.inject.Inject;
+import com.jayway.jsonpath.JsonPath;
+import java.util.ArrayList;
+import java.util.List;
+import org.jsmart.zerocode.core.domain.Step;
+import org.jsmart.zerocode.core.domain.Validator;
+import org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher;
+import org.jsmart.zerocode.core.engine.assertion.JsonAsserter;
+import org.jsmart.zerocode.core.engine.assertion.field.FieldHasExactValueAsserter;
+import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeAssertionsProcessor;
+import org.slf4j.Logger;
+
+import static org.jsmart.zerocode.core.utils.HelperJsonUtils.strictComparePayload;
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class ZeroCodeValidatorImpl implements ZeroCodeValidator {
+    private static final Logger LOGGER = getLogger(ZeroCodeValidatorImpl.class);
+
+    private final ZeroCodeAssertionsProcessor zeroCodeAssertionsProcessor;
+
+    @Inject
+    public ZeroCodeValidatorImpl(ZeroCodeAssertionsProcessor zeroCodeAssertionsProcessor) {
+        this.zeroCodeAssertionsProcessor = zeroCodeAssertionsProcessor;
+    }
+
+    @Override
+    public List<FieldAssertionMatcher> validateFlat(Step thisStep, String actualResult) {
+        LOGGER.info("Comparing results via flat validators");
+
+        List<FieldAssertionMatcher> failureResults = new ArrayList<>();
+        List<Validator> validators = thisStep.getValidators();
+
+        for (Validator validator : validators) {
+            String josnPath = validator.getField();
+            JsonNode expectedValue = validator.getValue();
+            Object actualValue = JsonPath.read(actualResult, josnPath);
+
+            List<JsonAsserter> asserters = zeroCodeAssertionsProcessor.createJsonAsserters(expectedValue.toString());
+
+            failureResults.addAll(zeroCodeAssertionsProcessor.assertAllAndReturnFailed(asserters, actualValue.toString()));
+        }
+
+        return failureResults;
+    }
+
+    @Override
+    public List<FieldAssertionMatcher> validateStrict(String expectedResult, String actualResult) {
+        LOGGER.info("Comparing results via STRICT matchers");
+
+        return strictComparePayload(expectedResult, actualResult);
+    }
+
+    @Override
+    public List<FieldAssertionMatcher> validateLenient(String expectedResult, String actualResult) {
+        LOGGER.info("Comparing results via LENIENT matchers");
+
+        List<JsonAsserter> asserters = zeroCodeAssertionsProcessor.createJsonAsserters(expectedResult);
+        return zeroCodeAssertionsProcessor.assertAllAndReturnFailed(asserters, actualResult);
+    }
+}
diff --git a/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java b/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java
index 3a77d9b1..e526eb5f 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/runner/ZeroCodeMultiStepsScenarioRunnerImpl.java
@@ -1,22 +1,20 @@
 package org.jsmart.zerocode.core.runner;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.google.inject.name.Named;
 import com.jayway.jsonpath.JsonPath;
-import com.jayway.jsonpath.PathNotFoundException;
 import com.univocity.parsers.csv.CsvParser;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.function.BiConsumer;
-import java.util.stream.Collectors;
 import org.jsmart.zerocode.core.domain.ScenarioSpec;
 import org.jsmart.zerocode.core.domain.Step;
+import org.jsmart.zerocode.core.domain.Validator;
 import org.jsmart.zerocode.core.domain.builders.ZeroCodeExecReportBuilder;
 import org.jsmart.zerocode.core.domain.builders.ZeroCodeIoWriteBuilder;
 import org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher;
@@ -24,9 +22,10 @@ import org.jsmart.zerocode.core.engine.assertion.JsonAsserter;
 import org.jsmart.zerocode.core.engine.executor.ApiServiceExecutor;
 import org.jsmart.zerocode.core.engine.preprocessor.ScenarioExecutionState;
 import org.jsmart.zerocode.core.engine.preprocessor.StepExecutionState;
-import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeExternalFileProcessor;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeAssertionsProcessor;
+import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeExternalFileProcessor;
 import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeParameterizedProcessor;
+import org.jsmart.zerocode.core.engine.validators.ZeroCodeValidator;
 import org.jsmart.zerocode.core.logbuilder.ZerocodeCorrelationshipLogger;
 import org.jsmart.zerocode.core.utils.ApiTypeUtils;
 import org.junit.runner.Description;
@@ -36,17 +35,14 @@ import org.slf4j.Logger;
 import static java.util.Optional.ofNullable;
 import static org.jsmart.zerocode.core.constants.ZerocodeConstants.KAFKA_TOPIC;
 import static org.jsmart.zerocode.core.domain.builders.ZeroCodeExecReportBuilder.newInstance;
-import static org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher.aMatchingMessage;
-import static org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher.aNotMatchingMessage;
 import static org.jsmart.zerocode.core.engine.mocker.RestEndPointMocker.wireMockServer;
 import static org.jsmart.zerocode.core.kafka.helper.KafkaCommonUtils.printBrokerProperties;
+import static org.jsmart.zerocode.core.utils.ApiTypeUtils.apiType;
+import static org.jsmart.zerocode.core.utils.HelperJsonUtils.readObjectAsMap;
 import static org.jsmart.zerocode.core.utils.HelperJsonUtils.strictComparePayload;
 import static org.jsmart.zerocode.core.utils.RunnerUtils.getFullyQualifiedUrl;
 import static org.jsmart.zerocode.core.utils.RunnerUtils.getParameterSize;
-import static org.jsmart.zerocode.core.utils.ApiTypeUtils.apiType;
 import static org.jsmart.zerocode.core.utils.SmartUtils.prettyPrintJson;
-import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
-import static org.skyscreamer.jsonassert.JSONCompareMode.STRICT;
 import static org.slf4j.LoggerFactory.getLogger;
 
 @Singleton
@@ -75,6 +71,9 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
     @Inject
     private ApiTypeUtils apiTypeUtils;
 
+    @Inject
+    ZeroCodeValidator validator;
+
     @Inject(optional = true)
     @Named("web.application.endpoint.host")
     private String host;
@@ -251,10 +250,10 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
                     failureResults.forEach(f -> {
                         builder.append(f.toString() + "\n");
                     });
-                    correlLogger.assertion(builder.toString());
+                    correlLogger.assertion(resolvedAssertionJson != null ? builder.toString() : expectedValidatorsAsJson(thisStep));
                 } else {
-
-                    correlLogger.assertion(prettyPrintJson(resolvedAssertionJson));
+                    correlLogger.assertion(resolvedAssertionJson != null && !"null".equalsIgnoreCase(resolvedAssertionJson) ?
+                            prettyPrintJson(resolvedAssertionJson) : expectedValidatorsAsJson(thisStep));
                 }
 
                 if (retryTillSuccess && (retryCounter + 1 < retryMaxTimes) && !failureResults.isEmpty()) {
@@ -362,6 +361,13 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
         return null;
     }
 
+    private String expectedValidatorsAsJson(Step thisStep) throws JsonProcessingException {
+        if(thisStep.getValidators() == null){
+            return "No validators were found for this step";
+        }
+        return prettyPrintJson(objectMapper.writeValueAsString((thisStep.getValidators())));
+    }
+
     private String executeApi(String logPrefixRelationshipId,
                               Step thisStep,
                               String resolvedRequestJson,
@@ -496,12 +502,28 @@ public class ZeroCodeMultiStepsScenarioRunnerImpl implements ZeroCodeMultiStepsS
 
     private List<FieldAssertionMatcher> compareStepResults(Step thisStep, String actualResult, String expectedResult) {
         List<FieldAssertionMatcher> failureResults = new ArrayList<>();
-        if (ofNullable(thisStep.getVerifyMode()).orElse("LENIENT").equals("STRICT")) {
-            failureResults = strictComparePayload(expectedResult, actualResult);
-        } else {
-            List<JsonAsserter> asserters = zeroCodeAssertionsProcessor.createJsonAsserters(expectedResult);
-            failureResults = zeroCodeAssertionsProcessor.assertAllAndReturnFailed(asserters, actualResult);
+
+        // --------------------
+        //  Validators (pyrest)
+        // --------------------
+        if (ofNullable(thisStep.getValidators()).orElse(null) != null) {
+            failureResults = validator.validateFlat(thisStep, actualResult);
+        }
+
+        // ------------------------
+        // STRICT mode (skyscreamer)
+        // ------------------------
+        else if (ofNullable(thisStep.getVerifyMode()).orElse("LENIENT").equals("STRICT")) {
+            failureResults = validator.validateStrict(expectedResult, actualResult);
+        }
+
+        // --------------------------
+        // LENIENT mode (skyscreamer)
+        // --------------------------
+        else {
+            failureResults = validator.validateLenient(expectedResult, actualResult);
         }
+
         return failureResults;
     }
 


Test patch:
diff --git a/core/src/main/java/org/jsmart/zerocode/core/engine/listener/ZeroCodeTestReportListener.java b/core/src/main/java/org/jsmart/zerocode/core/engine/listener/ZeroCodeTestReportListener.java
index 34c89952..244c9b44 100644
--- a/core/src/main/java/org/jsmart/zerocode/core/engine/listener/ZeroCodeTestReportListener.java
+++ b/core/src/main/java/org/jsmart/zerocode/core/engine/listener/ZeroCodeTestReportListener.java
@@ -36,7 +36,7 @@ public class ZeroCodeTestReportListener extends RunListener {
          * Called when all tests have finished
          */
         LOGGER.info("#ZeroCode: Test run completed for this runner. Generating test reports and charts. " +
-                "\n* For more examples, helps, Kafka streams and API use-cases visit http://zerocode.io");
+                "\n* For more examples, helps, Kafka streams, APIs and Load use-cases visit https://zerocode.io");
         generateChartsAndReports();
     }
 
diff --git a/core/src/test/java/org/jsmart/zerocode/core/domain/StepTest.java b/core/src/test/java/org/jsmart/zerocode/core/domain/StepTest.java
index ff63bf54..788a7ee6 100644
--- a/core/src/test/java/org/jsmart/zerocode/core/domain/StepTest.java
+++ b/core/src/test/java/org/jsmart/zerocode/core/domain/StepTest.java
@@ -87,6 +87,16 @@ public class StepTest {
         assertThat(stepDeserialized.getVerifyMode(), is("STRICT"));
     }
 
+    @Test
+    public void testValidators_array() throws Exception {
+        String jsonDocumentAsString =
+                smartUtils.getJsonDocumentAsString("unit_test_files/engine_unit_test_jsons/00_test_json_single_step_verifications.json");
+        Step stepDeserialized = mapper.readValue(jsonDocumentAsString, Step.class);
+
+        assertThat(stepDeserialized.getValidators().get(0).getField(), is("fooFoo"));
+        assertThat(stepDeserialized.getValidators().get(0).getValue().asText(), is("barBar"));
+    }
+
     @Test
     public void testParameterized_values() throws Exception {
         String jsonDocumentAsString =
diff --git a/core/src/test/java/org/jsmart/zerocode/core/domain/ValidatorTest.java b/core/src/test/java/org/jsmart/zerocode/core/domain/ValidatorTest.java
new file mode 100644
index 00000000..a580535b
--- /dev/null
+++ b/core/src/test/java/org/jsmart/zerocode/core/domain/ValidatorTest.java
@@ -0,0 +1,63 @@
+package org.jsmart.zerocode.core.domain;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Inject;
+import java.util.Arrays;
+import java.util.List;
+import org.jsmart.zerocode.core.di.main.ApplicationMainModule;
+import org.jsmart.zerocode.core.utils.SmartUtils;
+import org.jukito.JukitoRunner;
+import org.jukito.TestModule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@RunWith(JukitoRunner.class)
+public class ValidatorTest {
+
+    public static class JukitoModule extends TestModule {
+        @Override
+        protected void configureTest() {
+            ApplicationMainModule applicationMainModule = new ApplicationMainModule("config_hosts_test.properties");
+
+            /* Finally install the main module */
+            install(applicationMainModule);
+        }
+    }
+
+    @Inject
+    SmartUtils smartUtils;
+
+    @Inject
+    private ObjectMapper mapper;
+
+
+    @Test
+    public void testValidator_SerDe()throws Exception {
+        String json =
+                smartUtils.getJsonDocumentAsString("unit_test_files/engine_unit_test_jsons/13_validator_key_value_pair.json");
+        Validator validator = mapper.readValue(json, Validator.class);
+
+        assertThat(validator.getField(), is("foo"));
+        assertThat(validator.getValue().asText(), is("bar"));
+    }
+
+    @Test
+    public void testValidators_arraySerDe()throws Exception {
+        String json =
+                smartUtils.getJsonDocumentAsString("unit_test_files/engine_unit_test_jsons/14_validator_key_value_array.json");
+        Validator[] validatorArray = mapper.readValue(json, Validator[].class);
+
+        List<Validator> validators = Arrays.asList(validatorArray);
+        assertThat(validators.get(0).getField(), is("foo"));
+        assertThat(validators.get(0).getValue().asText(), is("bar"));
+
+        assertThat(validators.get(1).getField(), is("age"));
+        assertThat(validators.get(1).getValue().asInt(), is(23));
+
+        assertThat(validators.get(2).getField(), is("address"));
+        assertThat(validators.get(2).getValue().toString(), is("{\"line1\":\"East Croydon\",\"postcode\":\"ECY\"}"));
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImplTest.java b/core/src/test/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImplTest.java
new file mode 100644
index 00000000..630e9c8c
--- /dev/null
+++ b/core/src/test/java/org/jsmart/zerocode/core/engine/validators/ZeroCodeValidatorImplTest.java
@@ -0,0 +1,86 @@
+package org.jsmart.zerocode.core.engine.validators;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import java.util.List;
+import org.jsmart.zerocode.core.di.main.ApplicationMainModule;
+import org.jsmart.zerocode.core.di.provider.ObjectMapperProvider;
+import org.jsmart.zerocode.core.domain.ScenarioSpec;
+import org.jsmart.zerocode.core.domain.Step;
+import org.jsmart.zerocode.core.engine.assertion.FieldAssertionMatcher;
+import org.jsmart.zerocode.core.engine.preprocessor.ZeroCodeAssertionsProcessorImpl;
+import org.jsmart.zerocode.core.utils.SmartUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+public class ZeroCodeValidatorImplTest {
+    Injector injector;
+    SmartUtils smartUtils;
+    ObjectMapper mapper;
+
+    ZeroCodeAssertionsProcessorImpl jsonPreProcessor;
+
+    ZeroCodeValidatorImpl codeValidator;
+
+    @Before
+    public void setUpStuff() throws Exception {
+        String serverEnvFileName = "config_hosts_test.properties";
+        injector = Guice.createInjector(new ApplicationMainModule(serverEnvFileName));
+        smartUtils = injector.getInstance(SmartUtils.class);
+        mapper = new ObjectMapperProvider().get();
+        jsonPreProcessor =
+                new ZeroCodeAssertionsProcessorImpl(smartUtils.getMapper(), serverEnvFileName);
+
+        codeValidator = new ZeroCodeValidatorImpl(jsonPreProcessor);
+    }
+
+    @Test
+    public void test_validateFlat_happy() throws Exception {
+
+        ScenarioSpec scenarioSpec =
+                smartUtils.scenarioFileToJava(
+                        "unit_test_files/engine_unit_test_jsons/15_test_validators_single_step.json", ScenarioSpec.class);
+        Step step = scenarioSpec.getSteps().get(0);
+
+        String actualResult = "{\n" +
+                "                \"status\": 200,\n" +
+                "                \"body\": {\n" +
+                "                    \"name\": \"Mr Bean\"\n" +
+                "                }\n" +
+                "            }";
+
+        List<FieldAssertionMatcher> matchers = codeValidator.validateFlat(step, actualResult);
+        assertThat(matchers.size(), is(0));
+
+    }
+
+    @Test
+    public void test_validateFlat_nonMatching() throws Exception {
+
+        ScenarioSpec scenarioSpec =
+                smartUtils.scenarioFileToJava(
+                        "unit_test_files/engine_unit_test_jsons/15_test_validators_single_step.json", ScenarioSpec.class);
+        Step step = scenarioSpec.getSteps().get(0);
+
+        String actualResult = "{\n" +
+                "                \"status\": 201,\n" +
+                "                \"body\": {\n" +
+                "                    \"name\": \"Mrs X\"\n" +
+                "                }\n" +
+                "            }";
+
+        List<FieldAssertionMatcher> matchers = codeValidator.validateFlat(step, actualResult);
+        assertThat(matchers.size(), is(2));
+        assertThat(matchers.get(0).toString(), containsString("actual value 'Mrs X' did not match the expected value 'Mr Bean'"));
+        assertThat(matchers.get(1).toString(), containsString("actual value '201' did not match the expected value '200'"));
+
+        //TODO [TECH-DEBT - Bulk or section validator]
+        //assertThat(matchers.get(0).toString(), containsString("'$.body.name' with actual value"));
+        //assertThat(matchers.get(1).toString(), containsString("'$.status' with actual value "));
+    }
+}
diff --git a/core/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java b/core/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java
index b48a4c48..3217a17b 100644
--- a/core/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java
+++ b/core/src/test/java/org/jsmart/zerocode/core/utils/SmartUtilsTest.java
@@ -76,7 +76,7 @@ public class SmartUtilsTest {
     @Test
     public void willReadAllfileNamesFrom_TestResource() throws Exception {
         List<String> allTestCaseFiles = SmartUtils.getAllEndPointFiles("unit_test_files/engine_unit_test_jsons");
-        assertThat(allTestCaseFiles.size(), is(15));
+        assertThat(allTestCaseFiles.size(), is(18));
         assertThat(allTestCaseFiles.get(0), is("unit_test_files/engine_unit_test_jsons/00_test_json_single_step_verifications.json"));
     }
 
diff --git a/core/src/test/java/org/jsmart/zerocode/integrationtests/HelloWorldInMemoryTest.java b/core/src/test/java/org/jsmart/zerocode/integrationtests/HelloWorldInMemoryTest.java
index 0c765bbb..dd72e31c 100644
--- a/core/src/test/java/org/jsmart/zerocode/integrationtests/HelloWorldInMemoryTest.java
+++ b/core/src/test/java/org/jsmart/zerocode/integrationtests/HelloWorldInMemoryTest.java
@@ -1,7 +1,6 @@
 package org.jsmart.zerocode.integrationtests;
 
 import org.jsmart.zerocode.core.domain.HostProperties;
-import org.jsmart.zerocode.core.domain.JsonTestCase;
 import org.jsmart.zerocode.core.domain.Scenario;
 import org.jsmart.zerocode.core.tests.customrunner.TestOnlyZeroCodeUnitRunner;
 import org.junit.Test;
@@ -17,7 +16,7 @@ public class HelloWorldInMemoryTest {
      */
 
     @Test
-    @JsonTestCase("integration_test_files/helloworld/get_api_integration_test.json")
+    @Scenario("integration_test_files/helloworld/get_api_integration_test.json")
     public void testStrict_compare() throws Exception {
     
     }
diff --git a/core/src/test/java/org/jsmart/zerocode/integrationtests/HelloWorldValidatorsTest.java b/core/src/test/java/org/jsmart/zerocode/integrationtests/HelloWorldValidatorsTest.java
new file mode 100644
index 00000000..da174905
--- /dev/null
+++ b/core/src/test/java/org/jsmart/zerocode/integrationtests/HelloWorldValidatorsTest.java
@@ -0,0 +1,27 @@
+package org.jsmart.zerocode.integrationtests;
+
+import org.jsmart.zerocode.core.domain.HostProperties;
+import org.jsmart.zerocode.core.domain.Scenario;
+import org.jsmart.zerocode.core.tests.customrunner.TestOnlyZeroCodeUnitRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@HostProperties(host="http://localhost", port=9998, context = "")
+@RunWith(TestOnlyZeroCodeUnitRunner.class)
+public class HelloWorldValidatorsTest {
+
+    /**
+     * Mock end points are in test/resources: simulators/test_purpose_end_points.json.
+     * @RunWith(TestOnlyZeroCodeUnitRunner.class) : starts these mocks first before running the tests
+     */
+
+    @Test
+    @Scenario("integration_test_files/helloworld/get_api_integration_validators_test.json")
+    public void testValidators() throws Exception {
+    
+    }
+
+}
+
+
+
diff --git a/core/src/test/resources/integration_test_files/helloworld/get_api_integration_validators_test.json b/core/src/test/resources/integration_test_files/helloworld/get_api_integration_validators_test.json
new file mode 100644
index 00000000..80c1ef45
--- /dev/null
+++ b/core/src/test/resources/integration_test_files/helloworld/get_api_integration_validators_test.json
@@ -0,0 +1,35 @@
+{
+    "scenarioName": "Tech-Debt - Long pending Validators - Match Path or Key or Field vs Value",
+    "steps": [
+        {
+            "name": "find_match",
+            "url": "/api/v1/search/persons",
+            "method": "GET",
+            "request": {
+                "queryParams": {
+                    "lang": "Amazing",
+                    "city": "Lon"
+                }
+            },
+            "validators": [
+                {
+                    "field": "$.body.name",
+                    "value": "Mr Bean"
+                },
+                {
+                    "field": "$.status",
+                    "value": 200
+                }
+                  // IN PROGRESS : Do not delete (Warning)
+//                {
+//                    "field": "$.body",
+//                    "value": {
+//                        "name": "Mr Bean",
+//                        "city": "Lon"
+//                    }
+//                }
+            ]
+        }
+    ]
+}
+
diff --git a/core/src/test/resources/unit_test_files/engine_unit_test_jsons/00_test_json_single_step_verifications.json b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/00_test_json_single_step_verifications.json
index 800313f0..d8e95def 100755
--- a/core/src/test/resources/unit_test_files/engine_unit_test_jsons/00_test_json_single_step_verifications.json
+++ b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/00_test_json_single_step_verifications.json
@@ -1,28 +1,34 @@
 {
-  "loop": 3,
-  "name": "StepNameWithoutSpaceEgCREATE",
-  "url": "/persons",
-  "operation": "POST",
-  "request": {
-    "headers": {
-      "Content-Type": "application/json;charset=UTF-8",
-      "Cookie": "cookie_123"
+    "loop": 3,
+    "name": "StepNameWithoutSpaceEgCREATE",
+    "url": "/persons",
+    "operation": "POST",
+    "request": {
+        "headers": {
+            "Content-Type": "application/json;charset=UTF-8",
+            "Cookie": "cookie_123"
+        },
+        "queryParams": {
+            "param1": "value1",
+            "invId": 10101
+        },
+        "body": {
+            "Customer": {
+                "firstName": "FIRST_NAME"
+            }
+        }
     },
-    "queryParams": {
-      "param1": "value1",
-      "invId": 10101
+    "validators": [
+        {
+            "field": "fooFoo",
+            "value": "barBar"
+        }
+    ],
+    "verify": {
+        "status": 201,
+        "body": {
+            "id": 1001
+        }
     },
-    "body": {
-      "Customer": {
-        "firstName": "FIRST_NAME"
-      }
-    }
-  },
-  "verify": {
-    "status": 201,
-    "body": {
-      "id" : 1001
-    }
-  },
-  "verifyMode":"STRICT"
+    "verifyMode": "STRICT"
 }
diff --git a/core/src/test/resources/unit_test_files/engine_unit_test_jsons/13_validator_key_value_pair.json b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/13_validator_key_value_pair.json
new file mode 100644
index 00000000..c17ea868
--- /dev/null
+++ b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/13_validator_key_value_pair.json
@@ -0,0 +1,4 @@
+{
+    "field": "foo",
+    "value": "bar"
+}
\ No newline at end of file
diff --git a/core/src/test/resources/unit_test_files/engine_unit_test_jsons/14_validator_key_value_array.json b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/14_validator_key_value_array.json
new file mode 100644
index 00000000..099b5659
--- /dev/null
+++ b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/14_validator_key_value_array.json
@@ -0,0 +1,17 @@
+[
+    {
+        "field": "foo",
+        "value": "bar"
+    },
+    {
+        "field": "age",
+        "value": 23
+    },
+    {
+        "field": "address",
+        "value": {
+            "line1": "East Croydon",
+            "postcode": "ECY"
+        }
+    }
+]
\ No newline at end of file
diff --git a/core/src/test/resources/unit_test_files/engine_unit_test_jsons/15_test_validators_single_step.json b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/15_test_validators_single_step.json
new file mode 100755
index 00000000..62104522
--- /dev/null
+++ b/core/src/test/resources/unit_test_files/engine_unit_test_jsons/15_test_validators_single_step.json
@@ -0,0 +1,26 @@
+{
+    "scenarioName": "Unit testing series of validators",
+    "steps": [
+        {
+            "name": "get_visas",
+            "url": "/api/visas",
+            "method": "GET",
+            "request": {
+                "queryParams": {
+                    "city": "Lon"
+                }
+            },
+            "validators": [
+                {
+                    "field": "$.body.name",
+                    "value": "Mr Bean"
+                },
+                {
+                    "field": "$.status",
+                    "value": 200
+                }
+            ]
+        }
+    ]
+}
+