Repository: node-nock/nock
Pull request #745 (2016-11-05 06:25:58)

fix broken link: allow-unmocked-requests-on-a-mocked-hostname


Patch:
diff --git a/.npmignore b/.npmignore
index 27cfc60..ec8ed9a 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,3 +1,5 @@
 node_modules
 .gitignore
 coverage
+.nyc_output
+tests
diff --git a/.travis.yml b/.travis.yml
index d54e4d5..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"
-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 1a615ae..186edcc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,200 +1,192 @@
-9.0.2 / 2016-10-28
-==================
-
-  * Update CHANGELOG
-  * Implement AIRPLANE mode to skip internet-dependent tests
-
-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)
-  * 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.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
 ==================
 
-  * 9.0.0
-  * Drop support for node versions 0.10, 0.11, 0.12 and 5
-  * Add section on node versions support
-  * Update CHANGELOG
+  * 9.0.9: Revert PR [#802](https://github.com/node-nock/nock/issues/802)
+  * Merge pull request [#840](https://github.com/node-nock/nock/issues/840) from node-nock/revert-802-fix-754
+    Revert "Fix request timeout no working"
+  * Revert "Fix request timeout no working"
+  * Changelog v9.0.8
 
-8.2.1 / 2016-10-28
+9.0.8 / 2017-02-27
 ==================
 
-  * 8.2.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)
-  * Fix [#731](https://github.com/node-nock/nock/issues/731)
-  * test for empty object in body
+  * 9.0.8: Fix for [#754](https://github.com/node-nock/nock/issues/754)
+  * Merge pull request [#836](https://github.com/node-nock/nock/issues/836) from node-nock/greenkeeper-superagent-3.5.0
+    Update superagent to version 3.5.0 🚀
+  * Merge pull request [#802](https://github.com/node-nock/nock/issues/802) from aleung/fix-754
+    Fix request timeout no working
 
-8.2.0 / 2016-10-23
+9.0.7 / 2017-02-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
+  * Changelog
+  * 9.0.7: Fix for [#610](https://github.com/node-nock/nock/issues/610)
+  * Merge pull request [#829](https://github.com/node-nock/nock/issues/829) from yodeyer/fixQs
+    Fix interceptor if query have no query strings
+  * chore(package): update superagent to version 3.5.0
     https://greenkeeper.io/
-  * Ad documentation for optionally()
-  * Add optionally(), to replace and improve the undocumented 'requireDone'
-  * Make pendingMocks and isDone behave consistently
-  * Removed coverage folder from npm
-  * Update PendingMocks when an interceptor is removed
-  * Update CHANGELOG
-
-8.1.0 / 2016-10-10
-==================
+  * 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
+  * Fix https://github.com/node-nock/nock/issues/754
 
-  * 8.1.0
-  * Enable harmony mode when running tests
-  * Remove duplicated Interceptor.replyWithFile function
-  * Merge pull request [#710](https://github.com/node-nock/nock/issues/710) from hugoduraes/master
-    Leave rawHeaders case untouched on nock's replies
-  * Leave rawHeaders case untouched on nock's replies
-  * Re-enable previously skipped tests
-  * Merge pull request [#586](https://github.com/node-nock/nock/issues/586) from Elergy/master
-    Fix some broken tests
-  * Add nodejs v6 to travis config
-  * use public TravisCI badge icon, which shows failing tests.
-  * Skip a couple of failing specs (until we get them fixed)
-  * Remove tests/test.js
-    Also:
-    - Use nyc as the CLI of istanbul
-    - Use tap binary directly for running tests
-  * Support http OPTIONS method
-  * Fix some issues to run library with Node v0.10:
-    1. Set lodash version to ~4.9.0
-    2. Add data-event listener for http's res message
-  * Tests that depends on amazon.com's statusCode are fixed
-    Symbol with charCode 160 is replaced to space
-  * Merge pull request [#531](https://github.com/node-nock/nock/issues/531) from node-nock/greenkeeper-request-2.71.0
-    Update request to version 2.71.0 🚀
-  * Merge branch 'master' of github.com:node-nock/nock
-  * added snyk vulerabilities badge
-  * chore(package): update request to version 2.71.0
-    http://greenkeeper.io/
-  * Merge pull request [#528](https://github.com/node-nock/nock/issues/528) from mooyoul/improve-readme-toc
-    Improved Table of Contents section of README
-  * Changed `About Interceptors` title of TOC
-    ... just to clarify
-  * Lint README.md
-  * Added some missing TOC entries
-  * Fixed broken TOC link
-  * changelog update
-
-8.0.0 / 2016-04-06
+9.0.6 / 2017-02-13
 ==================
 
-  * v8.0.0
-  * added missing dep
-  * Merge branch 'greenkeeper-request-2.70.0'
-  * Merge branch 'master' into greenkeeper-request-2.70.0
-  * making the latest version of lodash work
-  * Merge pull request [#521](https://github.com/node-nock/nock/issues/521) from node-nock/greenkeeper-update-all
-    Update all dependencies 🌴
-  * fixes for latest tap version
-  * chore(package): update request to version 2.70.0
-    http://greenkeeper.io/
-  * chore(package): update dependencies
-    http://greenkeeper.io/
-
-7.7.3 / 2016-04-04
-==================
+  * 9.0.6: Fix for [#827](https://github.com/node-nock/nock/issues/827)
+  * Merge pull request [#828](https://github.com/node-nock/nock/issues/828) from node-nock/bug-827-matchStringOrRegexp
+    Bug [#827](https://github.com/node-nock/nock/issues/827) Defensive coding around target
+  * Bug [#827](https://github.com/node-nock/nock/issues/827) Defensive coding around target
+  * Fail if interceptor has query parameters but not the query
 
-  * v7.7.3
-  * Merge pull request [#520](https://github.com/node-nock/nock/issues/520) from ericsaboia/master
-    properly remove interceptor with regex domain from the list after used
-  * properly remove interceptor with regex domain from the list after used
-  * Merge pull request [#519](https://github.com/node-nock/nock/issues/519) from Byron-TW/patch-1
-    Fix typo
-  * Fix typo
-  * changelog update
-
-7.7.2 / 2016-03-25
-==================
-
-  * v7.7.2
-  * v7.7.1
-  * Merge branch 'master' of github.com:node-nock/nock
-
-7.7.0 / 2016-03-25
+9.0.5 / 2017-02-10
 ==================
 
-  * v7.7.0
-  * Merge pull request [#514](https://github.com/node-nock/nock/issues/514) from kevinburkeshyp/fix-req-no-match
-    Fix nock.emitter.on('no match') undefined argument
-  * Merge pull request [#512](https://github.com/node-nock/nock/issues/512) from kevinburkeshyp/fix-typo
-    Fix typo in tests
-  * browserify bundle update
-  * request abort destroys socket. fixes [#511](https://github.com/node-nock/nock/issues/511)
-  * Fix nock.emitter.on('no match') undefined argument
-    Previously if you disabled net connect via `nock.disableNetConnect`,
-    `nock.emitter.on('no match')` would return undefined for its first argument,
-    since `req` is never initialized. Replaces `req` with `options`, which is
-    present and set to a Url instance.
-    Fixes the global nock.emitter.on event listener to remove all event listeners
-    at the end of each test, otherwise an event listener registered in one test
-    might bleed over into the next.
-    (This might not be the right fix, I'm happy to change as necessary).
-  * Fix typo in tests
-
-7.6.0 / 2016-03-25
-==================
-
-  * v7.6.0
-  * Merge pull request [#509](https://github.com/node-nock/nock/issues/509) from mihar/patch-2
-    Update README.md
-  * Merge pull request [#510](https://github.com/node-nock/nock/issues/510) from RedCattleWealth/master
-    query accept a function to determine if the query is matched
-  * update README
-  * query accept a function to determin if the query is matched
-    for some case object compare is not enough
-  * Update README.md
-  * Merge pull request [#501](https://github.com/node-nock/nock/issues/501) from reconbot/patch-1
-    Make the source of NetConnectNotAllowedError clear
-  * changelog update
-
-7.5.0 / 2016-03-20
+  * 9.0.5: Various bug fixes
+  * Merge pull request [#822](https://github.com/node-nock/nock/issues/822) from KingHenne/fix/isomorphic-fetch-basic-auth
+    Fix isomorphic fetch usage with basicAuth/matchHeader
+  * Merge pull request [#823](https://github.com/node-nock/nock/issues/823) from KingHenne/fix/nyc-config
+    Exclude test files from coverage reports
+  * Merge pull request [#769](https://github.com/node-nock/nock/issues/769) from RubenVerborgh/fix-socket-this
+    Fix this pointer on "socket" event
+  * exclude test files from coverage reports
+  * add text summary coverage reporter
+  * fix basicAuth/matchHeader interceptors with isomorphic-fetch
+  * add failing test for isomorphic-fetch and matchHeader
+  * add failing test for isomorphic-fetch and basicAuth
+  * Merge pull request [#821](https://github.com/node-nock/nock/issues/821) from node-nock/chore-changelog-v9.0.4
+    Update changelog using "npm run changelog"
+  * Update changelog using "npm run changelog"
+
+9.0.4 / 2017-02-07
 ==================
 
-  * v7.5.0
-  * Merge branch 'satazor-back-recorder-options'
-  * Merge branch 'back-recorder-options' of git://github.com/satazor/nock into satazor-back-recorder-options
-  * Merge branch 'satazor-request-promise'
-  * Add ability to pass options to recorder from nodeback.
-  * Do not automatically consume responses, respect original consumer.
-  * Make the source of NetConnectNotAllowedError clear
-  * Merge pull request [#480](https://github.com/node-nock/nock/issues/480) from JemiloII/patch-1
-    Use lodash size function
-  * coveralls badge url
-  * switched repo token
-  * pgte -> node-nock
-  * Merge branch 'master' of github.com:pgte/nock
-  * testing for [#496](https://github.com/node-nock/nock/issues/496)
-  * Merge pull request [#494](https://github.com/node-nock/nock/issues/494) from mihar/patch-1
-    README.md: Fix typo
-  * README.md: Fix typo
-  * v7.4.0
+  * 9.0.4: Fix bug where only single set-cookie header would be returned
+  * Merge pull request [#820](https://github.com/node-nock/nock/issues/820) from node-nock/bug-818-multiple-set-cookie
+    Bug 818 multiple set cookie
+  * Merge pull request [#813](https://github.com/node-nock/nock/issues/813) from node-nock/greenkeeper-tap-10.0.0
+    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
diff --git a/README.md b/README.md
index 1efba33..d4bd084 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,7 @@ For instance, if a module performs HTTP requests to a CouchDB server or makes HT
   * [Request Body filtering](#request-body-filtering)
   * [Request Headers Matching](#request-headers-matching)
   * [Optional Requests](#optional-requests)
-  * [Allow __unmocked__ requests on a mocked hostname](#allow-__unmocked__-requests-on-a-mocked-hostname)
+  * [Allow __unmocked__ requests on a mocked hostname](#allow-unmocked-requests-on-a-mocked-hostname)
 - [Expectations](#expectations)
   * [.isDone()](#isdone)
   * [.cleanAll()](#cleanall)
@@ -132,9 +132,10 @@ Then the test can call the module, and the module will do the HTTP requests.
 
 ## READ THIS! - About interceptors
 
-When you setup an interceptor for an URL and that interceptor is used, it is removed from the interceptor list.
+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 4995db5..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,19 +166,28 @@ var restoreOverriddenRequests = function() {
     var override = requestOverride[proto];
     if(override) {
       override.module.request = override.request;
+      override.module.get = override.get;
       debug('- restored request for', proto);
     }
   });
   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 +198,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) {
@@ -186,6 +216,11 @@ function isContentEncoded(headers) {
   return _.isString(contentEncoding) && contentEncoding !== '';
 }
 
+function contentEncoding(headers, encoder) {
+  var contentEncoding = _.get(headers, 'content-encoding');
+  return contentEncoding === encoder;
+}
+
 function isJSONContent(headers) {
   var contentType = _.get(headers, 'content-type');
   if (Array.isArray(contentType)) {
@@ -229,7 +264,15 @@ var headersArrayToObject = function (rawHeaders) {
   var headers = {};
 
   for (var i=0, len=rawHeaders.length; i<len; i=i+2) {
-    headers[rawHeaders[i]] = rawHeaders[i+1];
+    var key = rawHeaders[i];
+    var value = rawHeaders[i+1];
+
+    if (headers[key]) {
+      headers[key] = _.isArray(headers[key]) ? headers[key] : [headers[key]];
+      headers[key].push(value);
+    } else {
+      headers[key] = value;
+    }
   }
 
   return headers;
@@ -277,11 +320,9 @@ function percentEncode(str) {
 }
 
 function matchStringOrRegexp(target, pattern) {
-  if (pattern instanceof RegExp) {
-    return target.toString().match(pattern);
-  } else {
-    return target === pattern;
-  }
+  var str = target && target.toString ? target.toString() : target;
+
+  return pattern instanceof RegExp  ? str.match(pattern) : str === pattern;
 }
 
 // return [newKey, newValue]
@@ -337,6 +378,7 @@ exports.overrideRequests = overrideRequests;
 exports.restoreOverriddenRequests = restoreOverriddenRequests;
 exports.stringifyRequest = stringifyRequest;
 exports.isContentEncoded = isContentEncoded;
+exports.contentEncoding = contentEncoding;
 exports.isJSONContent = isJSONContent;
 exports.headersFieldNamesToLowerCase = headersFieldNamesToLowerCase;
 exports.headersFieldsArrayToLowerCase = headersFieldsArrayToLowerCase;
diff --git a/lib/intercept.js b/lib/intercept.js
index a2ca20e..34699b7 100644
--- a/lib/intercept.js
+++ b/lib/intercept.js
@@ -123,6 +123,11 @@ function remove(interceptor) {
 }
 
 function removeAll() {
+  Object.keys(allInterceptors).forEach(function(key) {
+    allInterceptors[key].scopes.forEach(function(interceptor) {
+      interceptor.scope.keyedInterceptors = {};
+    });
+  });
   allInterceptors = {};
 }
 
diff --git a/lib/interceptor.js b/lib/interceptor.js
old mode 100644
new mode 100755
index 335a9f8..aab06dd
--- 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.scope')
-    , 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) {
@@ -256,16 +255,17 @@ Interceptor.prototype.match = function match(options, body, hostNameOnly) {
     var queryString;
     var queries;
 
-    if (this.queries && (queryIndex = path.indexOf('?')) !== -1) {
-        queryString = path.slice(queryIndex + 1);
+    if (this.queries) {
+        queryIndex = path.indexOf('?');
+        queryString = (queryIndex !== -1) ? path.slice(queryIndex + 1) : '';
         queries = qs.parse(queryString);
 
         // 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
@@ -283,40 +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
-        path = path.substr(0, queryIndex);
+        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);
     }
@@ -332,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;
@@ -340,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);
     };
 
@@ -349,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() {
@@ -401,7 +411,7 @@ Interceptor.prototype.query = function query(queries) {
         this.queries = queries;
     }
 
-    if(_.isFunction(queries)){
+    if (_.isFunction(queries)) {
         this.queries = queries;
         return this;
     }
@@ -518,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;
 };
 
@@ -531,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 be5215a..4c1a44f 100644
--- a/lib/match_body.js
+++ b/lib/match_body.js
@@ -29,7 +29,7 @@ function matchBody(spec, body) {
     return body.match(spec);
   }
 
-  if (typeof spec === "string") {
+  if (!isMultipart && typeof spec === "string") {
     spec = spec.replace(/\r?\n|\r/g, '');
   }
 
@@ -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 f6e2ea3..885aaf5 100644
--- a/lib/request_overrider.js
+++ b/lib/request_overrider.js
@@ -12,7 +12,8 @@ var EventEmitter     = require('events').EventEmitter,
     debug            = require('debug')('nock.request_overrider'),
     timers           = require('timers'),
     ReadableStream   = require('stream').Readable,
-    globalEmitter    = require('./global_emitter');
+    globalEmitter    = require('./global_emitter'),
+    zlib             = require('zlib');
 
 function getHeader(request, name) {
   if (!request._headers) {
@@ -21,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) {
@@ -33,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() {
@@ -51,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);
     });
   }
 
@@ -192,7 +198,7 @@ function RequestOverrider(req, options, interceptors, remove, cb) {
   req.once = req.on = function(event, listener) {
     // emit a fake socket.
     if (event == 'socket') {
-      listener(req.socket);
+      listener.call(req, req.socket);
       req.socket.emit('connect', req.socket);
       req.socket.emit('secureConnect', req.socket);
     }
@@ -267,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);
 
@@ -295,6 +301,20 @@ function RequestOverrider(req, options, interceptors, remove, cb) {
 
     if (typeof interceptor.body === 'function') {
       if (requestBody && common.isJSONContent(options.headers)) {
+        if (requestBody && common.contentEncoding(options.headers, 'gzip')) {
+          if (typeof zlib.gunzipSync !== 'function') {
+            emitError(new Error('Gzip encoding is currently not supported in this version of Node.'));
+            return;
+          }
+          requestBody = String(zlib.gunzipSync(new Buffer(requestBody, 'hex')), 'hex')
+        } else if (requestBody && common.contentEncoding(options.headers, 'deflate')) {
+          if (typeof zlib.deflateSync !== 'function') {
+            emitError(new Error('Deflate encoding is currently not supported in this version of Node.'));
+            return;
+          }
+          requestBody = String(zlib.inflateSync(new Buffer(requestBody, 'hex')), 'hex')
+        }
+
         requestBody = JSON.parse(requestBody);
       }
 
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 900abed..9cd3c44 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
     "testing",
     "isolation"
   ],
-  "version": "9.0.2",
+  "version": "9.0.18",
   "author": "Pedro Teixeira <pedro.teixeira@gmail.com>",
   "contributors": [
     {
@@ -149,6 +149,10 @@
     {
       "name": "Matt Oakes",
       "url": "https://github.com/matto1990"
+    },
+    {
+      "name": "Ian Walker-Sperber",
+      "url": "https://github.com/ianwsperber"
     }
   ],
   "repository": {
@@ -167,10 +171,11 @@
     "debug": "^2.2.0",
     "deep-equal": "^1.0.0",
     "json-stringify-safe": "^5.0.1",
-    "lodash": "~4.9.0",
+    "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",
@@ -178,6 +183,7 @@
     "browserify": "^13.0.0",
     "changelog": "^1.0.7",
     "coveralls": "^2.11.2",
+    "glob": "^7.1.1",
     "hyperquest": "^1.3.0",
     "isomorphic-fetch": "^2.2.0",
     "istanbul": "^0.4.2",
@@ -185,20 +191,24 @@
     "markdown-toc": "^0.13.0",
     "needle": "^1.0.0",
     "node-static": "^0.7.7",
-    "nyc": "^8.3.0",
+    "nyc": "^10.0.0",
     "pre-commit": "1.1.2",
     "request": "2.71.0",
     "request-promise": "^2.0.1",
     "restify": "^4.0.4",
     "restler": "3.4.0",
     "rimraf": "^2.3.2",
-    "superagent": "^1.4.0",
-    "tap": "^5.7.0",
-    "zombie": "^4.2.1"
+    "semver": "^5.3.0",
+    "superagent": "^3.5.0",
+    "tap": "^10.0.0",
+    "zombie": "^5.0.1"
   },
   "scripts": {
-    "test": "tap --harmony ./tests/test_*.js",
-    "coverage": "nyc --reporter=lcov tap --harmony ./tests/test_*.js",
+    "unit": "tap --harmony ./tests/test_*.js",
+    "preintegration": "./tests/bin/install_sub_deps versioned",
+    "integration": "tap --harmony ./tests/versioned/*/*.tap.js",
+    "test": "npm run unit && npm run integration",
+    "coverage": "nyc tap --harmony ./tests/test_*.js",
     "coveralls": "cat ./coverage/lcov.info | coveralls",
     "jshint": "jshint lib/*.js",
     "changelog": "changelog nock all -m > CHANGELOG.md",
@@ -208,5 +218,14 @@
     "jshint",
     "coverage"
   ],
+  "nyc": {
+    "reporter": [
+      "lcov",
+      "text-summary"
+    ],
+    "exclude": [
+      "tests/test_*.js"
+    ]
+  },
   "license": "MIT"
 }


Test patch:
diff --git a/tests/bin/install_sub_deps b/tests/bin/install_sub_deps
new file mode 100755
index 0000000..0dbe51b
--- /dev/null
+++ b/tests/bin/install_sub_deps
@@ -0,0 +1,61 @@
+#!/usr/bin/env node
+/* Modeled after: https://github.com/newrelic/node-newrelic/blob/master/test/bin/install_sub_deps */
+'use strict'
+
+const path = require('path')
+const glob = require('glob')
+const assert = require('assert')
+const exec = require('child_process').exec
+
+const folder = process.argv[2];
+const retries = 2
+
+assert(folder, 'Missing folder!');
+
+glob(`**/tests/${folder}/**/package.json`, {}, (err, packages) => {
+  if (err) { fail(err); }
+
+  install(packages.filter((line) => !/node_modules|example/.test(line)));
+});
+
+const install = (packages, retry)  => {
+  if (!retry) retry = 0
+
+  const failed = []
+  let counter = 0
+
+  packages.forEach((packageJsonPath) => {
+    const packagePath = path.resolve(packageJsonPath).split('/').slice(0, -1).join('/');
+
+    console.log('installing deps in %s', packagePath)
+    exec(`cd ${packagePath} && rm -rf node_modules && npm i`, {
+      maxBuffer: 1024 * 500
+    }, (err, stdout, stderr) => {
+      if (err || isErrorOutput(stderr)) {
+        printError(packageJsonPath, err || stderr)
+        if (retry < retries) {
+          failed.push(packageJsonPath)
+        }
+      }
+
+      if (++counter === packages.length) {
+        if (failed.length > 0 && retry < retries) {
+          console.log('%s package(s) failed to install, retrying...', failed.length)
+          exec('npm cache clean', () => install(failed, ++retry));
+        }
+      }
+    })
+  })
+}
+
+// determines if stderr contains errors
+function isErrorOutput(text) {
+  return (text && text.indexOf('npm ERR') > -1)
+}
+
+function printError(packagePath, error) {
+  console.log('-----------------------------------------------------------')
+  console.log(packagePath + ' failed installing with the following error:')
+  console.log(error)
+  console.log('-----------------------------------------------------------')
+}
\ No newline at end of file
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 25e5188..3476125 100644
--- a/tests/test_body_match.js
+++ b/tests/test_body_match.js
@@ -66,3 +66,40 @@ test('match body with empty object inside', function (t) {
     t.end();
   });
 })
+
+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')
+    .post('/', "--fixboundary\r\nContent-Disposition: form-data; name=\"field\"\r\n\r\nvalue\r\n--fixboundary--\r\n")
+    .reply(200);
+
+  var r = mikealRequest({
+    url: 'http://encodingsareus.com/',
+    method: 'post',
+  }, function(err, res) {
+    if (err) throw err;
+    assert.equal(res.statusCode, 200);
+    t.end();
+  });
+  var form = r.form();
+  form._boundary = 'fixboundary';  // fix boundary so that request could match at all
+  form.append('field', 'value');
+});
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_common.js b/tests/test_common.js
index 886a231..a16295d 100644
--- a/tests/test_common.js
+++ b/tests/test_common.js
@@ -30,9 +30,7 @@ tap.test('matchBody should not ignore new line characters from strings when Cont
       'Content-Type': "multipart/form-data;"
     }
   }
-  var matched = matchBody.call(testThis, function (body) {
-    return body === str1;
-  }, str2);
+  var matched = matchBody.call(testThis, str1, str2);
   t.true(matched);
   t.end()
 });
@@ -45,9 +43,7 @@ tap.test('matchBody should not ignore new line characters from strings when Cont
       'Content-Type': ["multipart/form-data;"]
     }
   }
-  var matched = matchBody.call(testThis, function (body) {
-    return body === str1;
-  }, str2);
+  var matched = matchBody.call(testThis, str1, str2);
   t.true(matched);
   t.end()
 });
@@ -158,3 +154,85 @@ 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();
+});
+
+
+tap.test('headersArrayToObject', function (t) {
+  var headers = [
+    "Content-Type",
+    "application/json; charset=utf-8",
+    "Last-Modified",
+    "foobar",
+    "Expires",
+    "fizbuzz"
+  ];
+
+  t.deepEqual(common.headersArrayToObject(headers), {
+    "Content-Type": "application/json; charset=utf-8",
+    "Last-Modified": "foobar",
+    "Expires": "fizbuzz"
+  });
+
+  var headersMultipleSetCookies = headers.concat([
+    "Set-Cookie",
+    "foo=bar; Domain=.github.com; Path=/",
+    "Set-Cookie",
+    "fiz=baz; Domain=.github.com; Path=/"
+  ]);
+
+  t.deepEqual(common.headersArrayToObject(headersMultipleSetCookies), {
+    "Content-Type": "application/json; charset=utf-8",
+    "Last-Modified": "foobar",
+    "Expires": "fizbuzz",
+    "Set-Cookie": [
+      "foo=bar; Domain=.github.com; Path=/",
+      "fiz=baz; Domain=.github.com; Path=/"
+    ]
+  });
+
+  t.end();
+});
diff --git a/tests/test_gzip_request.js b/tests/test_gzip_request.js
new file mode 100644
index 0000000..e179b43
--- /dev/null
+++ b/tests/test_gzip_request.js
@@ -0,0 +1,74 @@
+'use strict';
+
+var nock = require('../');
+var test = require('tap').test;
+var http = require('http');
+var assert = require('assert');
+var zlib = require('zlib');
+
+if (zlib.gzipSync && zlib.gunzipSync) {
+  test('accepts and decodes gzip encoded application/json', function (t) {
+    var message = {
+      my: 'contents'
+    };
+
+    t.plan(1);
+
+    nock('http://gzipped.com')
+      .post('/')
+      .reply(function (url, actual) {
+        t.same(actual, message);
+        t.end();
+        return 200
+      });
+
+    var req = http.request({
+        hostname: 'gzipped.com',
+        path: '/',
+        method: 'POST',
+        headers: {
+          'content-encoding': 'gzip',
+          'content-type': 'application/json'
+        }
+      });
+
+    var compressedMessage = zlib.gzipSync(JSON.stringify(message));
+
+    req.write(compressedMessage);
+    req.end();
+  });
+}
+
+if (zlib.deflateSync && zlib.inflateSync) {
+
+  test('accepts and decodes deflate encoded application/json', function (t) {
+    var message = {
+      my: 'contents'
+    };
+
+    t.plan(1);
+
+    nock('http://gzipped.com')
+      .post('/')
+      .reply(function (url, actual) {
+        t.same(actual, message);
+        t.end();
+        return 200
+      });
+
+    var req = http.request({
+        hostname: 'gzipped.com',
+        path: '/',
+        method: 'POST',
+        headers: {
+          'content-encoding': 'deflate',
+          'content-type': 'application/json'
+        }
+      });
+
+    var compressedMessage = zlib.deflateSync(JSON.stringify(message));
+
+    req.write(compressedMessage);
+    req.end();
+  });
+}
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
old mode 100644
new mode 100755
index 4e84d9b..716ed51
--- 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();
@@ -1868,7 +1865,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 +1890,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();
   });
 });
@@ -2249,6 +2246,23 @@ test('clean all works', function(t) {
 
 });
 
+test('cleanAll should remove pending mocks from all scopes', function(t) {
+  var scope1 = nock('http://example.org')
+    .get('/somepath')
+    .reply(200, 'hey');
+  t.deepEqual(scope1.pendingMocks(), ['GET http://example.org:80/somepath']);
+  var scope2 = nock('http://example.com')
+    .get('/somepath')
+    .reply(200, 'hey');
+  t.deepEqual(scope2.pendingMocks(), ['GET http://example.com:80/somepath']);
+
+  nock.cleanAll();
+
+  t.deepEqual(scope1.pendingMocks(), []);
+  t.deepEqual(scope2.pendingMocks(), []);
+  t.end();
+});
+
 test('is done works', function(t) {
   var scope = nock('http://amazon.com')
     .get('/nonexistent')
@@ -3455,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();
@@ -3500,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();
@@ -3546,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();
@@ -3623,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(/.*/, '*')
@@ -4077,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')
@@ -4212,6 +4287,7 @@ test('request emits socket', function(t) {
 
   var req = http.get('http://gotzsocketz.com');
   req.once('socket', function(socket) {
+    t.equal(this, req);
     t.type(socket, Object);
     t.type(socket.getPeerCertificate(), 'string');
     t.end();
@@ -4574,7 +4650,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();
     });
@@ -4707,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();
@@ -4771,6 +4845,24 @@ test('query() matches query values regardless of their type of declaration', fun
   })
 });
 
+test('query() doesn\'t match query values of requests without query string', function (t) {
+  var scope1 = nock('http://google.com')
+    .get('/')
+    .query({num:1,bool:true,empty:null,str:'fou'})
+    .reply(200, 'scope1');
+
+  var scope2 = nock('http://google.com')
+    .get('/')
+    .reply(200, 'scope2');
+
+  mikealRequest('http://google.com/', function(err, res) {
+    if (err) throw err;
+    t.equal(res.statusCode, 200);
+    t.equal(res.body, 'scope2');
+    t.end();
+  })
+});
+
 test('query() matches a query string using regexp', function (t) {
   var scope = nock('http://google.com')
     .get('/')
@@ -4905,7 +4997,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 +5009,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 +5021,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 +5033,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 +5050,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();
   })
 });
@@ -4977,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_isomorphic_fetch.js b/tests/test_isomorphic_fetch.js
index b9734aa..6879aa8 100644
--- a/tests/test_isomorphic_fetch.js
+++ b/tests/test_isomorphic_fetch.js
@@ -32,7 +32,7 @@ test("string-based reqheaders match works", function(t) {
     get('/path2').
     reply(200, 'somemoardata');
 
-  fetch('http://isomorphicfetchland.com/path2', {
+  return fetch('http://isomorphicfetchland.com/path2', {
     headers: {
       'header': 'header value',
     }
@@ -49,3 +49,56 @@ test("string-based reqheaders match works", function(t) {
       throw err;
     });
 });
+
+test("basicAuth match works", function (t) {
+  var scope = nock('http://isomorphicfetchland.com').
+    get('/path2').
+    basicAuth({
+      user: 'username',
+      pass: 'password'
+    }).
+    reply(200, 'somemoardata');
+
+  return fetch('http://isomorphicfetchland.com/path2', {
+    headers: {
+      'Authorization': 'Basic ' + new Buffer('username:password').toString('base64'),
+    }
+  }).
+    then(function (res) {
+      return res.text();
+    }).
+    then(function (text) {
+      scope.done();
+      t.equal(text, 'somemoardata', "response should match");
+      t.end();
+    }).
+    catch(function (err) {
+      throw err;
+    });
+});
+
+test("matchHeader works", function (t) {
+  var authorizationHeader = 'Basic ' + new Buffer('username:password').toString('base64');
+
+  var scope = nock('http://isomorphicfetchland.com').
+    get('/path2').
+    matchHeader('authorization', authorizationHeader).
+    reply(200, 'somemoardata');
+
+  return fetch('http://isomorphicfetchland.com/path2', {
+    headers: {
+      'Authorization': authorizationHeader,
+    }
+  }).
+    then(function (res) {
+      return res.text();
+    }).
+    then(function (text) {
+      scope.done();
+      t.equal(text, 'somemoardata', "response should match");
+      t.end();
+    }).
+    catch(function (err) {
+      throw err;
+    });
+});
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);
diff --git a/tests/test_timeout.js b/tests/test_timeout.js
new file mode 100644
index 0000000..07c5dbd
--- /dev/null
+++ b/tests/test_timeout.js
@@ -0,0 +1,21 @@
+'use strict';
+
+var nock    = require('../.');
+var test    = require('tap').test;
+var request = require('request');
+
+test('something', function (t) {
+    nock('http://google.com')
+        .get('/')
+        .delay(1000)
+        .reply(200, {});
+
+    request({
+        url: 'http://google.com',
+        timeout: 10
+    }, function (err, response) {
+        t.ok(err);
+        t.equal(err.code, 'ETIMEDOUT')
+        t.end();
+    });
+});
diff --git a/tests/versioned/common.js b/tests/versioned/common.js
new file mode 100644
index 0000000..1fd303d
--- /dev/null
+++ b/tests/versioned/common.js
@@ -0,0 +1,49 @@
+'use strict';
+
+const tap = require('tap');
+const nock = require('../../');
+
+/**
+ * Run common test suite against an HTTP client library.
+ *
+ * The makeRequest fn should be a wrapper around an
+ * HTTP client, which maps our arguments to those accepted by the client.
+ *
+ * The client is expected to accept an options object with props:
+ * - uri
+ * - method
+ * - timeout
+ * And return a promise, which in success has properties
+ * - statusCode (number)
+ * - body (string)
+ * And in failure is a standard error object.
+ *
+ * @param {Function} makeRequest Request fn
+ * @param {string} name Lib we're testing
+ */
+const runCommonTests = (makeRequest, name) => {
+  const test = (description, cb) => {
+    return tap.test(`${name}: ${description}`, cb);
+  };
+
+  test('basic intercept', (t) => {
+    nock('http://www.example.com')
+      .get('/')
+      .reply(200, 'OK');
+
+    return makeRequest({
+        uri: 'http://www.example.com'
+      })
+      .then((res) => {
+        t.equal(res.statusCode, 200);
+        t.deepEqual(res.body, 'OK');
+
+        t.end();
+      })
+      .catch(t.threw);
+  });
+};
+
+module.exports = {
+  runCommonTests
+};
diff --git a/tests/versioned/got-latest/package.json b/tests/versioned/got-latest/package.json
new file mode 100644
index 0000000..89c6f88
--- /dev/null
+++ b/tests/versioned/got-latest/package.json
@@ -0,0 +1,14 @@
+
+{
+  "name": "got-latest",
+  "version": "0.0.0",
+  "description": "Tests for got latest (>6)",
+  "main": "test.tap.js",
+  "scripts": {
+    "test": "tap test.tap.js"
+  },
+  "dependencies": {
+    "got": "^6"
+  },
+  "private": true
+}
\ No newline at end of file
diff --git a/tests/versioned/got-latest/test.tap.js b/tests/versioned/got-latest/test.tap.js
new file mode 100644
index 0000000..98c4bb6
--- /dev/null
+++ b/tests/versioned/got-latest/test.tap.js
@@ -0,0 +1,14 @@
+'use strict';
+
+const tap = require('tap');
+const got = require('got');
+const common = require('../common');
+
+const makeRequest = (options) => {
+  return got(options.uri, {
+    method: options.method,
+    timeout: options.timeout
+  });
+};
+
+common.runCommonTests(makeRequest, 'got@latest');
diff --git a/tests/versioned/popsicle-latest/package.json b/tests/versioned/popsicle-latest/package.json
new file mode 100644
index 0000000..e6c96b6
--- /dev/null
+++ b/tests/versioned/popsicle-latest/package.json
@@ -0,0 +1,14 @@
+
+{
+  "name": "posicle-latest",
+  "version": "0.0.0",
+  "description": "Tests for popsicle latest (>9)",
+  "main": "test.tap.js",
+  "scripts": {
+    "test": "tap test.tap.js"
+  },
+  "dependencies": {
+    "popsicle": "^9"
+  },
+  "private": true
+}
\ No newline at end of file
diff --git a/tests/versioned/popsicle-latest/test.tap.js b/tests/versioned/popsicle-latest/test.tap.js
new file mode 100644
index 0000000..068e97f
--- /dev/null
+++ b/tests/versioned/popsicle-latest/test.tap.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const tap = require('tap');
+const popsicle = require('popsicle');
+const common = require('../common');
+
+const makeRequest = (options) => {
+  return popsicle.request({
+    url: options.uri,
+    method: options.method || 'GET'
+  })
+  .then((res) => {
+    res.statusCode = res.status;
+
+    return res;
+  })
+};
+
+common.runCommonTests(makeRequest, 'popsicle@latest');
diff --git a/tests/versioned/request-latest/package.json b/tests/versioned/request-latest/package.json
new file mode 100644
index 0000000..c45fa42
--- /dev/null
+++ b/tests/versioned/request-latest/package.json
@@ -0,0 +1,14 @@
+
+{
+  "name": "request-latest",
+  "version": "0.0.0",
+  "description": "Tests for request latest (>2)",
+  "main": "test.tap.js",
+  "scripts": {
+    "test": "tap test.tap.js"
+  },
+  "dependencies": {
+    "request": "^2"
+  },
+  "private": true
+}
\ No newline at end of file
diff --git a/tests/versioned/request-latest/test.tap.js b/tests/versioned/request-latest/test.tap.js
new file mode 100644
index 0000000..b82088b
--- /dev/null
+++ b/tests/versioned/request-latest/test.tap.js
@@ -0,0 +1,21 @@
+'use strict';
+
+const tap = require('tap');
+const request = require('request');
+const common = require('../common');
+
+const makeRequest = (options) => {
+  return new Promise((resolve, reject) => {
+    request({
+      uri: options.uri,
+      method: options.method || 'GET'
+    }, (err, res, body) => {
+      if (err) { return reject(err); }
+
+      res.body = body;
+      resolve(res);
+    });
+  });
+};
+
+common.runCommonTests(makeRequest, 'request@latest');