Repository: node-nock/nock
Pull request #792 (2017-01-06 19:19:03)

fix readme typo
add missing closing paren


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..0548360 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,200 +1,190 @@
-9.0.2 / 2016-10-28
+9.0.16 / 2017-09-18
+===================
+
+  * 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
 ==================
 
-  * Update CHANGELOG
-  * Implement AIRPLANE mode to skip internet-dependent tests
+  * 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
 
-9.0.1 / 2016-10-28
+9.0.8 / 2017-02-27
 ==================
 
-  * 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.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
 
-9.0.0 / 2016-10-23
+9.0.7 / 2017-02-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.1 / 2016-10-28
-==================
-
-  * 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
-
-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
+  * 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
-==================
-
-  * 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
+  * 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
 
-7.7.2 / 2016-03-25
+9.0.5 / 2017-02-10
 ==================
 
-  * v7.7.2
-  * v7.7.1
-  * Merge branch 'master' of github.com:node-nock/nock
-
-7.7.0 / 2016-03-25
+  * 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.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
-==================
+  * 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
 
-  * 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.3 / 2017-02-06
 ==================
 
-  * 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.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"
+  * Updated debug namespace to match file.
diff --git a/README.md b/README.md
index 1efba33..08e640a 100644
--- a/README.md
+++ b/README.md
@@ -132,7 +132,7 @@ 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.
 
@@ -637,7 +637,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 +656,7 @@ nock('http://my.server.com')
  delay({
     head: headDelayInMs,
     body: bodyDelayInMs
- }
+ })
  ```
 
  for example
@@ -728,9 +728,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 +1077,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).
 
diff --git a/lib/common.js b/lib/common.js
index 5e78fe3..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) {
@@ -234,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;
@@ -282,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]
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..caab1a8
--- a/lib/interceptor.js
+++ b/lib/interceptor.js
@@ -4,7 +4,7 @@ var mixin           = require('./mixin')
     , matchBody       = require('./match_body')
     , common          = require('./common')
     , _               = require('lodash')
-    , debug           = require('debug')('nock.scope')
+    , debug           = require('debug')('nock.interceptor')
     , stringify       = require('json-stringify-safe')
     , util            = require('util')
     , qs              = require('qs');
@@ -163,7 +163,7 @@ Interceptor.prototype.reqheaderMatches = function reqheaderMatches(options, key)
         return true;
     }
 
-    if (reqHeader && header) {
+    if (!_.isUndefined(reqHeader) && !_.isUndefined(header)) {
         if (_.isFunction(reqHeader)) {
             return reqHeader(header);
         } else if (common.matchStringOrRegexp(header, reqHeader)) {
@@ -256,8 +256,9 @@ 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
@@ -299,7 +300,9 @@ Interceptor.prototype.match = function match(options, body, hostNameOnly) {
         }
 
         // 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') {
@@ -315,10 +318,11 @@ Interceptor.prototype.match = function match(options, body, hostNameOnly) {
 
     // special logger for query()
     if (queryIndex !== -1) {
-        this.scope.logger('matching ' + matchKey + '?' + queryString + ' to ' + this._key +
-        ' with query(' + stringify(this.queries) + '): ' + matches);
+        this.scope.logger('matching ' + method + ' ' + matchKey + path + '?' + queryString 
+        + ' to ' + this._key + ' with query(' + stringify(this.queries) + '): ' + matches);
     } else {
-        this.scope.logger('matching ' + matchKey + ' to ' + this._key + ': ' + matches);
+        this.scope.logger('matching ' + method + ' ' + matchKey + path 
+        + ' to ' + this._key + ': ' + matches);
     }
 
     if (matches) {
diff --git a/lib/match_body.js b/lib/match_body.js
index be5215a..83784f0 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, '');
   }
 
diff --git a/lib/recorder.js b/lib/recorder.js
index 0dbecdf..1afd20f 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,7 +282,7 @@ function record(rec_options) {
 
         if (!dont_print) {
           if (use_separator) {
-            logging(SEPARATOR + out + SEPARATOR);
+            logging(SEPARATOR + JSON.stringify(out, null, 2) + SEPARATOR);
           } else {
             logging(out);
           }
@@ -336,10 +337,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 85ceea9..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);
     });
   }
 
@@ -193,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);
     }
@@ -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 0437f39..c7304f1 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
     "testing",
     "isolation"
   ],
-  "version": "9.0.2",
+  "version": "9.0.16",
   "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": {
@@ -170,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",
@@ -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",
@@ -192,13 +198,17 @@
     "restify": "^4.0.4",
     "restler": "3.4.0",
     "rimraf": "^2.3.2",
-    "superagent": "^1.4.0",
-    "tap": "^8.0.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..62e1473 100644
--- a/tests/test_back.js
+++ b/tests/test_back.js
@@ -145,7 +145,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..fb645cb 100644
--- a/tests/test_body_match.js
+++ b/tests/test_body_match.js
@@ -66,3 +66,22 @@ test('match body with empty object inside', function (t) {
     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_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..9866eac
--- 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();
   })
 });
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/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');