Repository: pactumjs/pactum (js)

Issue:
Support cache mock interactions
**Is your feature request related to a problem? Please describe.**
During component testing, we use `useInteraction` to have expectations on the external calls. Same expectations are applied for multiple specs. Some micro-services will cache the response and that makes component tests to remove the cached response from cache server.

**Describe the solution you'd like**
`useInteraction` supports a new boolean property called `cache` will have the expectation exercised only once in the entire component testing cycle.

**Describe alternatives you've considered**
May be expectations would support the call count to be either `0` or `1`.


Pull request:
Supoprt skip interaction exercised in exceptions
Closes #105 

Changes:

- Added a new property to the interaction object
  ```js
  exceptions: {
     checkExercised: true    // default
  }
  ```
- Works with `interaction handlers`, `useInteraction`, `addInteraction`
- Unit & Component test update.
- Bump `pactum-matchers` version to `1.0.6`
- Bump `pactum` version to `3.1.2`


Patch:
diff --git a/package-lock.json b/package-lock.json
index 7d36d77..d3bd21c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "pactum",
-  "version": "3.1.1",
+  "version": "3.1.2",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -1655,9 +1655,9 @@
       }
     },
     "pactum-matchers": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/pactum-matchers/-/pactum-matchers-1.0.5.tgz",
-      "integrity": "sha512-WANTu9+JDIo079pCAXQGsLWepdUtj0bXU+uzPSbeE/H9a6TYfi4793Gkt4QY3+t46c5n5KdDnCCYtGugrYZlcg=="
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/pactum-matchers/-/pactum-matchers-1.0.6.tgz",
+      "integrity": "sha512-vUXnIAEmQdg3tKXzcj2OFhB1A492u+c89ps6JY6aDH0xH29fbyDYmnHl4xtp3peQosq8AOqC9D6eTXMAhcE5xw=="
     },
     "parse-graphql": {
       "version": "1.0.0",
diff --git a/package.json b/package.json
index 9224d4f..9b7a34b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "pactum",
-  "version": "3.1.1",
+  "version": "3.1.2",
   "description": "REST API Testing Tool for all levels in a Test Pyramid",
   "main": "./src/index.js",
   "types": "./src/index.d.ts",
@@ -67,7 +67,7 @@
     "klona": "^2.0.4",
     "lightcookie": "^1.0.25",
     "openapi-fuzzer-core": "^1.0.6",
-    "pactum-matchers": "^1.0.5",
+    "pactum-matchers": "^1.0.6",
     "parse-graphql": "^1.0.0",
     "phin": "^3.6.0",
     "polka": "^0.5.2"
diff --git a/src/exports/mock.d.ts b/src/exports/mock.d.ts
index 6f10349..2b4ee26 100644
--- a/src/exports/mock.d.ts
+++ b/src/exports/mock.d.ts
@@ -37,6 +37,7 @@ export interface InteractionResponse {
 }
 
 export interface InteractionExpectations {
+  disable?: boolean;
   exercised?: boolean;
   callCount?: number;
 }
diff --git a/src/models/Interaction.model.js b/src/models/Interaction.model.js
index f004ad7..cba3b7e 100644
--- a/src/models/Interaction.model.js
+++ b/src/models/Interaction.model.js
@@ -31,11 +31,17 @@ function setRawDefaults(raw) {
       if (typeof raw.response.status === 'undefined') raw.response.status = 404;
     }
     if (!raw.expects) {
-      raw.expects = { exercised: true };
+      raw.expects = { disable: false, exercised: true };
     }
     if (typeof raw.expects.exercised === 'undefined') {
       raw.expects.exercised = true;
     }
+    if (typeof raw.expects.disable === 'undefined') {
+      raw.expects.disable = false;
+    }
+    if (raw.expects.disable) {
+      raw.expects.exercised = false;
+    }
   }
 }
 
@@ -173,6 +179,7 @@ class InteractionResponseDelay {
 
 class InteractionExpectations {
   constructor(expects) {
+    this.disable = expects.disable;
     this.exercised = expects.exercised;
     this.callCount = expects.callCount;
   }
@@ -203,7 +210,7 @@ class Interaction {
       request,
       response,
       expects,
-      stores,
+      stores
     } = raw;
     this.id = id || helper.getRandomId();
     if (flow) this.flow = flow;
diff --git a/src/models/expect.js b/src/models/expect.js
index d8d557e..a5af6f7 100644
--- a/src/models/expect.js
+++ b/src/models/expect.js
@@ -78,16 +78,19 @@ class Expect {
         headers: interaction.request.headers,
         body: interaction.request.body
       };
-      if (expects.exercised && !interaction.exercised) {
+      if (expects.disable) {
+        log.debug('Interaction expect exercised is skipped', intReq);
+      }
+      if (expects.exercised && !interaction.exercised && !expects.disable) {
         log.warn('Interaction Not Exercised', intReq);
         this.fail(`Interaction not exercised: ${interaction.request.method} - ${interaction.request.path}`);
       }
-      if (!expects.exercised && interaction.exercised) {
+      if (!expects.exercised && interaction.exercised && !expects.disable) {
         log.warn('Interaction got Exercised', intReq);
         this.fail(`Interaction exercised: ${interaction.request.method} - ${interaction.request.path}`);
       }
       if (typeof expects.callCount !== 'undefined') {
-        if (expects.callCount !== interaction.callCount) {
+        if (expects.callCount !== interaction.callCount && !expects.disable) {
           this.fail(`Interaction call count ${interaction.callCount} !== ${expects.callCount} for ${interaction.request.method} - ${interaction.request.path}`);
         }
       }


Test patch:
diff --git a/test/component/interactions.spec.js b/test/component/interactions.spec.js
index c834555..f688703 100644
--- a/test/component/interactions.spec.js
+++ b/test/component/interactions.spec.js
@@ -1,6 +1,7 @@
 const { like, eachLike, regex } = require('pactum-matchers');
 const pactum = require('../../src/index');
 const FormData = require('form-data-lite');
+const { expect } = require('chai');
 
 describe('Mock', () => {
 
@@ -822,4 +823,116 @@ describe('Pact - VALID', () => {
       .toss();
   });
 
+});
+
+
+describe('Interactions - expects skip check', () => {
+
+  it('skip expects - defaults', async () => {
+    await pactum.spec()
+      .useInteraction([{
+        request: {
+          method: 'GET',
+          path: '/api/projects/{id}',
+          pathParams: {
+            id: '101'
+          }
+        },
+        response: {
+          status: 200,
+          body: {
+            id: '$S{ProjectId}'
+          }
+        },
+        stores: {
+          ProjectId: 'req.pathParams.id'
+        },
+        expects: {
+          disable: true,
+          exercised: true,
+          callCount: 1
+        }
+      },
+      {
+        request: {
+          method: 'GET',
+          path: '/api/users/{id}',
+          pathParams: {
+            id: '1'
+          }
+        },
+        response: {
+          status: 200,
+          body: {
+            id: '$S{UserId}'
+          }
+        },
+        stores: {
+          UserId: 'req.pathParams.id'
+        }
+      }])
+      .get('http://localhost:9393/api/users/1')
+      .expectStatus(200)
+      .expectJsonLike({
+        id: '1'
+      })
+      .toss();
+  });
+
+  it('skip expects (false) - failure', async () => {
+    try {
+      await pactum.spec()
+        .useInteraction([{
+          request: {
+            method: 'GET',
+            path: '/api/projects/{id}',
+            pathParams: {
+              id: '101'
+            }
+          },
+          response: {
+            status: 200,
+            body: {
+              id: '$S{ProjectId}'
+            }
+          },
+          stores: {
+            ProjectId: 'req.pathParams.id'
+          },
+          expects: {
+            disable: false
+          }
+        },
+        {
+          request: {
+            method: 'GET',
+            path: '/api/users/{id}',
+            pathParams: {
+              id: '1'
+            }
+          },
+          response: {
+            status: 200,
+            body: {
+              id: '$S{UserId}'
+            }
+          },
+          stores: {
+            UserId: 'req.pathParams.id'
+          },
+          expects: {
+            exercised: true,
+            callCount: 1
+          }
+        }])
+        .get('http://localhost:9393/api/users/1')
+        .expectStatus(200)
+        .expectJsonLike({
+          id: '1'
+        })
+    } catch (error) {
+      err = error;
+    }
+    expect(err.message).contains('Interaction not exercised: GET - /api/projects/{id}');
+  });
 });
\ No newline at end of file
diff --git a/test/component/remote.server.spec.js b/test/component/remote.server.spec.js
index c403514..25ade24 100644
--- a/test/component/remote.server.spec.js
+++ b/test/component/remote.server.spec.js
@@ -157,7 +157,7 @@ describe('Remote Server - before and after spec', () => {
       callCount: 0,
       "calls": [],
       "exercised": false,
-      expects: { exercised: true }
+      expects: { disable: false, exercised: true }
     });
   });
 
@@ -195,7 +195,7 @@ describe('Remote Server - before and after spec', () => {
         callCount: 0,
         "calls": [],
         "exercised": false,
-        expects: { exercised: true }
+        expects: { disable: false, exercised: true }
       },
       {
         id: 'id2',
@@ -205,7 +205,7 @@ describe('Remote Server - before and after spec', () => {
         callCount: 0,
         "calls": [],
         "exercised": false,
-        expects: { exercised: true }
+        expects: { disable: false, exercised: true }
       }
     ]);
   });
@@ -235,7 +235,7 @@ describe('Remote Server - before and after spec', () => {
         callCount: 0,
         "calls": [],
         "exercised": false,
-        expects: { exercised: true }
+        expects: { disable: false, exercised: true }
       },
       {
         id: 'id2',
@@ -245,7 +245,7 @@ describe('Remote Server - before and after spec', () => {
         callCount: 0,
         "calls": [],
         "exercised": false,
-        expects: { exercised: true }
+        expects: { disable: false, exercised: true }
       }
     ]);
   });
diff --git a/test/component/remote.spec.js b/test/component/remote.spec.js
index 0a82107..fa0be35 100644
--- a/test/component/remote.spec.js
+++ b/test/component/remote.spec.js
@@ -63,6 +63,7 @@ describe('Remote- post single mock interaction', () => {
             }
           },
           "expects": {
+            "disable": false,
             "exercised": true
           }
         }
@@ -99,6 +100,7 @@ describe('Remote- post single mock interaction', () => {
             }
           },
           "expects": {
+            "disable": false,
             "exercised": true
           }
         }
@@ -160,6 +162,7 @@ describe('Remote- post single mock interaction', () => {
             }
           },
           "expects": {
+            "disable": false,
             "exercised": true
           }
         }
diff --git a/test/unit/interaction.spec.js b/test/unit/interaction.spec.js
index b6e6d33..7de4374 100644
--- a/test/unit/interaction.spec.js
+++ b/test/unit/interaction.spec.js
@@ -54,6 +54,7 @@ describe('Interaction', () => {
         "matchingRules": {}
       },
       "expects": {
+        "disable": false,
         "exercised": true,
         "callCount": undefined
       }
@@ -110,6 +111,7 @@ describe('Interaction', () => {
         "matchingRules": {}
       },
       "expects": {
+        "disable": false,
         "exercised": true,
         "callCount": undefined
       }
@@ -174,6 +176,7 @@ describe('Interaction', () => {
         }
       },
       "expects": {
+        "disable": false,
         "exercised": true,
         "callCount": undefined
       }
@@ -232,6 +235,7 @@ describe('Interaction', () => {
         "matchingRules": {}
       },
       "expects": {
+        "disable": false,
         "exercised": true,
         "callCount": undefined
       }
@@ -298,6 +302,7 @@ describe('Interaction', () => {
         }
       },
       "expects": {
+        "disable": false,
         "exercised": true,
         "callCount": undefined
       }
@@ -315,8 +320,6 @@ describe('Interaction', () => {
     };
     const interaction = new Interaction(raw, true);
     expect(interaction.request).deep.equals({
-
-
       "method": "GET",
       "path": "/api/projects/1",
       "queryParams": {},
@@ -374,6 +377,7 @@ describe('Interaction', () => {
         "matchingRules": {}
       },
       "expects": {
+        "disable": false,
         "exercised": true,
         "callCount": undefined
       }
@@ -435,6 +439,7 @@ describe('Interaction', () => {
         "matchingRules": {}
       },
       "expects": {
+        "disable": false,
         "exercised": true,
         "callCount": undefined
       }
@@ -495,6 +500,7 @@ describe('Interaction', () => {
         "matchingRules": {}
       },
       "expects": {
+        "disable": false,
         "exercised": true,
         "callCount": undefined
       }
@@ -551,6 +557,7 @@ describe('Interaction', () => {
         "matchingRules": {}
       },
       "expects": {
+        "disable": false,
         "exercised": true,
         "callCount": undefined
       }
@@ -599,6 +606,7 @@ describe('Interaction', () => {
         "matchingRules": {}
       },
       "expects": {
+        "disable": false,
         "exercised": true,
         "callCount": undefined
       }
@@ -633,6 +641,7 @@ describe('Interaction', () => {
         "matchingRules": {}
       },
       "expects": {
+        "disable": false,
         "exercised": true,
         "callCount": undefined
       }