Repository: node-nock/nock
Pull request #921 (2017-05-28 15:39:00)

Solves #920
Straightforward fix of #920

coveralls: 
[![Coverage Status](https://coveralls.io/builds/11722212/badge)](https://coveralls.io/builds/11722212)

Coverage increased (+0.02%) to 92.702% when pulling **a1095226507f46553f1051aaafbaf27b19554e7f on edorivai:master** into **17d7c25682ea520b0a255e933b64d5c7f355330f on node-nock:master**.


Patch:
diff --git a/.travis.yml b/.travis.yml
index 2cdf08b..086c7f1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,20 @@
+sudo: false
 language: node_js
 node_js:
   - 4
   - 6
-  - 7
-script: "npm run coverage && npm run integration"
-sudo: false
-after_success: "npm run coveralls"
+  - 8
+
+# Retry install on fail to avoid failing a build on network/disk/external errors
+install:
+  - travis_retry npm install
+
+script: npm run coverage && npm run integration
+after_success: npm run coveralls
+
+# Trigger a push build on master and greenkeeper branches + PRs build on every branches
+# Avoid double build on PRs (See https://github.com/travis-ci/travis-ci/issues/1147)
+branches:
+  only:
+    - master
+    - /^greenkeeper.*$/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b234cc9..186edcc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,124 @@
+9.0.18 / 2017-09-20
+===================
+
+  * fix(recorder): don’t JSON.stringify output if output_objects is not enabled
+    This is a follow up fix for https://github.com/node-nock/nock/pull/959
+  * feat: promisify nock back ([#798](https://github.com/node-nock/nock/issues/798))
+  * chore(CHANGELOG) v9.0.17
+
+9.0.17 / 2017-09-19
+===================
+
+  * 9.0.17
+  * 835 support regex for both host and port with allowUnmocked
+  * 835-added-fail-test-case
+  * docs(README) - fix info about mocking ([#971](https://github.com/node-nock/nock/issues/971))
+  * Document replyWithFile using Content-Type header
+  * fix readme typo
+    add missing closing paren
+  * fix grammar
+    change `the response event will occur immediately, but the IncomingMessage not it's end event until after the delay` -> `the response event will occur immediately, but the IncomingMessage will not emit it's end event until after the delay`
+  * Improve Match Logging
+    Currently, matching logging does not include the method or path of the request.  I have added these to make debugging non-matches easier.
+  * Update README.md
+    please review for accuracy. I actually don't know how it behaves but this section was very confusing to read.
+  * fix: printing recorded requests with output_objects resulted in output of [Object Object]
+  * chore(CHANGELOG): 9.0.16
+
+9.0.16 / 2017-09-18
+===================
+
+  * 9.0.16
+  * fix: match headers with falsy values
+  * test: match headers with falsy values
+  * chore(travis): don’t build twice for PRs and retry npm install
+  * chore(travis): don’t test in node v7
+    It’s no longer supported
+  * chore: remove console logs in tests
+  * docs(CHANGELOG): update for 9.0.15
+
+9.0.15 / 2017-09-18
+===================
+
+  * 9.0.15
+  * fix: `nock.define()` respects badheaders option
+  * test: `nock.define()` ignores badheaders option
+  * docs(README): document `nock.recorder.clear()`
+
+9.0.14 / 2017-07-17
+===================
+
+  * Fixes for Node 8
+  * Merge pull request [#948](https://github.com/node-nock/nock/issues/948) from martinkuba/master
+    Fix for [#947](https://github.com/node-nock/nock/issues/947)
+  * www.amazon.com does not return any data
+  * Merge pull request [#929](https://github.com/node-nock/nock/issues/929) from martinkuba/node8
+    Updates to unblock Node 8
+  * Merge pull request [#942](https://github.com/node-nock/nock/issues/942) from michaelnisi/browserify
+    Skip browserify test because zombie fails @7.10
+  * Skip browserify test because zombie fails @7.10
+  * wrap http.get on Node 8 only
+  * workaround for socket hang up
+  * fix recorder when calling res.end(data)
+  * use req.setHeader()
+  * override http.get
+  * added Node 8 to Travis
+  * Merge pull request [#667](https://github.com/node-nock/nock/issues/667) from ksmith97/master
+    Updated debug namespace to match file.
+
+9.0.13 / 2017-04-10
+===================
+
+  * 9.0.13: Revert [#863](https://github.com/node-nock/nock/issues/863)
+  * Merge pull request [#880](https://github.com/node-nock/nock/issues/880) from node-nock/revert-v9.0.12-issue-863
+    Revert "Merge branch 'fasterize-fix_match_body'"
+  * Revert "Merge branch 'fasterize-fix_match_body'"
+    This reverts commit 8333e3aef1e79dc2ed96dca819124f4a18f1af4d, reversing
+    changes made to 30881e2a72dbb950e771f912c5f9ddbcb91c0acd.
+
+9.0.12 / 2017-04-10
+===================
+
+  * 9.0.12: Use key length in array/object comparison
+  * Merge branch 'fasterize-fix_match_body'
+    * fasterize-fix_match_body:
+    [[#863](https://github.com/node-nock/nock/issues/863)] Fix matchBody for object and array comparison
+  * Merge branch 'fix_match_body' of https://github.com/fasterize/nock into fasterize-fix_match_body
+    * 'fix_match_body' of https://github.com/fasterize/nock:
+    [[#863](https://github.com/node-nock/nock/issues/863)] Fix matchBody for object and array comparison
+  * [[#863](https://github.com/node-nock/nock/issues/863)] Fix matchBody for object and array comparison
+
+9.0.11 / 2017-03-29
+===================
+
+  * 9.0.11: Fixes for [#856](https://github.com/node-nock/nock/issues/856) (pending mocks) and [#425](https://github.com/node-nock/nock/issues/425) (multipart forms)
+  * Merge pull request [#834](https://github.com/node-nock/nock/issues/834) from delijati/master
+    PR [#834](https://github.com/node-nock/nock/issues/834): Don't remove newlines from multipart form reqests
+  * Merge pull request [#857](https://github.com/node-nock/nock/issues/857) from disrupticons/fixing-issue-856
+    Fix [#856](https://github.com/node-nock/nock/issues/856): nock.cleanAll() should remove pending mocks
+  * fixing test description
+
+9.0.10 / 2017-03-28
+===================
+
+  * 9.0.10: Versioned tested and better .npmignore
+  * adding test for multipart match body
+  * Merge pull request [#858](https://github.com/node-nock/nock/issues/858) from fabiosantoscode/patch-1
+    Add some big folders to .npmignore
+  * Add some big folders to .npmignore
+  * Remove pending mocks from scopes on nock.cleanAll()
+  * Merge pull request [#842](https://github.com/node-nock/nock/issues/842) from node-nock/feature-832-versioned-tests
+    Feature [#832](https://github.com/node-nock/nock/issues/832): Versioned tests
+  * [#832](https://github.com/node-nock/nock/issues/832) Add glob as dev dep
+  * [#832](https://github.com/node-nock/nock/issues/832) Add integration tests to travis
+  * [#832](https://github.com/node-nock/nock/issues/832) Cleanup sub dep install script
+  * [#832](https://github.com/node-nock/nock/issues/832) Add tests for request & popsicle
+  * [#832](https://github.com/node-nock/nock/issues/832) Remove socketTimeout test
+  * [#832](https://github.com/node-nock/nock/issues/832) Run got tests using common test runner
+  * 832 Versioned tests for got module
+  * 832 Add sub dep install from NR
+  * Changelog v9.0.9
+
 9.0.9 / 2017-02-28
 ==================
 
@@ -25,6 +146,7 @@
     Fix interceptor if query have no query strings
   * chore(package): update superagent to version 3.5.0
     https://greenkeeper.io/
+  * fix for the "fix matchBody for multipart requests [#425](https://github.com/node-nock/nock/issues/425)"
   * Test against request 2.79.0, with which timeout issue occured.
   * Add Visual Studio Code folder into gitignore
   * Update the failed test case
@@ -68,116 +190,3 @@
     Update tap to version 10.0.0 🚀
   * Bug [#818](https://github.com/node-nock/nock/issues/818) Assert mutiple set-cookie headers are included in object
   * Bug [#818](https://github.com/node-nock/nock/issues/818) Send back multiple headers if multiple present
-
-9.0.3 / 2017-02-06
-==================
-
-  * 9.0.3: Change error message for unmatched requests
-  * Merge pull request [#819](https://github.com/node-nock/nock/issues/819) from node-nock/chore-add-ian-contrib
-    Add Ian WS to contrib
-  * Add Ian WS to contrib
-  * Merge pull request [#597](https://github.com/node-nock/nock/issues/597) from ianwsperber/feature-better-unmatched-request-error
-    Better unmatched request error
-  * chore(package): update tap to version 10.0.0
-    https://greenkeeper.io/
-  * Merge pull request [#799](https://github.com/node-nock/nock/issues/799) from node-nock/greenkeeper-tap-9.0.3
-    Update tap to version 9.0.3 🚀
-  * Merge pull request [#797](https://github.com/node-nock/nock/issues/797) from Thebigbignooby/patch-1
-    Fix typo in README
-  * Use pretty JSON and no longer stringify body
-  * Only include body in stringified request for non-GET requests
-  * Update intercept tests for new stringified format
-  * Format stringified request as JSON and include headers
-  * Baseline test for common.stringifyRequest
-  * chore(package): update tap to version 9.0.3
-    https://greenkeeper.io/
-  * fix typo
-    URL begins with a hard u, pronounced "you" and so should be preceded by "a", not "an"
-  * Set `this` to request on socket event.
-    This makes behavior consistent with the Node.js HTTP module
-    (https://github.com/nodejs/node/blob/v6.0.0/lib/_http_client.js#L541).
-  * Merge pull request [#757](https://github.com/node-nock/nock/issues/757) from node-nock/greenkeeper-tap-8.0.1
-    Update tap to version 8.0.1 🚀
-  * Add test for delay/timeout
-  * chore(package): update tap to version 8.0.1
-    https://greenkeeper.io/
-  * Merge pull request [#755](https://github.com/node-nock/nock/issues/755) from node-nock/greenkeeper-lodash-4.17.2
-    Update lodash to version 4.17.2 🚀
-  * chore(package): update lodash to version 4.17.2
-    https://greenkeeper.io/
-  * Merge pull request [#760](https://github.com/node-nock/nock/issues/760) from node-nock/greenkeeper-nyc-10.0.0
-    Update nyc to version 10.0.0 🚀
-  * chore(package): update nyc to version 10.0.0
-    https://greenkeeper.io/
-  * Merge pull request [#683](https://github.com/node-nock/nock/issues/683) from szdavid92/feat/gzip-content-type-decoder
-    Add gzip and deflate decoder for json content type
-  * Merge pull request [#743](https://github.com/node-nock/nock/issues/743) from node-nock/greenkeeper-zombie-5.0.1
-    Update zombie to version 5.0.1 🚀
-  * Merge pull request [#738](https://github.com/node-nock/nock/issues/738) from node-nock/greenkeeper-lodash-4.16.6
-    Update lodash to version 4.16.6 🚀
-  * chore(package): update zombie to version 5.0.1
-    https://greenkeeper.io/
-  * chore(package): update lodash to version 4.16.6
-    https://greenkeeper.io/
-  * fix wording
-  * add gzip and deflate decoder for json content type
-  * Add nodejs v7 to travis config
-  * Update CHANGELOG
-
-9.0.2 / 2016-10-28
-==================
-
-  * 9.0.2
-  * Merge branch 'airplane'
-  * Update CHANGELOG
-
-9.0.1 / 2016-10-28
-==================
-
-  * 9.0.1
-  * Merge pull request [#734](https://github.com/node-nock/nock/issues/734) from RobertWHurst/master
-    Fixes regression introduced by [#719](https://github.com/node-nock/nock/issues/719)
-  * Implement AIRPLANE mode to skip internet-dependent tests
-  * Utilize tap's skip functionality to toggle tests based on available features
-  * Add more notes on contributing section
-  * Move "How does it work?" section to the top of the README
-  * Fix [#731](https://github.com/node-nock/nock/issues/731)
-  * test for empty object in body
-  * Update README & CHANGELOG
-
-9.0.0 / 2016-10-23
-==================
-
-  * 9.0.0
-  * Drop support for node versions 0.10, 0.11, 0.12 and 5
-  * Add section on node versions support
-  * Update CHANGELOG
-
-8.2.0 / 2016-10-23
-==================
-
-  * 8.2.0
-  * Add changelog to the dev dependencies
-  * Ignore browserify-bundle.js
-  * Update browserify-bundle.js
-  * Merge pull request [#723](https://github.com/node-nock/nock/issues/723) from pimterry/optional-mocks
-    Add optionally() and make isDone and pendingMocks consistent.
-  * Include all review markup changes in browserify-bundle
-  * Add .activeMocks() to recreate previous .pendingMocks() behaviour
-  * Add times+optional and persist+optional tests
-  * Document assumption in pendingMocks()
-  * Clarify optional mock behaviour a little in README
-  * Merge pull request [#721](https://github.com/node-nock/nock/issues/721) from pimterry/removeInterceptor-from-pending
-    Update PendingMocks when an interceptor is removed
-  * Merge pull request [#722](https://github.com/node-nock/nock/issues/722) from Ginden/no-garbage-in-npm
-    Removed coverage folder from npm
-  * Merge pull request [#668](https://github.com/node-nock/nock/issues/668) from abdulito/master
-    Support http OPTIONS method
-  * Merge pull request [#725](https://github.com/node-nock/nock/issues/725) from node-nock/greenkeeper-async-2.1.1
-    Update async to version 2.1.1 🚀
-  * Merge pull request [#719](https://github.com/node-nock/nock/issues/719) from ltegman/fix/body-spec-regex-in-array
-    Handle arrays like objects in match_body
-  * Handle arrays like objects in match_body
-  * chore(package): update async to version 2.1.1
-    https://greenkeeper.io/
-  * Ad documentation for optionally()
diff --git a/README.md b/README.md
index ab2323a..5b5af7d 100644
--- a/README.md
+++ b/README.md
@@ -135,6 +135,7 @@ Then the test can call the module, and the module will do the HTTP requests.
 When you setup an interceptor for a URL and that interceptor is used, it is removed from the interceptor list.
 This means that you can intercept 2 or more calls to the same URL and return different things on each of them.
 It also means that you must setup one interceptor for each request you are going to have, otherwise nock will throw an error because that URL was not present in the interceptor list.
+If you don’t want interceptors to be removed as they are used, you can use the [.persist()](#persist) method.
 
 ## Specifying hostname
 
@@ -328,7 +329,7 @@ or even as a file:
 ```js
 var scope = nock('http://myapp.iriscouch.com')
                 .get('/')
-                .replyWithFile(200, __dirname + '/replies/user.json');
+                .replyWithFile(200, __dirname + '/replies/user.json', { 'Content-Type': 'application/json' });
 ```
 
 Instead of an object or a buffer you can also pass in a callback to be evaluated for the value of the response body:
@@ -580,7 +581,7 @@ Nock supports any HTTP verb, and it has convenience methods for the GET, POST, P
 You can intercept any HTTP verb using `.intercept(path, verb [, requestBody [, options]])`:
 
 ```js
-scope('http://my.domain.com')
+var scope = nock('http://my.domain.com')
   .intercept('/path', 'PATCH')
   .reply(304);
 ```
@@ -637,7 +638,7 @@ nock('http://my.server.com')
   .reply(200, '<html></html>')
 ```
 
-NOTE: the [`'response'`](http://nodejs.org/api/http.html#http_event_response) event will occur immediately, but the [IncomingMessage](http://nodejs.org/api/http.html#http_http_incomingmessage) not emit it's `'end'` event until after the delay.
+NOTE: the [`'response'`](http://nodejs.org/api/http.html#http_event_response) event will occur immediately, but the [IncomingMessage](http://nodejs.org/api/http.html#http_http_incomingmessage) will not emit it's `'end'` event until after the delay.
 
 ## Delay the response
 
@@ -656,7 +657,7 @@ nock('http://my.server.com')
  delay({
     head: headDelayInMs,
     body: bodyDelayInMs
- }
+ })
  ```
 
  for example
@@ -728,9 +729,9 @@ var scope = nock('http://myapp.iriscouch.com')
 
 ## Scope filtering
 
-You can filter the scope (protocol, domain and port through) of a nock through a function. This filtering functions is defined at the moment of defining the nock's scope through its optional `options` parameters:
+You can filter the scope (protocol, domain or port) of nock through a function. The filtering function is accepted at the `filteringScope` field of the `options` argument.
 
-This can be useful, for instance, if you have a node module that randomly changes subdomains to which it sends requests (e.g. Dropbox node module is like that)
+This can be useful if you have a node module that randomly changes subdomains to which it sends requests, e.g., the Dropbox node module behaves like this.
 
 ```js
 var scope = nock('https://api.dropbox.com', {
@@ -1077,7 +1078,7 @@ var nockCalls = nock.recorder.play();
 
 The `nockCalls` var will contain an array of strings representing the generated code you need.
 
-Copy and paste that code into your tests, customize at will, and you're done!
+Copy and paste that code into your tests, customize at will, and you're done! You can call `nock.recorder.reset()` to remove already recorded calls from the array that `nock.recorder.play()` returns.
 
 (Remember that you should do this one test at a time).
 
@@ -1303,6 +1304,17 @@ nockBack('zomboFixture.json', function(nockDone) {
 });
 ```
 
+If your tests are using promises then use `nockBack` like this:
+
+```
+return nockBack('promisedFixture.json')
+  .then(({nockDone, context}) => {
+    //  do your tests returning a promise and chain it with
+    //  `.then(nockDone);`
+  });
+});
+```
+
 ### Options
 
 As an optional second parameter you can pass the following options
diff --git a/lib/back.js b/lib/back.js
index 5ce12a7..94ce962 100644
--- a/lib/back.js
+++ b/lib/back.js
@@ -1,5 +1,7 @@
 'use strict';
 
+/* global Promise */
+
 var _ = require('lodash');
 var nock = require('./scope');
 var recorder = require('./recorder');
@@ -31,10 +33,12 @@ try {
  * nock the current function with the fixture given
  *
  * @param {string}   fixtureName  - the name of the fixture, e.x. 'foo.json'
- * @param {object}   options      - [optional], extra options for nock with, e.x. { assert: true }
- * @param {function} nockedFn     - the callback function to be executed with the given fixture being loaded,
- *                                  the function will be called with { scopes: loaded_nocks || [] } set as this
- *
+ * @param {object}   options      - [optional] extra options for nock with, e.x. `{ assert: true }`
+ * @param {function} nockedFn     - [optional] callback function to be executed with the given fixture being loaded;
+ *                                  if defined the function will be called with context `{ scopes: loaded_nocks || [] }`
+ *                                  set as `this` and `nockDone` callback function as first and only parameter;
+ *                                  if not defined a promise resolving to `{nockDone, context}` where `context` is
+ *                                  aforementioned `{ scopes: loaded_nocks || [] }`
  *
  * List of options:
  *
@@ -52,9 +56,19 @@ function Back (fixtureName, options, nockedFn) {
                       '\trequire(nock).back.fixtures = \'/path/to/fixures/\'');
   }
 
-  if( arguments.length === 2 ) {
-    nockedFn = options;
+  if (!_.isString(fixtureName)) {
+    throw new Error('Parameter fixtureName must be a string');
+  }
+
+  if( arguments.length === 1 ) {
     options = {};
+  } else if( arguments.length === 2 ) {
+    // If 2nd parameter is a function then `options` has been omitted
+    // otherwise `options` haven't been omitted but `nockedFn` was.
+    if (_.isFunction(options)) {
+      nockedFn = options;
+      options = {};
+    }
   }
 
   _mode.setup();
@@ -69,7 +83,12 @@ function Back (fixtureName, options, nockedFn) {
 
   debug('context:', context);
 
-  nockedFn.call(context, nockDone);
+  // If nockedFn is a function then invoke it, otherwise return a promise resolving to nockDone.
+  if (_.isFunction(nockedFn)) {
+    nockedFn.call(context, nockDone);
+  } else {
+    return Promise.resolve({nockDone: nockDone, context: context});
+  }
 }
 
 
@@ -299,7 +318,7 @@ var Modes = {
 
 Back.setMode = function(mode) {
   if( !Modes.hasOwnProperty(mode) ) {
-    throw new Error ('some usage error');
+    throw new Error ('Unknown mode: ' + mode);
   }
 
   Back.currentMode = mode;
diff --git a/lib/common.js b/lib/common.js
index 82b88e9..d302c34 100644
--- a/lib/common.js
+++ b/lib/common.js
@@ -2,6 +2,7 @@
 
 var _ = require('lodash');
 var debug = require('debug')('nock.common');
+var semver = require('semver')
 
 /**
  * Normalizes the request options so that it always has `host` property.
@@ -120,7 +121,8 @@ var overrideRequests = function(newRequest) {
           http: require('http'),
           https: require('https')
         }[moduleName],
-        overriddenRequest = module.request;
+        overriddenRequest = module.request,
+        overriddenGet = module.get;
 
     if(requestOverride[moduleName]) {
       throw new Error('Module\'s request already overridden for ' + moduleName + ' protocol.');
@@ -129,7 +131,8 @@ var overrideRequests = function(newRequest) {
     //  Store the properties of the overridden request so that it can be restored later on.
     requestOverride[moduleName] = {
       module: module,
-      request: overriddenRequest
+      request: overriddenRequest,
+      get: overriddenGet
     };
 
     module.request = function(options, callback) {
@@ -137,6 +140,14 @@ var overrideRequests = function(newRequest) {
       return newRequest(proto, overriddenRequest.bind(module), options, callback);
     };
 
+    if (semver.satisfies(process.version, '>=8')) {
+      module.get = function(options, callback) {
+        var req = newRequest(proto, overriddenRequest.bind(module), options, callback);
+        req.end();
+        return req;
+      }
+    }
+
     debug('- overridden request for', proto);
   });
 };
@@ -155,6 +166,7 @@ var restoreOverriddenRequests = function() {
     var override = requestOverride[proto];
     if(override) {
       override.module.request = override.request;
+      override.module.get = override.get;
       debug('- restored request for', proto);
     }
   });
diff --git a/lib/interceptor.js b/lib/interceptor.js
index 5de6e2e..aab06dd 100755
--- a/lib/interceptor.js
+++ b/lib/interceptor.js
@@ -1,19 +1,19 @@
 'use strict';
 
-var mixin           = require('./mixin')
-    , matchBody       = require('./match_body')
-    , common          = require('./common')
-    , _               = require('lodash')
-    , debug           = require('debug')('nock.interceptor')
-    , stringify       = require('json-stringify-safe')
-    , util            = require('util')
-    , qs              = require('qs');
+var mixin = require('./mixin')
+    , matchBody = require('./match_body')
+    , common = require('./common')
+    , _ = require('lodash')
+    , debug = require('debug')('nock.interceptor')
+    , stringify = require('json-stringify-safe')
+    , util = require('util')
+    , qs = require('qs');
 
 var fs;
 
 try {
     fs = require('fs');
-} catch(err) {
+} catch (err) {
     // do nothing, we're in the browser
 }
 
@@ -102,8 +102,8 @@ Interceptor.prototype.reply = function reply(statusCode, body, rawHeaders) {
     //  If the content is not encoded we may need to transform the response body.
     //  Otherwise we leave it as it is.
     if (!common.isContentEncoded(this.headers)) {
-        if (body && typeof(body) !== 'string' &&
-            typeof(body) !== 'function' &&
+        if (body && typeof (body) !== 'string' &&
+            typeof (body) !== 'function' &&
             !Buffer.isBuffer(body) &&
             !common.isStream(body)) {
             try {
@@ -117,7 +117,7 @@ Interceptor.prototype.reply = function reply(statusCode, body, rawHeaders) {
                 if (this.scope.contentLen) {
                     this.headers['content-length'] = body.length;
                 }
-            } catch(err) {
+            } catch (err) {
                 throw new Error('Error encoding response body into JSON');
             }
         }
@@ -130,7 +130,7 @@ Interceptor.prototype.reply = function reply(statusCode, body, rawHeaders) {
 };
 
 Interceptor.prototype.replyWithFile = function replyWithFile(statusCode, filePath, headers) {
-    if (! fs) {
+    if (!fs) {
         throw new Error('No fs');
     }
     var readStream = fs.createReadStream(filePath);
@@ -143,7 +143,7 @@ Interceptor.prototype.replyWithFile = function replyWithFile(statusCode, filePat
 // https://github.com/pgte/nock/issues/163
 Interceptor.prototype.reqheaderMatches = function reqheaderMatches(options, key) {
     //  We don't try to match request headers if these weren't even specified in the request.
-    if (! options.headers) {
+    if (!options.headers) {
         return true;
     }
 
@@ -158,8 +158,7 @@ Interceptor.prototype.reqheaderMatches = function reqheaderMatches(options, key)
     //  NOTE: We use lower-case header field names throughout Nock.
     if (key === 'host' &&
         (_.isUndefined(header) ||
-        _.isUndefined(reqHeader)))
-    {
+            _.isUndefined(reqHeader))) {
         return true;
     }
 
@@ -193,14 +192,14 @@ Interceptor.prototype.match = function match(options, body, hostNameOnly) {
     if (this.scope.transformPathFunction) {
         path = this.scope.transformPathFunction(path);
     }
-    if (typeof(body) !== 'string') {
+    if (typeof (body) !== 'string') {
         body = body.toString();
     }
     if (this.scope.transformRequestBodyFunction) {
         body = this.scope.transformRequestBodyFunction(body, this._requestBody);
     }
 
-    var checkHeaders = function(header) {
+    var checkHeaders = function (header) {
         if (_.isFunction(header.value)) {
             return header.value(options.getHeader(header.name));
         }
@@ -214,7 +213,7 @@ Interceptor.prototype.match = function match(options, body, hostNameOnly) {
     }
 
     var reqHeadersMatch =
-        ! this.reqheaders ||
+        !this.reqheaders ||
         Object.keys(this.reqheaders).every(this.reqheaderMatches.bind(this, options));
 
     if (!reqHeadersMatch) {
@@ -264,9 +263,9 @@ Interceptor.prototype.match = function match(options, body, hostNameOnly) {
         // Only check for query string matches if this.queries is an object
         if (_.isObject(this.queries)) {
 
-            if(_.isFunction(this.queries)){
+            if (_.isFunction(this.queries)) {
                 matchQueries = this.queries(queries);
-            }else {
+            } else {
                 // Make sure that you have an equal number of keys. We are
                 // looping through the passed query params and not the expected values
                 // if the user passes fewer query params than expected but all values
@@ -284,42 +283,42 @@ Interceptor.prototype.match = function match(options, body, hostNameOnly) {
                         var expVal = self.queries[key];
                         var isMatch = true;
                         if (val === undefined || expVal === undefined) {
-                        isMatch = false;
-                    } else if (expVal instanceof RegExp) {
-                      isMatch = common.matchStringOrRegexp(val, expVal);
-                    } else if (_.isArray(expVal) || _.isObject(expVal)) {
-                      isMatch = _.isEqual(val, expVal);
-                    } else {
-                      isMatch = common.matchStringOrRegexp(val, expVal);
-                    }
-                matchQueries = matchQueries && !!isMatch;
-                });
+                            isMatch = false;
+                        } else if (expVal instanceof RegExp) {
+                            isMatch = common.matchStringOrRegexp(val, expVal);
+                        } else if (_.isArray(expVal) || _.isObject(expVal)) {
+                            isMatch = _.isEqual(val, expVal);
+                        } else {
+                            isMatch = common.matchStringOrRegexp(val, expVal);
+                        }
+                        matchQueries = matchQueries && !!isMatch;
+                    });
                 }
                 debug('matchQueries: %j', matchQueries);
             }
         }
 
         // Remove the query string from the path
-        if(queryIndex !== -1) {
+        if (queryIndex !== -1) {
             path = path.substr(0, queryIndex);
         }
     }
 
     if (typeof this.uri === 'function') {
         matches = matchQueries &&
-        method.toUpperCase() + ' ' + proto + '://' + options.host === this.baseUri &&
-        this.uri.call(this, path);
+            method.toUpperCase() + ' ' + proto + '://' + options.host === this.baseUri &&
+            this.uri.call(this, path);
     } else {
         matches = method === this.method &&
-        common.matchStringOrRegexp(matchKey, this.basePath) &&
-        common.matchStringOrRegexp(path, this.path) &&
-        matchQueries;
+            common.matchStringOrRegexp(matchKey, this.basePath) &&
+            common.matchStringOrRegexp(path, this.path) &&
+            matchQueries;
     }
 
     // special logger for query()
     if (queryIndex !== -1) {
         this.scope.logger('matching ' + matchKey + '?' + queryString + ' to ' + this._key +
-        ' with query(' + stringify(this.queries) + '): ' + matches);
+            ' with query(' + stringify(this.queries) + '): ' + matches);
     } else {
         this.scope.logger('matching ' + matchKey + ' to ' + this._key + ': ' + matches);
     }
@@ -335,6 +334,8 @@ Interceptor.prototype.match = function match(options, body, hostNameOnly) {
 };
 
 Interceptor.prototype.matchIndependentOfBody = function matchIndependentOfBody(options) {
+    var isRegex = _.isRegExp(this.__nock_scopeKey) && _.isRegExp(this.path);
+
     var method = (options.method || 'GET').toUpperCase()
         , path = options.path
         , proto = options.proto;
@@ -343,7 +344,7 @@ Interceptor.prototype.matchIndependentOfBody = function matchIndependentOfBody(o
         path = this.scope.transformPathFunction(path);
     }
 
-    var checkHeaders = function(header) {
+    var checkHeaders = function (header) {
         return options.getHeader && common.matchStringOrRegexp(options.getHeader(header.name), header.value);
     };
 
@@ -352,8 +353,14 @@ Interceptor.prototype.matchIndependentOfBody = function matchIndependentOfBody(o
         return false;
     }
 
+    var comparisonKey = isRegex ? this.__nock_scopeKey : this._key;
     var matchKey = method + ' ' + proto + '://' + options.host + path;
-    return this._key === matchKey;
+
+    if (isRegex) {
+        return !!matchKey.match(comparisonKey) && !!path.match(this.path);
+    }
+
+    return comparisonKey === matchKey;
 };
 
 Interceptor.prototype.filteringPath = function filteringPath() {
@@ -404,7 +411,7 @@ Interceptor.prototype.query = function query(queries) {
         this.queries = queries;
     }
 
-    if(_.isFunction(queries)){
+    if (_.isFunction(queries)) {
         this.queries = queries;
         return this;
     }
@@ -521,7 +528,7 @@ Interceptor.prototype.delayConnection = function delayConnection(ms) {
     return this;
 };
 
-Interceptor.prototype.getTotalDelay =function getTotalDelay() {
+Interceptor.prototype.getTotalDelay = function getTotalDelay() {
     return this.delayInMs + this.delayConnectionInMs;
 };
 
@@ -534,4 +541,4 @@ Interceptor.prototype.getTotalDelay =function getTotalDelay() {
 Interceptor.prototype.socketDelay = function socketDelay(ms) {
     this.socketDelayInMs = ms;
     return this;
-};
+};
\ No newline at end of file
diff --git a/lib/match_body.js b/lib/match_body.js
index 83784f0..4c1a44f 100644
--- a/lib/match_body.js
+++ b/lib/match_body.js
@@ -59,6 +59,10 @@ function deepEqualExtended(spec, body) {
   }
   if (spec && (spec.constructor === Object || spec.constructor === Array) && body) {
     var keys = Object.keys(spec);
+    var bodyKeys = Object.keys(body);
+    if (keys.length !== bodyKeys.length) {
+      return false;
+    }
     for (var i = 0; i < keys.length; i++) {
       if (!deepEqualExtended(spec[keys[i]], body[keys[i]])) {
         return false;
diff --git a/lib/recorder.js b/lib/recorder.js
index 0dbecdf..a0f118d 100644
--- a/lib/recorder.js
+++ b/lib/recorder.js
@@ -8,6 +8,7 @@ var debug = require('debug')('nock.recorder');
 var _ = require('lodash');
 var Stream = require('stream');
 var URL = require('url');
+var semver = require('semver')
 
 var SEPARATOR = '\n<<<<<<-- cut here -->>>>>>\n';
 var recordingInProgress = false;
@@ -281,6 +282,9 @@ function record(rec_options) {
 
         if (!dont_print) {
           if (use_separator) {
+            if (typeof out !== 'string') {
+              out = JSON.stringify(out, null, 2);
+            }
             logging(SEPARATOR + out + SEPARATOR);
           } else {
             logging(out);
@@ -336,10 +340,25 @@ function record(rec_options) {
           }
           bodyChunks.push(data);
         }
-        oldWrite.call(req, data);
+        oldWrite.apply(req, arguments);
       }
     };
 
+    // in Node 8, res.end() does not call res.write() directly
+    if (semver.satisfies(process.version, '>=8')) {
+      var oldEnd = req.end;
+      req.end = function(data, encoding) {
+        if (data) {
+          debug(thisRecordingId, 'new', proto, 'body chunk');
+          if (! Buffer.isBuffer(data)) {
+            data = new Buffer(data, encoding);
+          }
+          bodyChunks.push(data);
+        }
+        oldEnd.apply(req, arguments);
+      };
+    }
+
     return req;
   });
 }
diff --git a/lib/request_overrider.js b/lib/request_overrider.js
index 59f8952..885aaf5 100644
--- a/lib/request_overrider.js
+++ b/lib/request_overrider.js
@@ -22,7 +22,7 @@ function getHeader(request, name) {
 
   var key = name.toLowerCase();
 
-  return request._headers[key];
+  return request.getHeader ? request.getHeader(key) : request._headers[key];
 }
 
 function setHeader(request, name, value) {
@@ -34,8 +34,12 @@ function setHeader(request, name, value) {
   request._headerNames = request._headerNames || {};
   request._removedHeader = request._removedHeader || {};
 
-  request._headers[key] = value;
-  request._headerNames[key] = name;
+  if (request.setHeader) {
+    request.setHeader(key, value);
+  } else {
+    request._headers[key] = value;
+    request._headerNames[key] = name;
+  }
 
   if (name == 'expect' && value == '100-continue') {
     timers.setImmediate(function() {
@@ -52,7 +56,8 @@ function setRequestHeaders(req, options, interceptor) {
   //  We mock request headers if these were specified.
   if (interceptor.reqheaders) {
     _.forOwn(interceptor.reqheaders, function(val, key) {
-      setHeader(req, key, val);
+      if (!_.isFunction(val))
+        setHeader(req, key, val);
     });
   }
 
@@ -268,7 +273,7 @@ function RequestOverrider(req, options, interceptors, remove, cb) {
     //  We again set request headers, now for our matched interceptor.
     setRequestHeaders(req, options, interceptor);
     interceptor.req = req;
-    req.headers = req._headers;
+    req.headers = req.getHeaders ? req.getHeaders() : req._headers;
 
     interceptor.scope.emit('request', req, interceptor);
 
diff --git a/lib/scope.js b/lib/scope.js
index 17ff13f..9543b02 100644
--- a/lib/scope.js
+++ b/lib/scope.js
@@ -297,6 +297,7 @@ function define(nockDefs) {
       , status     = getStatusFromDefinition(nockDef)
       , rawHeaders = nockDef.rawHeaders || []
       , reqheaders = nockDef.reqheaders || {}
+      , badheaders = nockDef.badheaders || []
       , body       = nockDef.body       || ''
       , options    = nockDef.options    || {};
 
@@ -305,6 +306,7 @@ function define(nockDefs) {
     //  be changing the user's options object so we clone it first.
     options = _.clone(options) || {};
     options.reqheaders = reqheaders;
+    options.badheaders = badheaders;
 
     //  Response is not always JSON as it could be a string or binary data or
     //  even an array of binary buffers (e.g. when content is enconded)
diff --git a/package.json b/package.json
index 979b8ea..9cd3c44 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
     "testing",
     "isolation"
   ],
-  "version": "9.0.13",
+  "version": "9.0.18",
   "author": "Pedro Teixeira <pedro.teixeira@gmail.com>",
   "contributors": [
     {
@@ -174,7 +174,8 @@
     "lodash": "~4.17.2",
     "mkdirp": "^0.5.0",
     "propagate": "0.4.0",
-    "qs": "^6.0.2"
+    "qs": "^6.0.2",
+    "semver": "^5.3.0"
   },
   "devDependencies": {
     "async": "^2.1.1",
@@ -197,6 +198,7 @@
     "restify": "^4.0.4",
     "restler": "3.4.0",
     "rimraf": "^2.3.2",
+    "semver": "^5.3.0",
     "superagent": "^3.5.0",
     "tap": "^10.0.0",
     "zombie": "^5.0.1"


Test patch:
diff --git a/tests/test_back.js b/tests/test_back.js
index 58d9ba6..b2e81c9 100644
--- a/tests/test_back.js
+++ b/tests/test_back.js
@@ -5,7 +5,8 @@ var nock    = require('../.')
   , tap     = require('tap')
   , http    = require('http')
   , fs      = require('fs')
-  , exists  = fs.existsSync;
+  , exists  = fs.existsSync
+  , _       = require('lodash');
 
 nock.enableNetConnect();
 
@@ -72,6 +73,55 @@ tap.test('nockBack throws an exception when fixtures is not set', function (t) {
 
 });
 
+tap.test('nockBack throws an exception when fixtureName is not a string', function (t) {
+
+  nockBack.fixtures = __dirname + '/fixtures';
+
+  try {
+    nockBack();
+  } catch (e) {
+    t.ok(true, 'excpected exception');
+    t.equal(e.message, 'Parameter fixtureName must be a string');
+    t.end();
+    return;
+  }
+
+  t.fail(true, false, 'test should have ended');
+
+});
+
+tap.test('nockBack returns a promise when neither options nor nockbackFn are specified', function (t) {
+
+  nockBack.fixtures = __dirname + '/fixtures';
+
+  var promise = nockBack('test-promise-fixture.json');
+  t.ok(promise);
+  promise.then((params) => {
+    var nockDone = params.nockDone;
+    var context = params.context;
+    t.assert(_.isFunction(nockDone));
+    t.assert(_.isObject(context));
+    t.end();
+  });
+
+});
+
+tap.test('nockBack returns a promise when nockbackFn is not specified', function (t) {
+
+  nockBack.fixtures = __dirname + '/fixtures';
+
+  var promise = nockBack('test-promise-fixture.json', {test: 'options'});
+  t.ok(promise);
+  promise.then((params) => {
+    var nockDone = params.nockDone;
+    var context = params.context;
+    t.assert(_.isFunction(nockDone));
+    t.assert(_.isObject(context));
+    t.end();
+  });
+
+});
+
 tap.test('nockBack wild tests', function (nw) {
 
   //  Manually disable net connectivity to confirm that dryrun enables it.
@@ -145,7 +195,7 @@ tap.test('nockBack dryrun tests', function (nw) {
 
     nockBack(fixture, function (done) {
       var req = http.request({
-          host: "www.amazon.com"
+          host: "amazon.com"
         , path: '/'
         , port: 80
         }, function(res) {
diff --git a/tests/test_body_match.js b/tests/test_body_match.js
index fb645cb..3476125 100644
--- a/tests/test_body_match.js
+++ b/tests/test_body_match.js
@@ -67,6 +67,24 @@ test('match body with empty object inside', function (t) {
   });
 })
 
+test('doesn\'t match body with mismatching keys', function (t) {
+  nock('http://encodingsareus.com')
+    .post('/', { a: 'a' })
+    .reply(200);
+
+  mikealRequest({
+    url: 'http://encodingsareus.com/',
+    method: 'post',
+    json: {
+      a: 'a',
+      b: 'b'
+    }
+  }, function(err) {
+    assert.ok(err);
+    t.end();
+  });
+});
+
 test('match body with form multipart', function(t) {
 
   nock('http://encodingsareus.com')
diff --git a/tests/test_browserify.js b/tests/test_browserify.js
index 06946dc..1751bb0 100644
--- a/tests/test_browserify.js
+++ b/tests/test_browserify.js
@@ -1,12 +1,14 @@
 'use strict';
 
+var semver = require('semver');
+if (semver.gt(process.versions.node, '7.9.0')) return
+
+var Browser = require('zombie');
+var Static = require('node-static');
+var browserify = require('browserify');
 var fs = require('fs');
-var path = require('path');
 var http = require('http');
-var browserify = require('browserify');
-var Static = require('node-static');
-var Browser = require('zombie');
-
+var path = require('path');
 var test = require('tap').test;
 var before = test;
 var after  = test;
@@ -40,7 +42,7 @@ test('run bundle', function(t) {
   var browser = new Browser();
 
   browser.on('error', function(err) {
-    console.error('BROWSER ERROR: ' + err.stack);
+    throw err
   });
 
   browser.visit('/', function() {
diff --git a/tests/test_https_allowunmocked.js b/tests/test_https_allowunmocked.js
index 9682f5f..c7d5a15 100644
--- a/tests/test_https_allowunmocked.js
+++ b/tests/test_https_allowunmocked.js
@@ -20,6 +20,5 @@ test('allowUnmocked for https', {skip: process.env.AIRPLANE}, function(t) {
     t.true(typeof body !== 'undefined', 'body should not be undefined');
     t.true(body.length !== 0, 'body should not be empty');
     t.end();
-    return console.log(resp.statusCode, 'body length: ', body.length);
   });
 });
diff --git a/tests/test_intercept.js b/tests/test_intercept.js
index 7d1f1c4..716ed51 100755
--- a/tests/test_intercept.js
+++ b/tests/test_intercept.js
@@ -58,7 +58,6 @@ test("allow override works (2)", function(t) {
   mikealRequest(options, function(err, resp, body) {
     scope.done();
     t.end();
-    return console.log(resp.statusCode, body);
   });
 });
 
@@ -436,7 +435,6 @@ test("get with reply callback returning array with headers", function(t) {
   }, function(res) {
     res.setEncoding('utf8');
     t.equal(res.statusCode, 202);
-    console.log('res.headers:', res.headers);
     t.deepEqual(res.headers, {
       'x-key': 'value',
       'x-key-2': 'value 2',
@@ -1361,7 +1359,6 @@ test("reply with date header", function(t){
     , path: '/'
     , port: 80
   }, function(res) {
-    console.error(res.headers);
     t.equal(res.headers['date'], date.toUTCString());
     res.on('end', function() {
       scope.done();
@@ -3472,7 +3469,6 @@ test('define() is backward compatible', function(t) {
   });
 
   req.on('error', function(err) {
-    console.error(err);
     //  This should never happen.
     t.ok(false, 'Error should never occur.');
     t.end();
@@ -3517,7 +3513,6 @@ test('define() works with non-JSON responses', function(t) {
   });
 
   req.on('error', function(err) {
-    console.error(err);
     //  This should never happen.
     t.ok(false, 'Error should never occur.');
     t.end();
@@ -3563,7 +3558,6 @@ test('define() works with binary buffers', function(t) {
   });
 
   req.on('error', function(err) {
-    console.error(err);
     //  This should never happen.
     t.ok(false, 'Error should never occur.');
     t.end();
@@ -3640,6 +3634,47 @@ test('define() uses reqheaders', function(t) {
 
 });
 
+test('define() uses badheaders', function(t) {
+  var nockDef = [{
+    "scope":"http://example.com",
+    "method":"GET",
+    "path":"/",
+    "status":401,
+    "badheaders": ['x-foo']
+  }, {
+    "scope":"http://example.com",
+    "method":"GET",
+    "path":"/",
+    "status":200,
+    "reqheaders": {
+      'x-foo': 'bar'
+    }
+  }];
+
+  var nocks = nock.define(nockDef);
+
+  t.ok(nocks);
+
+  var req = new http.request({
+    host: 'example.com',
+    method: 'GET',
+    path: '/',
+    headers: {
+      'x-foo': 'bar'
+    }
+  }, function(res) {
+    t.equal(res.statusCode, 200);
+
+    res.once('end', function() {
+      t.end();
+    });
+    // Streams start in 'paused' mode and must be started.
+    // See https://nodejs.org/api/stream.html#stream_class_stream_readable
+    res.resume();
+  });
+  req.end();
+});
+
 test('sending binary and receiving JSON should work ', function(t) {
   var scope = nock('http://example.com')
     .filteringRequestBody(/.*/, '*')
@@ -4094,6 +4129,29 @@ test('mocking succeeds even when mocked and specified request header names have
 
 });
 
+test('mocking succeeds when mocked and specified request headers have falsy values (#966)', function(t) {
+  var scope = nock('http://example.com', {
+    reqheaders: {
+      "x-foo": 0
+    }
+  })
+    .post('/resource')
+    .reply(200, { status: "ok" });
+
+  mikealRequest({
+    method: 'POST',
+    uri: 'http://example.com/resource',
+    headers: {
+      "X-Foo": 0
+    }
+  }, function(err, res, body) {
+    t.error(err);
+    t.equal(res.statusCode, 200);
+    t.end();
+  });
+
+});
+
 test('mocking succeeds even when host request header is not specified', function(t) {
   var scope = nock('http://example.com')
     .post('/resource')
@@ -4725,10 +4783,8 @@ test('no content type provided', function(t) {
       method: 'POST',
       headers: {}
   }, function(res) {
-    console.log('haz response');
     res.on('data', function() {});
     res.once('end', function() {
-      console.log('response ended');
       scope.done();
       t.ok(true);
       t.end();
@@ -5013,6 +5069,60 @@ test('query(true) will match when the path has no query', function (t) {
   })
 });
 
+test('match domain and path using regexp (#835)', function (t) {
+  nock.cleanAll();
+  var imgResponse = 'Matched Images Page';
+
+  var scope = nock(/google/)
+    .get(/img/)
+    .reply(200, imgResponse);
+
+  mikealRequest.get('http://www.google.com/imghp?hl=en', function (err, res, body) {
+    scope.done();
+    t.type(err, 'null');
+    t.equal(res.statusCode, 200);
+    t.equal(body, imgResponse);
+    t.end();
+  });
+});
+
+test('match multiple paths to domain using regexp with allowUnmocked (#835)', function (t) {
+  nock.cleanAll();
+
+  var nockOpts = { allowUnmocked: true };
+  var searchResponse = 'Matched Google Search Results Page';
+  var imgResponse = 'Matched Google Images Page';
+
+  var scope1 = nock(/google/, nockOpts)
+    .get(/imghp/)
+    .reply(200, imgResponse);
+
+  var scope2 = nock(/google/, nockOpts)
+    .get(/search\?/)
+    .reply(200, searchResponse);
+
+
+  mikealRequest.get('http://www.google.com', function (err, res, body) {
+    t.type(err, 'null');
+    t.equal(res.statusCode, 200);
+
+    mikealRequest.get('http://www.google.com/imghp?hl=en', function (err, res, body) {
+      scope1.done();
+      t.type(err, 'null');
+      t.equal(res.statusCode, 200);
+      t.equal(body, imgResponse);
+
+      mikealRequest.get('http://www.google.com/search?q=pugs', function (err, res, body) {
+        scope2.done();
+        t.type(err, 'null');
+        t.equal(res.statusCode, 200);
+        t.equal(body, searchResponse);
+        t.end();
+      });
+    });
+  });
+});
+
 test("teardown", function(t) {
   var leaks = Object.keys(global)
     .splice(globalCount, Number.MAX_VALUE);
diff --git a/tests/test_recorder.js b/tests/test_recorder.js
index 9376a27..a1c908f 100644
--- a/tests/test_recorder.js
+++ b/tests/test_recorder.js
@@ -280,8 +280,7 @@ test('records nonstandard ports', function(t) {
 
   //  Create test http server and perform the tests while it's up.
   var testServer = http.createServer(function(req, res) {
-    res.write(RESPONSE_BODY);
-    res.end();
+    res.end(RESPONSE_BODY);
   }).listen(8081, function(err) {
 
     t.equal(err, undefined);
@@ -731,8 +730,7 @@ test('works with clients listening for readable', {skip: process.env.AIRPLANE},
 
   //  Create test http server and perform the tests while it's up.
   var testServer = http.createServer(function(req, res) {
-    res.write(RESPONSE_BODY);
-    res.end();
+    res.end(RESPONSE_BODY);
   }).listen(8081, function(err) {
 
     // t.equal(err, undefined);