Repository: node-nock/nock
Pull request #597 (2016-06-23 13:52:51)

Feature: Better unmatched request error
Improves on error messaging as mentioned in #398. 

Error messages should now include header information & appear in format

```
{"method":"GET","url":"http://www.example.com:81/path/1","headers":{"cookie":"fiz=baz"},"body":"{\"foo\": \"bar\"}"}
```

**NOTE: NEEDS WORK**
I introduced unit tests for this feature, which are passing, but for a reason I have yet to determine seemingly unrelated _tests were failing on my local machine_ subsequent to my changes to the stringify function, without an obvious cause. I haven't spent a ton of time debugging, but since there's demand for this feature I wanted to get a PR up in case anyone would like to take a look and try running the tests :)



Patch:
diff --git a/lib/common.js b/lib/common.js
index 5e78fe3..71a4e0f 100644
--- a/lib/common.js
+++ b/lib/common.js
@@ -161,13 +161,21 @@ var restoreOverriddenRequests = function() {
   requestOverride = [];
 };
 
+/**
+ * Get high level information about request as string
+ * @param  {Object} options
+ * @param  {string} options.method
+ * @param  {string} options.port
+ * @param  {string} options.proto
+ * @param  {string} options.hostname
+ * @param  {string} options.path
+ * @param  {Object} options.headers
+ * @param  {string|object} body
+ * @return {string}
+ */
 function stringifyRequest(options, body) {
   var method = options.method || 'GET';
 
-  if (body && typeof(body) !== 'string') {
-    body = body.toString();
-  }
-
   var port = options.port;
   if (! port) port = (options.proto == 'https' ? '443' : '80');
 
@@ -178,7 +186,17 @@ function stringifyRequest(options, body) {
 
   if (port) port = ':' + port;
 
-  return method + ' ' + options.proto + '://' + options.hostname + port + options.path + ' ' + body;
+  var log = {
+    method: method,
+    url: options.proto + '://' + options.hostname + port + options.path,
+    headers: options.headers
+  };
+
+  if (body) {
+    log.body = body;
+  }
+
+  return JSON.stringify(log, null, 2);
 }
 
 function isContentEncoded(headers) {


Test patch:
diff --git a/tests/test_common.js b/tests/test_common.js
index 886a231..45950f9 100644
--- a/tests/test_common.js
+++ b/tests/test_common.js
@@ -158,3 +158,48 @@ tap.test('matchStringOrRegexp', function (t) {
   t.false(common.matchStringOrRegexp('to match', /not/), 'false if pattern is regex and target doesn\'t match');
   t.end();
 });
+
+tap.test('stringifyRequest', function (t) {
+  var getMockOptions = function () {
+    return {
+      method: "POST",
+      port: 81,
+      proto: 'http',
+      hostname: 'www.example.com',
+      path: '/path/1',
+      headers: {
+        cookie: 'fiz=baz'
+      }
+    };
+  }
+  var body = {"foo": "bar"};
+  var postReqOptions = getMockOptions();
+
+  t.equal(common.stringifyRequest(postReqOptions, body),
+    JSON.stringify({
+      "method":"POST",
+      "url":"http://www.example.com:81/path/1",
+      "headers":{
+        "cookie": "fiz=baz"
+      },
+      "body": {
+        "foo": "bar"
+      }
+    }, null, 2)
+  );
+
+  var getReqOptions = getMockOptions();
+  getReqOptions.method = "GET";
+
+  t.equal(common.stringifyRequest(getReqOptions, null),
+    JSON.stringify({
+      "method":"GET",
+      "url":"http://www.example.com:81/path/1",
+      "headers":{
+        "cookie": "fiz=baz"
+      }
+    }, null, 2)
+  );
+
+  t.end();
+});
diff --git a/tests/test_intercept.js b/tests/test_intercept.js
index 4e84d9b..83cde94 100644
--- a/tests/test_intercept.js
+++ b/tests/test_intercept.js
@@ -1868,7 +1868,7 @@ test("emits error if https route is missing", function(t) {
   // This listener is intentionally after the end call so make sure that
   // listeners added after the end will catch the error
   req.on('error', function (err) {
-    t.equal(err.message.trim(), 'Nock: No match for request GET https://google.com/abcdef892932');
+    t.equal(err.message.trim(), 'Nock: No match for request ' + JSON.stringify({"method":"GET","url":"https://google.com/abcdef892932"}, null, 2));
     t.end();
   });
 });
@@ -1893,7 +1893,7 @@ test("emits error if https route is missing", function(t) {
   // This listener is intentionally after the end call so make sure that
   // listeners added after the end will catch the error
   req.on('error', function (err) {
-    t.equal(err.message.trim(), 'Nock: No match for request GET https://google.com:123/dsadsads');
+    t.equal(err.message.trim(), 'Nock: No match for request ' + JSON.stringify({"method":"GET","url":"https://google.com:123/dsadsads"}, null, 2));
     t.end();
   });
 });
@@ -4574,7 +4574,7 @@ test('you must setup an interceptor for each request', function(t) {
     t.equal(body, 'First match', 'should match first request response body');
 
     mikealRequest.get('http://www.example.com/hey', function(error, res, body) {
-      t.equal(error && error.toString(), 'Error: Nock: No match for request GET http://www.example.com/hey ');
+      t.equal(error && error.toString(), 'Error: Nock: No match for request ' + JSON.stringify({"method":"GET","url":"http://www.example.com/hey","headers":{"host":"www.example.com"}}, null, 2));
       scope.done();
       t.end();
     });
@@ -4905,7 +4905,7 @@ test('query() with a function, function return false the query treat as Un-match
     .reply(200);
 
   mikealRequest('http://google.com/?i=should&pass=?', function(err, res) {
-    t.equal(err.message.trim(), 'Nock: No match for request GET http://google.com/?i=should&pass=?');
+    t.equal(err.message.trim(), 'Nock: No match for request ' + JSON.stringify({"method":"GET","url":"http://google.com/?i=should&pass=?","headers":{"host":"google.com"}}, null, 2));
     t.end();
   })
 });
@@ -4917,7 +4917,7 @@ test('query() will not match when a query string does not match name=value', fun
     .reply(200);
 
   mikealRequest('https://c.com/b?foo=baz', function(err, res) {
-    t.equal(err.message.trim(), 'Nock: No match for request GET https://c.com/b?foo=baz');
+    t.equal(err.message.trim(), 'Nock: No match for request ' + JSON.stringify({"method":"GET","url":"https://c.com/b?foo=baz","headers":{"host":"c.com"}}, null, 2));
     t.end();
   })
 });
@@ -4929,7 +4929,7 @@ test('query() will not match when a query string is present that was not registe
     .reply(200);
 
   mikealRequest('https://b.com/c?foo=bar&baz=foz', function(err, res) {
-    t.equal(err.message.trim(), 'Nock: No match for request GET https://b.com/c?foo=bar&baz=foz');
+    t.equal(err.message.trim(), 'Nock: No match for request ' + JSON.stringify({"method":"GET","url":"https://b.com/c?foo=bar&baz=foz","headers":{"host":"b.com"}}, null, 2));
     t.end();
   })
 });
@@ -4941,7 +4941,7 @@ test('query() will not match when a query string is malformed', function (t) {
     .reply(200);
 
   mikealRequest('https://a.com/d?foobar', function(err, res) {
-    t.equal(err.message.trim(), 'Nock: No match for request GET https://a.com/d?foobar');
+    t.equal(err.message.trim(), 'Nock: No match for request ' + JSON.stringify({"method":"GET","url":"https://a.com/d?foobar","headers":{"host":"a.com"}}, null, 2));
     t.end();
   })
 });
@@ -4958,7 +4958,7 @@ test('query() will not match when a query string has fewer correct values than e
     .reply(200);
 
   mikealRequest('http://google.com/?num=1str=fou', function(err, res) {
-    t.equal(err.message.trim(), 'Nock: No match for request GET http://google.com/?num=1str=fou');
+    t.equal(err.message.trim(), 'Nock: No match for request ' + JSON.stringify({"method":"GET","url":"http://google.com/?num=1str=fou","headers":{"host":"google.com"}}, null, 2));
     t.end();
   })
 });