Repository: pactumjs/pactum
Pull request #206 (2022-09-11 09:23:29)

feat: expect json snapshot in bdd
closes: #205 


Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 70e3543..bd09e42 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -24,6 +24,7 @@ If applicable, add screenshots to help explain your problem.
 **Software (please complete the following information):**
  - OS: [e.g. Windows]
  - NodeJS Version [e.g. 14]
+ - Test Runner [e.g mocha]
 
 
 **Additional context**
diff --git a/package-lock.json b/package-lock.json
index e3cb12d..b6aa7eb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "pactum",
-  "version": "3.2.1",
+  "version": "3.2.2",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 33d375e..d7661e4 100644
--- a/package.json
+++ b/package.json
@@ -1,20 +1,12 @@
 {
   "name": "pactum",
-  "version": "3.2.1",
+  "version": "3.2.2",
   "description": "REST API Testing Tool for all levels in a Test Pyramid",
   "main": "./src/index.js",
   "types": "./src/index.d.ts",
   "files": [
     "/src"
   ],
-  "exports": {
-    ".": "./src/index.js",
-    "./handler": "./src/exports/handler.js",
-    "./mock": "./src/exports/mock.js",
-    "./request": "./src/exports/request.js",
-    "./settings": "./src/exports/settings.js",
-    "./state": "./src/exports/state.js"
-  },
   "scripts": {
     "test": "npm run test:unit && npm run test:component",
     "test:unit": "mocha --timeout 10000 ./test/unit/",
diff --git a/src/exports/expect.d.ts b/src/exports/expect.d.ts
index 82a5087..f23ab62 100644
--- a/src/exports/expect.d.ts
+++ b/src/exports/expect.d.ts
@@ -20,6 +20,8 @@ export interface Have {
   jsonMatchStrict(path: string, value: object): void;
   jsonLength(value: number): void;
   jsonLength(path: string, value: number): void;
+  jsonSnapshot(value?: any): void;
+  jsonSnapshot(name: string, value?: any): void;
   responseTimeLessThan(ms: number): void;
   error(err?: string | object): void;
   _(handler: string, data: any): Promise<void>;
diff --git a/src/exports/expect.js b/src/exports/expect.js
index e82251e..4c2668e 100644
--- a/src/exports/expect.js
+++ b/src/exports/expect.js
@@ -86,6 +86,11 @@ class Have {
     this._validate();
   }
 
+  jsonSnapshot(name, value) {
+    typeof name === 'string' ? this.expect.jsonSnapshots.push({ name, value }): this.expect.jsonSnapshots.push({ value: name });
+    this._validate();
+  }
+
   responseTimeLessThan(ms) {
     this.expect.responseTime = ms;
     this._validate();
diff --git a/src/models/Spec.d.ts b/src/models/Spec.d.ts
index 52a0f4d..5fb95cb 100644
--- a/src/models/Spec.d.ts
+++ b/src/models/Spec.d.ts
@@ -336,6 +336,7 @@ declare class Spec {
    * @see https://pactumjs.github.io/api/assertions/expectJsonSnapshot.html
    */
   expectJsonSnapshot(value?: object): Spec;
+  expectJsonSnapshot(name: string, value?: object): Spec;
 
   /**
    * updates the reference snapshot file
diff --git a/src/models/Spec.js b/src/models/Spec.js
index ee7443e..bc70b43 100644
--- a/src/models/Spec.js
+++ b/src/models/Spec.js
@@ -399,8 +399,8 @@ class Spec {
   }
   expectJsonMatchStrictAt(...args) { return this.expectJsonMatchStrict(...args); }
 
-  expectJsonSnapshot(matchers) {
-    this._expect.jsonSnapshot.push(matchers);
+  expectJsonSnapshot(name, value) {
+    typeof name === 'string' ? this._expect.jsonSnapshots.push({ name, value }): this._expect.jsonSnapshots.push({ value: name });
     return this;
   }
 
diff --git a/src/models/expect.js b/src/models/expect.js
index 9ee757d..8295971 100644
--- a/src/models/expect.js
+++ b/src/models/expect.js
@@ -31,7 +31,7 @@ class Expect {
     this.jsonMatchQuery = [];
     this.jsonMatchStrict = [];
     this.jsonMatchStrictQuery = [];
-    this.jsonSnapshot = [];
+    this.jsonSnapshots = [];
     this.jsonLength = [];
     this.jsonLengthQuery = [];
     this.headers = [];
@@ -327,38 +327,37 @@ class Expect {
   }
 
   _validateJsonSnapshot(response) {
-    if (this.jsonSnapshot.length > 0) {
-      if (!this.name) {
-        this.fail('Snapshot name is required');
-      }
-      if (this.updateSnapshot) {
-        log.warn(`Update snapshot is enabled for "${this.name}"`);
-        file.saveSnapshot(this.name, response.json);
-      }
-      this.jsonSnapshot = processor.processData(this.jsonSnapshot);
-      const expected = file.getSnapshotFile(this.name, response.json);
+    if (this.jsonSnapshots.length > 0) {
+      this.jsonSnapshots = processor.processData(this.jsonSnapshots);
+      let snapshot_name = this.name;
       const actual = response.json;
-      const rules = {};
-      for (let i = 0; i < this.jsonSnapshot.length; i++) {
-        const data = this.jsonSnapshot[i];
-        if (data) {
-          const current_rules = jmv.getMatchingRules(data, '$.body');
-          const errors = jmv.validate(actual,  jmv.getRawValue(data), current_rules, '$.body');
+      const all_rules = {};
+
+      for (let i = 0; i < this.jsonSnapshots.length; i++) {
+        const { name, value } = this.jsonSnapshots[i];
+        snapshot_name = name || snapshot_name;
+        if (!snapshot_name) {
+          this.fail('Snapshot name is required');
+        }
+        if (this.updateSnapshot) {
+          log.warn(`Update snapshot is enabled for '${snapshot_name}'`);
+          file.saveSnapshot(snapshot_name, response.json);
+        }    
+        if (value) {
+          const current_rules = jmv.getMatchingRules(value, '$.body');
+          let errors = jmv.validate(actual,  jmv.getRawValue(value), current_rules, '$.body');
           if (errors) {
             this.fail(errors.replace('$.body', '$'));
           }
-          Object.assign(rules, current_rules);
+          Object.assign(all_rules, current_rules);
         }
       }
-      if (Object.keys(rules).length > 0) {
-        let errors = jmv.validate(actual, expected, rules, '$.body');
+      
+      const expected = file.getSnapshotFile(snapshot_name, response.json);
+      if (Object.keys(all_rules).length > 0) {
+        const errors = jmv.validate(actual, expected, all_rules, '$.body', true);
         if (errors) {
           this.fail(errors.replace('$.body', '$'));
-        } else {
-          errors = jmv.validate(expected, actual, rules, '$.body');
-          if (errors) {
-            this.fail(errors.replace('$.body', '$'));
-          }
         }
       } else {
         assert.deepStrictEqual(actual, expected);


Test patch:
diff --git a/test/component/expect.jsonSnapshot.spec.js b/test/component/expect.jsonSnapshot.spec.js
new file mode 100644
index 0000000..aa117e8
--- /dev/null
+++ b/test/component/expect.jsonSnapshot.spec.js
@@ -0,0 +1,149 @@
+const { like } = require('pactum-matchers');
+const { spec } = require('../../src/index');
+const fs = require('fs');
+const ce = require('chai').expect;
+
+describe('expectations - json snapshot', () => {
+
+  describe('builder style - expectJsonSnapshot', () => {
+
+    before(async () => {
+      await spec()
+        .useInteraction('get people')
+        .name('json snapshot - deep equal')
+        .get('http://localhost:9393/api/people')
+        .expectStatus(200)
+        .expectJsonSnapshot();
+      await spec()
+        .useInteraction('get user with id 1')
+        .name('json snapshot - with matchers')
+        .get('http://localhost:9393/api/users/1')
+        .expectStatus(200)
+        .expectJsonSnapshot({
+          id: like(1)
+        });
+    });
+
+    after(async () => {
+      fs.unlinkSync(`.pactum/snapshots/json snapshot - deep equal.json`);
+      fs.unlinkSync(`.pactum/snapshots/json snapshot - with matchers.json`);
+    });
+
+    it('should validate snapshot successfully', async () => {
+      await spec()
+        .useInteraction('get people')
+        .name('json snapshot - deep equal')
+        .get('http://localhost:9393/api/people')
+        .expectStatus(200)
+        .expectJsonSnapshot();
+    });
+
+    it('should not validate snapshot successfully when response does not match', async () => {
+      let err;
+      try {
+        await pactum.spec()
+          .useInteraction('get user with id 1')
+          .name('json snapshot - deep equal')
+          .get('http://localhost:9393/api/users/1')
+          .expectStatus(200)
+          .expectJsonSnapshot()
+          .useLogLevel('ERROR');
+      } catch (error) {
+        err = error;
+      }
+      ce(err).not.undefined;
+    });
+
+    it('should validate snapshot successfully with matchers', async () => {
+      await spec()
+        .useInteraction('get user with id 1')
+        .name('json snapshot - with matchers')
+        .get('http://localhost:9393/api/users/1')
+        .expectStatus(200)
+        .expectJsonSnapshot({
+          id: like(1)
+        });
+    });
+
+  });
+
+  describe('bdd style - jsonSnapshot', () => {
+
+    before(async () => {
+      await spec()
+        .useInteraction('get people')
+        .name('json snapshot - deep equal')
+        .get('http://localhost:9393/api/people')
+        .expectStatus(200)
+        .expectJsonSnapshot();
+      await spec()
+        .useInteraction('get user with id 1')
+        .name('json snapshot - with matchers')
+        .get('http://localhost:9393/api/users/1')
+        .expectStatus(200)
+        .expectJsonSnapshot({
+          id: like(1)
+        });
+    });
+
+    after(async () => {
+      fs.unlinkSync(`.pactum/snapshots/json snapshot - deep equal.json`);
+      fs.unlinkSync(`.pactum/snapshots/json snapshot - with matchers.json`);
+    });
+
+    it('should validate snapshot successfully', async () => {
+      const http = spec();
+      http.useInteraction('get people');
+      http.get('http://localhost:9393/api/people');
+      await http.toss();
+      http.response().should.have.jsonSnapshot('json snapshot - deep equal');
+    });
+
+    it('should not validate snapshot successfully when response does not match', async () => {
+      const http = spec();
+      http.useInteraction('get user with id 1');
+      http.get('http://localhost:9393/api/users/1');
+      await http.toss();
+      let err;
+      try {
+        http.response().should.have.jsonSnapshot('json snapshot - deep equal')
+      } catch (error) {
+        err = error;
+      }
+      ce(err).not.to.be.undefined;
+    });
+
+    it('should validate snapshot successfully with matchers', async () => {
+      const http = spec();
+      http.useInteraction('get user with id 1');
+      http.get('http://localhost:9393/api/users/1');
+      await http.toss();
+      http.response().should.have.jsonSnapshot('json snapshot - with matchers', { id: like(1) });
+    });
+
+    it('should validate snapshot successfully with multiple matchers', async () => {
+      const http = spec();
+      http.useInteraction('get user with id 1');
+      http.get('http://localhost:9393/api/users/1');
+      await http.toss();
+      http.response().should.have.jsonSnapshot('json snapshot - with matchers', { id: like(1) });
+      http.response().should.have.jsonSnapshot('json snapshot - with matchers', { name: like('snow') });
+    });
+
+    it('should not validate snapshot successfully when matchers fail', async () => {
+      const http = spec();
+      http.useInteraction('get user with id 1');
+      http.get('http://localhost:9393/api/users/1');
+      await http.toss();
+      let err;
+      try {
+        http.response().should.have.jsonSnapshot('json snapshot - with matchers', { id: like('1') })
+      } catch (error) {
+        err = error;
+      }
+      ce(err).not.to.be.undefined;
+    });
+
+  });
+
+});
\ No newline at end of file
diff --git a/test/component/expects.spec.js b/test/component/expects.spec.js
index f37be29..a5e71ba 100644
--- a/test/component/expects.spec.js
+++ b/test/component/expects.spec.js
@@ -711,89 +711,6 @@ describe('Expects', () => {
     expect(err).not.undefined;
   });
 
-  it('json snapshot - deep equal', async () => {
-    await pactum.spec()
-      .useInteraction('get people')
-      .name('json snapshot - deep equal')
-      .get('http://localhost:9393/api/people')
-      .expectStatus(200)
-      .expectJsonSnapshot();
-    await pactum.spec()
-      .useInteraction('get people')
-      .name('json snapshot - deep equal')
-      .get('http://localhost:9393/api/people')
-      .expectStatus(200)
-      .expectJsonSnapshot();
-    fs.unlinkSync(`.pactum/snapshots/json snapshot - deep equal.json`);
-  });
-
-  it('json snapshot - deep equal - fails', async () => {
-    await pactum.spec()
-      .useInteraction({
-        request: {
-          method: 'GET',
-          path: '/api/users/1'
-        },
-        response: {
-          status: 200,
-          body: {
-            id: 'random-id',
-            name: 'snow'
-          }
-        }
-      })
-      .name('json snapshot - deep equal')
-      .get('http://localhost:9393/api/users/1')
-      .expectStatus(200)
-      .expectJsonSnapshot();
-    let err;
-    try {
-      await pactum.spec()
-        .useInteraction({
-          request: {
-            method: 'GET',
-            path: '/api/users/1'
-          },
-          response: {
-            status: 200,
-            body: {
-              id: 'random-ids',
-              name: 'snow'
-            }
-          }
-        })
-        .name('json snapshot - deep equal')
-        .get('http://localhost:9393/api/users/1')
-        .expectStatus(200)
-        .expectJsonSnapshot()
-        .useLogLevel('ERROR');
-    } catch (error) {
-      err = error;
-    }
-    fs.unlinkSync(`.pactum/snapshots/json snapshot - deep equal.json`);
-    expect(err).not.undefined;
-  });
-
-  it('json snapshot - with matchers', async () => {
-    await pactum.spec()
-      .useInteraction('get user with id 1')
-      .name('json snapshot - with matchers')
-      .get('http://localhost:9393/api/users/1')
-      .expectStatus(200)
-      .expectJsonSnapshot({
-        id: like(1)
-      });
-    await pactum.spec()
-      .useInteraction('get user with id 1')
-      .name('json snapshot - with matchers')
-      .get('http://localhost:9393/api/users/1')
-      .expectStatus(200)
-      .expectJsonSnapshot({
-        id: like(1)
-      });
-    fs.unlinkSync(`.pactum/snapshots/json snapshot - with matchers.json`);
-  });
-
   it('json snapshot - with multiple matchers', async () => {
     await pactum.spec()
       .useInteraction({
@@ -1026,7 +943,6 @@ describe('Expects', () => {
       e = error;
     }
     expect(e).not.undefined;
-    fs.unlinkSync(`.pactum/snapshots/json snapshot - with invalid matchers.json`);
   });
 
   it('error - empty', async () => {