Repository: pactumjs/pactum
Pull request #63 (2021-04-29 05:00:58)

added expect Error
- added expect Error


Patch:
diff --git a/src/exports/expect.d.ts b/src/exports/expect.d.ts
index ef1f10b..78ab4a2 100644
--- a/src/exports/expect.d.ts
+++ b/src/exports/expect.d.ts
@@ -15,6 +15,7 @@ export interface Have {
   jsonMatchStrict(value: object): void;
   jsonMatchStrictAt(path: string, value: object): 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 0ade240..1b02e0c 100644
--- a/src/exports/expect.js
+++ b/src/exports/expect.js
@@ -89,6 +89,11 @@ class Have {
     this._validate();
   }
 
+  error(err) {
+    this.expect.errors.push(err);
+    this._validate();
+  }
+
   _(handler, data) {
     this.expect.customExpectHandlers.push({ handler, data });
     return this._validate();
diff --git a/src/models/Spec.d.ts b/src/models/Spec.d.ts
index 398d230..5a6256c 100644
--- a/src/models/Spec.d.ts
+++ b/src/models/Spec.d.ts
@@ -531,6 +531,10 @@ declare class Spec {
    */
   expectJsonMatchStrictAt(value: object): Spec;
 
+  expectError(): Spec;
+  expectError(error: string): Spec;
+  expectError(error: object): Spec;
+
   /**
    * expects the json to match with stored snapshots
    * @example
diff --git a/src/models/Spec.js b/src/models/Spec.js
index a11824c..b0eb6fc 100644
--- a/src/models/Spec.js
+++ b/src/models/Spec.js
@@ -393,6 +393,11 @@ class Spec {
     return this;
   }
 
+  expectError(error) {
+    this._expect.errors.push(error);
+    return this;
+  }
+
   updateSnapshot() {
     this._expect.updateSnapshot = true;
     return this;
diff --git a/src/models/Tosser.js b/src/models/Tosser.js
index c93e731..c93b7d2 100644
--- a/src/models/Tosser.js
+++ b/src/models/Tosser.js
@@ -54,7 +54,7 @@ class Tosser {
   }
 
   async setResponse() {
-    this.response = await getResponse(this.request);
+    this.response = await getResponse(this);
     const retryOptions = this.request.retryOptions;
     if (retryOptions) {
       const { count, delay, strategy } = retryOptions;
@@ -70,7 +70,7 @@ class Tosser {
         }
         if (!retry) {
           await helper.sleep(delay);
-          this.response = await getResponse(this.request);
+          this.response = await getResponse(this);
         } else {
           break;
         }
@@ -139,7 +139,7 @@ class Tosser {
   }
 
   validateError() {
-    if (this.response instanceof Error) {
+    if (this.response instanceof Error && this.expect.errors.length === 0) {
       this.spec.status = 'ERROR';
       this.spec.failure = this.response.toString();
       this.runReport();
@@ -157,22 +157,25 @@ class Tosser {
   }
 
   runReport() {
-    if (config.reporter.autoRun) {
+    if (config.reporter.autoRun && this.expect.errors.length === 0) {
       rlc.afterSpecReport(this.spec);
     }
   }
 
 }
 
-async function getResponse(req) {
+async function getResponse(tosser) {
+  const { request, expect } = tosser;
   let res = {};
   const requestStartTime = Date.now();
   try {
-    log.debug(`${req.method} ${req.url}`);
-    res = await phin(req);
+    log.debug(`${request.method} ${request.url}`);
+    res = await phin(request);
     res.json = helper.getJson(res.body);
   } catch (error) {
-    log.error('Error performing request', error);
+    if (expect.errors.length === 0) {
+      log.error('Error performing request', error);
+    }
     res = error;
   }
   res.responseTime = Date.now() - requestStartTime;
diff --git a/src/models/expect.js b/src/models/expect.js
index 41e3164..7d0a26b 100644
--- a/src/models/expect.js
+++ b/src/models/expect.js
@@ -34,6 +34,7 @@ class Expect {
     this.headerContains = [];
     this.responseTime = null;
     this.customExpectHandlers = [];
+    this.errors = [];
   }
 
   validate(request, response) {
@@ -54,6 +55,7 @@ class Expect {
     this._validateJsonMatchStrictQuery(response);
     this._validateJsonSnapshot(response);
     this._validateResponseTime(response);
+    this._validateErrors(response);
     // for asynchronous expectations
     return this._validateCustomExpectHandlers(request, response);
   }
@@ -347,6 +349,31 @@ class Expect {
     }
   }
 
+  _validateErrors(response) {
+    if (this.errors.length > 0) {
+      if (!(response instanceof Error)) {
+        this.fail(`No Error while performing a request`);
+      }
+      for (let i = 0; i < this.errors.length; i++) {
+        const expected = this.errors[i];
+        if (typeof expected === 'string') {
+          const actual = response.toString();
+          if (!actual.includes(expected)) {
+            this.fail(`Error - "${actual}" doesn't include - ${expected}`);
+          }
+        }
+        if (typeof expected === 'object') {
+          const rules = jmv.getMatchingRules(expected, '$.error');
+          const value = jmv.getRawValue(expected);
+          const errors = jmv.validate(response, value, rules, '$.error', false);
+          if (errors) {
+            this.fail(errors.replace('$.error', '$'));
+          }
+        }
+      }
+    }
+  }
+
   async _validateCustomExpectHandlers(request, response) {
     for (let i = 0; i < this.customExpectHandlers.length; i++) {
       const requiredHandler = this.customExpectHandlers[i];


Test patch:
diff --git a/test/component/bdd.spec.js b/test/component/bdd.spec.js
index 041acbe..bb5d776 100644
--- a/test/component/bdd.spec.js
+++ b/test/component/bdd.spec.js
@@ -143,6 +143,16 @@ describe('BDD - AutoReportRunner Disabled', () => {
     this.spec.response().to.have.status(200);
   });
 
+  it('should not return an error', async () => {
+    let err;
+    try {
+      this.spec.response().should.have.error();
+    } catch (error) {
+      err = error;
+    }
+    ce(err).not.undefined;
+  });
+
   after(() => {
     this.spec.end();
     settings.setReporterAutoRun(true);
diff --git a/test/component/expects.spec.js b/test/component/expects.spec.js
index 84eff6f..54b31fb 100644
--- a/test/component/expects.spec.js
+++ b/test/component/expects.spec.js
@@ -734,7 +734,7 @@ describe('Expects', () => {
 
   it('json snapshot - with matchers', async () => {
     await pactum.spec()
-    .useInteraction('get user with id 1')
+      .useInteraction('get user with id 1')
       .name('json snapshot - with matchers')
       .get('http://localhost:9393/api/users/1')
       .expectStatus(200)
@@ -742,7 +742,7 @@ describe('Expects', () => {
         id: like(1)
       });
     await pactum.spec()
-    .useInteraction('get user with id 1')
+      .useInteraction('get user with id 1')
       .name('json snapshot - with matchers')
       .get('http://localhost:9393/api/users/1')
       .expectStatus(200)
@@ -961,4 +961,59 @@ describe('Expects', () => {
     expect(err2).not.undefined;
   });
 
+  it('error - empty', async () => {
+    await pactum.spec()
+      .get('http://localhost:9392')
+      .expectError();
+  });
+
+  it('error - with string', async () => {
+    await pactum.spec()
+      .get('http://localhost:9392')
+      .expectError('ECONNREFUSED');
+  });
+
+  it('error - with object', async () => {
+    await pactum.spec()
+      .get('http://localhost:9392')
+      .expectError({ code: 'ECONNREFUSED' });
+  });
+
+  it('error - empty - fails', async () => {
+    let err;
+    try {
+      await pactum.spec()
+        .useInteraction('default get')
+        .get('http://localhost:9393/default/get')
+        .expectError();
+    } catch (error) {
+      err = error;
+    }
+    expect(err).not.undefined;
+  });
+
+  it('error - with string - fails', async () => {
+    let err;
+    try {
+      await pactum.spec()
+        .get('http://localhost:9392')
+        .expectError('ECONNRESET');
+    } catch (error) {
+      err = error;
+    }
+    expect(err).not.undefined;
+  });
+
+  it('error - with object - fails', async () => {
+    let err;
+    try {
+      await pactum.spec()
+        .get('http://localhost:9392')
+        .expectError({ code: 'ECONNRESET' });
+    } catch (error) {
+      err = error;
+    }
+    expect(err).not.undefined;
+  });
+
 });
\ No newline at end of file