Giter Site home page Giter Site logo

nock's Introduction

Nock

npm Build Status Coverage Status Backers on Open Collective Sponsors on Open Collective

Notice

We have introduced experimental support for fetch. Please share your feedback with us. You can install it by:

npm install --save-dev nock@beta

HTTP server mocking and expectations library for Node.js

Nock can be used to test modules that perform HTTP requests in isolation.

For instance, if a module performs HTTP requests to a CouchDB server or makes HTTP requests to the Amazon API, you can test that module in isolation.

Table of Contents

How does it work?

Nock works by overriding Node's http.request function. Also, it overrides http.ClientRequest too to cover for modules that use it directly.

Install

$ npm install --save-dev nock

Node version support

The latest version of nock supports all currently maintained Node versions, see Node Release Schedule

Here is a list of past nock versions with respective node version support

node nock
0.10 up to 8.x
0.11 up to 8.x
0.12 up to 8.x
4 up to 9.x
5 up to 8.x
6 up to 10.x
7 up to 9.x
8 up to 11.x
9 up to 9.x

Usage

On your test, you can setup your mocking object like this:

const nock = require('nock')

const scope = nock('https://api.github.com')
  .get('/repos/atom/atom/license')
  .reply(200, {
    license: {
      key: 'mit',
      name: 'MIT License',
      spdx_id: 'MIT',
      url: 'https://api.github.com/licenses/mit',
      node_id: 'MDc6TGljZW5zZTEz',
    },
  })

This setup says that we will intercept every HTTP call to https://api.github.com.

It will intercept an HTTPS GET request to /repos/atom/atom/license, reply with a status 200, and the body will contain a (partial) response in JSON.

READ THIS! - About interceptors

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() method.

Specifying hostname

The request hostname can be a string, URL, or a RegExp.

const scope = nock('http://www.example.com')
  .get('/resource')
  .reply(200, 'domain matched')
const scope = nock(new URL('http://www.example.com'))
  .get('/resource')
  .reply(200, 'domain matched')
const scope = nock(/example\.com/)
  .get('/resource')
  .reply(200, 'domain regex matched')

Note: You can choose to include or not the protocol in the hostname matching.

Specifying path

The request path can be a string, a RegExp or a filter function and you can use any HTTP verb.

Using a string:

const scope = nock('http://www.example.com')
  .get('/resource')
  .reply(200, 'path matched')

Using a regular expression:

const scope = nock('http://www.example.com')
  .get(/source$/)
  .reply(200, 'path using regex matched')

Using a function:

const scope = nock('http://www.example.com')
  .get(uri => uri.includes('cats'))
  .reply(200, 'path using function matched')

Specifying request body

You can specify the request body to be matched as the second argument to the get, post, put or delete specifications. There are five types of second argument allowed:

String: nock will exact match the stringified request body with the provided string

nock('http://www.example.com')
  .post('/login', 'username=pgte&password=123456')
  .reply(200, { id: '123ABC' })

Buffer: nock will exact match the stringified request body with the provided buffer

nock('http://www.example.com')
  .post('/login', Buffer.from([0xff, 0x11]))
  .reply(200, { id: '123ABC' })

RegExp: nock will test the stringified request body against the provided RegExp

nock('http://www.example.com')
  .post('/login', /username=\w+/gi)
  .reply(200, { id: '123ABC' })

JSON object: nock will exact match the request body with the provided object. In order to increase flexibility, nock also supports RegExp as an attribute value for the keys:

nock('http://www.example.com')
  .post('/login', { username: 'pgte', password: /.+/i })
  .reply(200, { id: '123ABC' })

Function: nock will evaluate the function providing the request body object as first argument. Return true if it should be considered a match:

nock('http://www.example.com')
  .post('/login', body => body.username && body.password)
  .reply(200, { id: '123ABC' })

In case you need to perform a partial matching on a complex, nested request body you should have a look at libraries like lodash.matches. Indeed, partial matching can be achieved as:

nock('http://www.example.com')
  .post('/user', _.matches({ address: { country: 'US' } }))
  .reply(200, { id: '123ABC' })

Specifying request query string

Nock understands query strings. Search parameters can be included as part of the path:

nock('http://example.com').get('/users?foo=bar').reply(200)

Instead of placing the entire URL, you can specify the query part as an object:

nock('http://example.com')
  .get('/users')
  .query({ name: 'pedro', surname: 'teixeira' })
  .reply(200, { results: [{ id: 'pgte' }] })

Nock supports array-style/object-style query parameters. The encoding format matches with request module.

nock('http://example.com')
  .get('/users')
  .query({
    names: ['alice', 'bob'],
    tags: {
      alice: ['admin', 'tester'],
      bob: ['tester'],
    },
  })
  .reply(200, { results: [{ id: 'pgte' }] })

A URLSearchParams instance can be provided.

const params = new URLSearchParams({ foo: 'bar' })

nock('http://example.com').get('/').query(params).reply(200)

Nock supports passing a function to query. The function determines if the actual query matches or not.

nock('http://example.com')
  .get('/users')
  .query(actualQueryObject => {
    // do some compare with the actual Query Object
    // return true for matched
    // return false for not matched
    return true
  })
  .reply(200, { results: [{ id: 'pgte' }] })

To mock the entire url regardless of the passed query string:

nock('http://example.com')
  .get('/users')
  .query(true)
  .reply(200, { results: [{ id: 'pgte' }] })

A query string that is already URL encoded can be matched by passing the encodedQueryParams flag in the options when creating the Scope.

nock('http://example.com', { encodedQueryParams: true })
  .get('/users')
  .query('foo%5Bbar%5D%3Dhello%20world%21')
  .reply(200, { results: [{ id: 'pgte' }] })

Specifying replies

You can specify the return status code for a path on the first argument of reply like this:

const scope = nock('http://myapp.iriscouch.com').get('/users/1').reply(404)

You can also specify the reply body as a string:

const scope = nock('http://www.google.com')
  .get('/')
  .reply(200, 'Hello from Google!')

or as a JSON-encoded object:

const scope = nock('http://myapp.iriscouch.com').get('/').reply(200, {
  username: 'pgte',
  email: '[email protected]',
  _id: '4324243fsd',
})

or even as a file:

const scope = nock('http://myapp.iriscouch.com')
  .get('/')
  .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:

const scope = nock('http://www.google.com')
  .post('/echo')
  .reply(201, (uri, requestBody) => requestBody)

In Nock 11.x it was possible to invoke .reply() with a status code and a function that returns an array containing a status code and body. (The status code from the array would take precedence over the one passed directly to reply.) This is no longer allowed. In Nock 12 and later, either call .reply() with a status code and a function that returns the body, or call it with a single argument: a function that returns an array containing both the status code and body.

An asynchronous function that gets an error-first callback as its last argument also works:

const scope = nock('http://www.google.com')
  .post('/echo')
  .reply(201, (uri, requestBody, cb) => {
    fs.readFile('cat-poems.txt', cb) // Error-first callback
  })

In Nock 11 and later, if an error is passed to the callback, Nock will rethrow it as a programmer error. In Nock 10 and earlier, the error was sent in the response body, with a 500 HTTP response status code.

You can also return the status code and body using just one function:

const scope = nock('http://www.google.com')
  .post('/echo')
  .reply((uri, requestBody) => {
    return [
      201,
      'THIS IS THE REPLY BODY',
      { header: 'value' }, // optional headers
    ]
  })

or, use an error-first callback that also gets the status code:

const scope = nock('http://www.google.com')
  .post('/echo')
  .reply((uri, requestBody, cb) => {
    setTimeout(() => cb(null, [201, 'THIS IS THE REPLY BODY']), 1000)
  })

A Stream works too:

const scope = nock('http://www.google.com')
  .get('/cat-poems')
  .reply(200, (uri, requestBody) => {
    return fs.createReadStream('cat-poems.txt')
  })

Access original request and headers

If you're using the reply callback style, you can access the original client request using this.req like this:

const scope = nock('http://www.google.com')
  .get('/cat-poems')
  .reply(function (uri, requestBody) {
    console.log('path:', this.req.path)
    console.log('headers:', this.req.headers)
    // ...
  })

Note: Remember to use normal function in that case, as arrow functions are using enclosing scope for this binding.

Replying with errors

You can reply with an error like this:

nock('http://www.google.com')
  .get('/cat-poems')
  .replyWithError('something awful happened')

JSON error responses are allowed too:

nock('http://www.google.com').get('/cat-poems').replyWithError({
  message: 'something awful happened',
  code: 'AWFUL_ERROR',
})

Note: This will emit an error event on the request object, not the reply.

Specifying headers

Header field names are case-insensitive

Per HTTP/1.1 4.2 Message Headers specification, all message headers are case insensitive and thus internally Nock uses lower-case for all field names even if some other combination of cases was specified either in mocking specification or in mocked requests themselves.

Specifying Request Headers

You can specify the request headers like this:

const scope = nock('http://www.example.com', {
  reqheaders: {
    authorization: 'Basic Auth',
  },
})
  .get('/')
  .reply(200)

Or you can use a regular expression or function to check the header values. The function will be passed the header value.

const scope = nock('http://www.example.com', {
  reqheaders: {
    'X-My-Headers': headerValue => headerValue.includes('cats'),
    'X-My-Awesome-Header': /Awesome/i,
  },
})
  .get('/')
  .reply(200)

If reqheaders is not specified or if host is not part of it, Nock will automatically add host value to request header.

If no request headers are specified for mocking then Nock will automatically skip matching of request headers. Since the host header is a special case which may get automatically inserted by Nock, its matching is skipped unless it was also specified in the request being mocked.

You can also have Nock fail the request if certain headers are present:

const scope = nock('http://www.example.com', {
  badheaders: ['cookie', 'x-forwarded-for'],
})
  .get('/')
  .reply(200)

When invoked with this option, Nock will not match the request if any of the badheaders are present.

Basic authentication can be specified as follows:

const scope = nock('http://www.example.com')
  .get('/')
  .basicAuth({ user: 'john', pass: 'doe' })
  .reply(200)

Specifying Reply Headers

You can specify the reply headers like this:

const scope = nock('https://api.github.com')
  .get('/repos/atom/atom/license')
  .reply(200, { license: 'MIT' }, { 'X-RateLimit-Remaining': 4999 })

Or you can use a function to generate the headers values. The function will be passed the request, response, and response body (if available). The body will be either a buffer, a stream, or undefined.

const scope = nock('http://www.headdy.com')
  .get('/')
  .reply(200, 'Hello World!', {
    'Content-Length': (req, res, body) => body.length,
    ETag: () => `${Date.now()}`,
  })

Default Reply Headers

You can also specify default reply headers for all responses like this:

const scope = nock('http://www.headdy.com')
  .defaultReplyHeaders({
    'X-Powered-By': 'Rails',
    'Content-Type': 'application/json',
  })
  .get('/')
  .reply(200, 'The default headers should come too')

Or you can use a function to generate the default headers values:

const scope = nock('http://www.headdy.com')
  .defaultReplyHeaders({
    'Content-Length': (req, res, body) => body.length,
  })
  .get('/')
  .reply(200, 'The default headers should come too')

Including Content-Length Header Automatically

When using interceptor.reply() to set a response body manually, you can have the Content-Length header calculated automatically.

const scope = nock('http://www.headdy.com')
  .replyContentLength()
  .get('/')
  .reply(200, { hello: 'world' })

NOTE: this does not work with streams or other advanced means of specifying the reply body.

Including Date Header Automatically

You can automatically append a Date header to your mock reply:

const scope = nock('http://www.headdy.com')
  .replyDate()
  .get('/')
  .reply(200, { hello: 'world' })

Or provide your own Date object:

const scope = nock('http://www.headdy.com')
  .replyDate(new Date(2015, 0, 1))
  .get('/')
  .reply(200, { hello: 'world' })

HTTP Verbs

Nock supports any HTTP verb, and it has convenience methods for the GET, POST, PUT, HEAD, DELETE, PATCH, OPTIONS and MERGE HTTP verbs.

You can intercept any HTTP verb using .intercept(path, verb [, requestBody [, options]]):

const scope = nock('http://my.domain.com')
  .intercept('/path', 'PATCH')
  .reply(304)

Support for HTTP and HTTPS

By default nock assumes HTTP. If you need to use HTTPS you can specify the https:// prefix like this:

const scope = nock('https://secure.my.server.com')
// ...

Non-standard ports

You are able to specify a non-standard port like this:

const scope = nock('http://my.server.com:8081')

Repeat response n times

You are able to specify the number of times to repeat the same response.

NOTE: When request times is more than the number you specified, you will get an error before cleaning this interceptor.

nock('http://zombo.com').get('/').times(4).reply(200, 'Ok')

http.get('http://zombo.com/') // respond body "Ok"
http.get('http://zombo.com/') // respond body "Ok"
http.get('http://zombo.com/') // respond body "Ok"
http.get('http://zombo.com/') // respond body "Ok"

// This code will get an error with message:
// Nock: No match for request
http.get('http://zombo.com/')

// clean your interceptor
nock.cleanAll()

http.get('http://zombo.com/') // real respond with zombo.com result

Sugar syntax

nock('http://zombo.com').get('/').once().reply(200, 'Ok')
nock('http://zombo.com').get('/').twice().reply(200, 'Ok')
nock('http://zombo.com').get('/').thrice().reply(200, 'Ok')

To repeat this response for as long as nock is active, use .persist().

Delay the response

Nock can simulate response latency to allow you to test timeouts, race conditions, an other timing related scenarios.
You are able to specify the number of milliseconds that your reply should be delayed.

nock('http://my.server.com')
  .get('/')
  .delay(2000) // 2 seconds delay will be applied to the response header.
  .reply(200, '<html></html>')

delay(1000) is an alias for delayConnection(1000).delayBody(0)
delay({ head: 1000, body: 2000 }) is an alias for delayConnection(1000).delayBody(2000)
Both of which are covered in detail below.

Delay the connection

You are able to specify the number of milliseconds that your connection should be idle before it starts to receive the response.

To simulate a socket timeout, provide a larger value than the timeout setting on the request.

nock('http://my.server.com')
  .get('/')
  .delayConnection(2000) // 2 seconds
  .reply(200, '<html></html>')

req = http.request('http://my.server.com', { timeout: 1000 })

Nock emits timeout events almost immediately by comparing the requested connection delay to the timeout parameter passed to http.request() or http.ClientRequest#setTimeout().
This allows you to test timeouts without using fake timers or slowing down your tests. If the client chooses to not take an action (e.g. abort the request), the request and response will continue on as normal, after real clock time has passed.

Technical Details

Following the 'finish' event being emitted by ClientRequest, Nock will wait for the next event loop iteration before checking if the request has been aborted. At this point, any connection delay value is compared against any request timeout setting and a 'timeout' is emitted when appropriate from the socket and the request objects. A Node timeout timer is then registered with any connection delay value to delay real time before checking again if the request has been aborted and the 'response' is emitted by the request.

A similar method, .socketDelay() was removed in version 13. It was thought that having two methods so subtlety similar was confusing.
The discussion can be found at #1974.

Delay the response body

You are able to specify the number of milliseconds that the response body should be delayed.
This is the time between the headers being received and the body starting to be received.

nock('http://my.server.com')
  .get('/')
  .delayBody(2000) // 2 seconds
  .reply(200, '<html></html>')
Technical Details

Following the 'response' being emitted by ClientRequest, Nock will register a timeout timer with the body delay value to delay real time before the IncomingMessage emits its first 'data' or the 'end' event.

Chaining

You can chain behaviour like this:

const scope = nock('http://myapp.iriscouch.com')
  .get('/users/1')
  .reply(404)
  .post('/users', {
    username: 'pgte',
    email: '[email protected]',
  })
  .reply(201, {
    ok: true,
    id: '123ABC',
    rev: '946B7D1C',
  })
  .get('/users/123ABC')
  .reply(200, {
    _id: '123ABC',
    _rev: '946B7D1C',
    username: 'pgte',
    email: '[email protected]',
  })

Scope filtering

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 if you have a node module that randomly changes subdomains to which it sends requests, e.g., the Dropbox node module behaves like this.

const scope = nock('https://api.dropbox.com', {
  filteringScope: scope => /^https:\/\/api[0-9]*.dropbox.com/.test(scope),
})
  .get('/1/metadata/auto/Photos?include_deleted=false&list=true')
  .reply(200)

Conditional scope filtering

You can also choose to filter out a scope based on your system environment (or any external factor). The filtering function is accepted at the conditionally field of the options argument.

This can be useful if you only want certain scopes to apply depending on how your tests are executed.

const scope = nock('https://api.myservice.com', {
  conditionally: () => true,
})

Path filtering

You can also filter the URLs based on a function.

This can be useful, for instance, if you have random or time-dependent data in your URL.

You can use a regexp for replacement, just like String.prototype.replace:

const scope = nock('http://api.myservice.com')
  .filteringPath(/password=[^&]*/g, 'password=XXX')
  .get('/users/1?password=XXX')
  .reply(200, 'user')

Or you can use a function:

const scope = nock('http://api.myservice.com')
  .filteringPath(path => '/ABC')
  .get('/ABC')
  .reply(200, 'user')

Note that scope.filteringPath is not cumulative: it should only be used once per scope.

Request Body filtering

You can also filter the request body based on a function.

This can be useful, for instance, if you have random or time-dependent data in your request body.

You can use a regexp for replacement, just like String.prototype.replace:

const scope = nock('http://api.myservice.com')
  .filteringRequestBody(/password=[^&]*/g, 'password=XXX')
  .post('/users/1', 'data=ABC&password=XXX')
  .reply(201, 'OK')

Or you can use a function to transform the body:

const scope = nock('http://api.myservice.com')
  .filteringRequestBody(body => 'ABC')
  .post('/', 'ABC')
  .reply(201, 'OK')

If you don't want to match the request body you should omit the body argument from the method function:

const scope = nock('http://api.myservice.com')
  .post('/some_uri') // no body argument
  .reply(200, 'OK')

Request Headers Matching

If you need to match requests only if certain request headers match, you can.

const scope = nock('http://api.myservice.com')
  // Interceptors created after here will only match when the header `accept` equals `application/json`.
  .matchHeader('accept', 'application/json')
  .get('/')
  .reply(200, {
    data: 'hello world',
  })
  .get('/')
  // Only this interceptor will match the header value `x-my-action` with `MyFirstAction`
  .matchHeader('x-my-action', 'MyFirstAction')
  .reply(200, {
    data: 'FirstActionResponse',
  })
  .get('/')
  // Only this interceptor will match the header value `x-my-action` with `MySecondAction`
  .matchHeader('x-my-action', 'MySecondAction')
  .reply(200, {
    data: 'SecondActionResponse',
  })

You can also use a regexp for the header body.

const scope = nock('http://api.myservice.com')
  .matchHeader('User-Agent', /Mozilla\/.*/)
  .get('/')
  .reply(200, {
    data: 'hello world',
  })

You can also use a function for the header body.

const scope = nock('http://api.myservice.com')
  .matchHeader('content-length', val => val >= 1000)
  .get('/')
  .reply(200, {
    data: 'hello world',
  })

Optional Requests

By default every mocked request is expected to be made exactly once, and until it is it'll appear in scope.pendingMocks(), and scope.isDone() will return false (see expectations). In many cases this is fine, but in some (especially cross-test setup code) it's useful to be able to mock a request that may or may not happen. You can do this with optionally(). Optional requests are consumed just like normal ones once matched, but they do not appear in pendingMocks(), and isDone() will return true for scopes with only optional requests pending.

const example = nock('http://example.com')
example.pendingMocks() // []
example.get('/pathA').reply(200)
example.pendingMocks() // ["GET http://example.com:80/path"]

// ...After a request to example.com/pathA:
example.pendingMocks() // []

example.get('/pathB').optionally().reply(200)
example.pendingMocks() // []

// You can also pass a boolean argument to `optionally()`. This
// is useful if you want to conditionally make a mocked request
// optional.
const getMock = optional =>
  example.get('/pathC').optionally(optional).reply(200)

getMock(true)
example.pendingMocks() // []
getMock(false)
example.pendingMocks() // ["GET http://example.com:80/pathC"]

Allow unmocked requests on a mocked hostname

If you need some request on the same host name to be mocked and some others to really go through the HTTP stack, you can use the allowUnmocked option like this:

const scope = nock('http://my.existing.service.com', { allowUnmocked: true })
  .get('/my/url')
  .reply(200, 'OK!')

// GET /my/url => goes through nock
// GET /other/url => actually makes request to the server

Note: When applying {allowUnmocked: true}, if the request is made to the real server, no interceptor is removed.

Expectations

Every time an HTTP request is performed for a scope that is mocked, Nock expects to find a handler for it. If it doesn't, it will throw an error.

Calls to nock() return a scope which you can assert by calling scope.done(). This will assert that all specified calls on that scope were performed.

Example:

const scope = nock('http://google.com')
  .get('/')
  .reply(200, 'Hello from Google!')

// do some stuff

setTimeout(() => {
  // Will throw an assertion error if meanwhile a "GET http://google.com" was
  // not performed.
  scope.done()
}, 5000)

.isDone()

You can call isDone() on a single expectation to determine if the expectation was met:

const scope = nock('http://google.com').get('/').reply(200)

scope.isDone() // will return false

It is also available in the global scope, which will determine if all expectations have been met:

nock.isDone()

.cleanAll()

You can cleanup all the prepared mocks (could be useful to cleanup some state after a failed test) like this:

nock.cleanAll()

.abortPendingRequests()

You can abort all current pending request like this:

nock.abortPendingRequests()

.persist()

You can make all the interceptors for a scope persist by calling .persist() on it:

const scope = nock('http://example.com')
  .persist()
  .get('/')
  .reply(200, 'Persisting all the way')

Note that while a persisted scope will always intercept the requests, it is considered "done" after the first interception.

If you want to stop persisting an individual persisted mock you can call persist(false):

const scope = nock('http://example.com').persist().get('/').reply(200, 'ok')

// Do some tests ...

scope.persist(false)

You can also use nock.cleanAll() which removes all mocks, including persistent mocks.

To specify an exact number of times that nock should repeat the response, use .times().

.pendingMocks()

If a scope is not done, you can inspect the scope to infer which ones are still pending using the scope.pendingMocks() function:

if (!scope.isDone()) {
  console.error('pending mocks: %j', scope.pendingMocks())
}

It is also available in the global scope:

console.error('pending mocks: %j', nock.pendingMocks())

.activeMocks()

You can see every mock that is currently active (i.e. might potentially reply to requests) in a scope using scope.activeMocks(). A mock is active if it is pending, optional but not yet completed, or persisted. Mocks that have intercepted their requests and are no longer doing anything are the only mocks which won't appear here.

You probably don't need to use this - it mainly exists as a mechanism to recreate the previous (now-changed) behavior of pendingMocks().

console.error('active mocks: %j', scope.activeMocks())

It is also available in the global scope:

console.error('active mocks: %j', nock.activeMocks())

.isActive()

Your tests may sometimes want to deactivate the nock interceptor. Once deactivated, nock needs to be re-activated to work. You can check if nock interceptor is active or not by using nock.isActive(). Sample:

if (!nock.isActive()) {
  nock.activate()
}

.clone()

You can clone a scope by calling .clone() on it:

const scope = nock('http://example.test')

const getScope = scope.get('/').reply(200)
const postScope = scope.clone().post('/').reply(200)

Restoring

You can restore the HTTP interceptor to the normal unmocked behaviour by calling:

nock.restore()

note 1: restore does not clear the interceptor list. Use nock.cleanAll() if you expect the interceptor list to be empty.

note 2: restore will also remove the http interceptor itself. You need to run nock.activate() to re-activate the http interceptor. Without re-activation, nock will not intercept any calls.

Activating

Only for cases where nock has been deactivated using nock.restore(), you can reactivate the HTTP interceptor to start intercepting HTTP calls using:

nock.activate()

note: To check if nock HTTP interceptor is active or inactive, use nock.isActive().

Turning Nock Off (experimental!)

You can bypass Nock completely by setting the NOCK_OFF environment variable to "true".

This way you can have your tests hit the real servers just by switching on this environment variable.

$ NOCK_OFF=true node my_test.js

Enable/Disable real HTTP requests

By default, any requests made to a host that is not mocked will be executed normally. If you want to block these requests, nock allows you to do so.

Disabling requests

For disabling real http requests.

nock.disableNetConnect()

So, if you try to request any host not 'nocked', it will throw a NetConnectNotAllowedError.

nock.disableNetConnect()
const req = http.get('http://google.com/')
req.on('error', err => {
  console.log(err)
})
// The returned `http.ClientRequest` will emit an error event (or throw if you're not listening for it)
// This code will log a NetConnectNotAllowedError with message:
// Nock: Disallowed net connect for "google.com:80"

Enabling requests

For enabling any real HTTP requests (the default behavior):

nock.enableNetConnect()

You could allow real HTTP requests for certain host names by providing a string or a regular expression for the hostname, or a function that accepts the hostname and returns true or false:

// Using a string
nock.enableNetConnect('amazon.com')

// Or a RegExp
nock.enableNetConnect(/(amazon|github)\.com/)

// Or a Function
nock.enableNetConnect(
  host => host.includes('amazon.com') || host.includes('github.com'),
)

http.get('http://www.amazon.com/')
http.get('http://github.com/')

http.get('http://google.com/')
// This will throw NetConnectNotAllowedError with message:
// Nock: Disallowed net connect for "google.com:80"

A common use case when testing local endpoints would be to disable all but localhost, then add in additional nocks for external requests:

nock.disableNetConnect()
// Allow localhost connections so we can test local routes and mock servers.
nock.enableNetConnect('127.0.0.1')

Resetting NetConnect

When you're done with the test, you probably want to set everything back to normal:

nock.cleanAll()
nock.enableNetConnect()

Recording

This is a cool feature:

Guessing what the HTTP calls are is a mess, especially if you are introducing nock on your already-coded tests.

For these cases where you want to mock an existing live system you can record and playback the HTTP calls like this:

nock.recorder.rec()
// Some HTTP calls happen and the nock code necessary to mock
// those calls will be outputted to console

Recording relies on intercepting real requests and responses and then persisting them for later use.

In order to stop recording you should call nock.restore() and recording will stop.

ATTENTION!: when recording is enabled, nock does no validation, nor will any mocks be enabled. Please be sure to turn off recording before attempting to use any mocks in your tests.

dont_print option

If you just want to capture the generated code into a var as an array you can use:

nock.recorder.rec({
  dont_print: true,
})
// ... some HTTP calls
const 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! You can call nock.recorder.clear() to remove already recorded calls from the array that nock.recorder.play() returns.

(Remember that you should do this one test at a time).

output_objects option

In case you want to generate the code yourself or use the test data in some other way, you can pass the output_objects option to rec:

nock.recorder.rec({
  output_objects: true,
})
// ... some HTTP calls
const nockCallObjects = nock.recorder.play()

The returned call objects have the following properties:

  • scope - the scope of the call including the protocol and non-standard ports (e.g. 'https://github.com:12345')
  • method - the HTTP verb of the call (e.g. 'GET')
  • path - the path of the call (e.g. '/pgte/nock')
  • body - the body of the call, if any
  • status - the HTTP status of the reply (e.g. 200)
  • response - the body of the reply which can be a JSON, string, hex string representing binary buffers or an array of such hex strings (when handling content-encoded in reply header)
  • rawHeaders - the headers of the reply which are formatted as a flat array containing header name and header value pairs (e.g. ['accept', 'application/json', 'set-cookie', 'my-cookie=value'])
  • reqheader - the headers of the request

If you save this as a JSON file, you can load them directly through nock.load(path). Then you can post-process them before using them in the tests. For example, to add request body filtering (shown here fixing timestamps to match the ones captured during recording):

nocks = nock.load(pathToJson)
nocks.forEach(function (nock) {
  nock.filteringRequestBody = (body, aRecordedBody) => {
    if (typeof body !== 'string' || typeof aRecordedBody !== 'string') {
      return body
    }

    const recordedBodyResult = /timestamp:([0-9]+)/.exec(aRecordedBody)
    if (recordedBodyResult) {
      const recordedTimestamp = recordedBodyResult[1]
      return body.replace(
        /(timestamp):([0-9]+)/g,
        function (match, key, value) {
          return key + ':' + recordedTimestamp
        },
      )
    } else {
      return body
    }
  }
})

Alternatively, if you need to pre-process the captured nock definitions before using them (e.g. to add scope filtering) then you can use nock.loadDefs(path) and nock.define(nockDefs). Shown here is scope filtering for Dropbox node module which constantly changes the subdomain to which it sends the requests:

//  Pre-process the nock definitions as scope filtering has to be defined before the nocks are defined (due to its very hacky nature).
const nockDefs = nock.loadDefs(pathToJson)
nockDefs.forEach(def => {
  //  Do something with the definition object e.g. scope filtering.
  def.options = {
    ...def.options,
    filteringScope: scope => /^https:\/\/api[0-9]*.dropbox.com/.test(scope),
  }
})

//  Load the nocks from pre-processed definitions.
const nocks = nock.define(nockDefs)

enable_reqheaders_recording option

Recording request headers by default is deemed more trouble than it's worth as some of them depend on the timestamp or other values that may change after the tests have been recorded thus leading to complex postprocessing of recorded tests. Thus by default the request headers are not recorded.

The genuine use cases for recording request headers (e.g. checking authorization) can be handled manually or by using enable_reqheaders_recording in recorder.rec() options.

nock.recorder.rec({
  dont_print: true,
  output_objects: true,
  enable_reqheaders_recording: true,
})

Note that even when request headers recording is enabled Nock will never record user-agent headers. user-agent values change with the version of Node and underlying operating system and are thus useless for matching as all that they can indicate is that the user agent isn't the one that was used to record the tests.

logging option

Nock will print using console.log by default (assuming that dont_print is false). If a different function is passed into logging, nock will send the log string (or object, when using output_objects) to that function. Here's a basic example.

const appendLogToFile = content => {
  fs.appendFile('record.txt', content)
}
nock.recorder.rec({
  logging: appendLogToFile,
})

use_separator option

By default, nock will wrap its output with the separator string <<<<<<-- cut here -->>>>>> before and after anything it prints, whether to the console or a custom log function given with the logging option.

To disable this, set use_separator to false.

nock.recorder.rec({
  use_separator: false,
})

.removeInterceptor()

This allows removing a specific interceptor. This can be either an interceptor instance or options for a url. It's useful when there's a list of common interceptors shared between tests, where an individual test requires one of the shared interceptors to behave differently.

Examples:

nock.removeInterceptor({
  hostname: 'localhost',
  path: '/mockedResource',
})
nock.removeInterceptor({
  hostname: 'localhost',
  path: '/login',
  method: 'POST',
  proto: 'https',
})
const interceptor = nock('http://example.org').get('somePath')
nock.removeInterceptor(interceptor)

Note .reply(...) method returns Scope, not Interceptor, and so it is not a valid argument for nock.removeInterceptor. So if your method chain ends with .reply to be used with nock.removeInterceptor the chain need to be break in between:

// this will NOT work
const interceptor = nock('http://example.org').get('somePath').reply(200, 'OK')
nock.removeInterceptor(interceptor)
// this is how it should be
const interceptor = nock('http://example.org').get('somePath')
interceptor.reply(200, 'OK')
nock.removeInterceptor(interceptor)

Events

A scope emits the following events:

  • emit('request', function(req, interceptor, body))
  • emit('replied', function(req, interceptor))

Global no match event

You can also listen for no match events like this:

nock.emitter.on('no match', req => {})

Nock Back

Fixture recording support and playback.

Setup

You must specify a fixture directory before using, for example:

In your test helper

const nockBack = require('nock').back

nockBack.fixtures = '/path/to/fixtures/'
nockBack.setMode('record')

Options

  • nockBack.fixtures : path to fixture directory
  • nockBack.setMode() : the mode to use

Usage

By default if the fixture doesn't exist, a nockBack will create a new fixture and save the recorded output for you. The next time you run the test, if the fixture exists, it will be loaded in.

The this context of the callback function will have a property scopes to access all of the loaded nock scopes.

const nockBack = require('nock').back
const request = require('request')
nockBack.setMode('record')

nockBack.fixtures = __dirname + '/nockFixtures' //this only needs to be set once in your test helper

// recording of the fixture
nockBack('zomboFixture.json', nockDone => {
  request.get('http://zombo.com', (err, res, body) => {
    nockDone()

    // usage of the created fixture
    nockBack('zomboFixture.json', function (nockDone) {
      http.get('http://zombo.com/').end() // respond body "Ok"

      this.assertScopesFinished() //throws an exception if all nocks in fixture were not satisfied
      http.get('http://zombo.com/').end() // throws exception because someFixture.json only had one call

      nockDone() //never gets here
    })
  })
})

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)`
})

Or, with async/await:

const { nockDone, context } = await nockBack('promisedFixture.json')
//  your test code
nockDone()

Options

As an optional second parameter you can pass the following options

  • before: a preprocessing function, gets called before nock.define
  • after: a postprocessing function, gets called after nock.define
  • afterRecord: a postprocessing function, gets called after recording. Is passed the array of scopes recorded and should return the intact array, a modified version of the array, or if custom formatting is desired, a stringified version of the array to save to the fixture
  • recorder: custom options to pass to the recorder
Example
function prepareScope(scope) {
  scope.filteringRequestBody = (body, aRecordedBody) => {
    if (typeof body !== 'string' || typeof aRecordedBody !== 'string') {
      return body
    }

    const recordedBodyResult = /timestamp:([0-9]+)/.exec(aRecordedBody)
    if (recordedBodyResult) {
      const recordedTimestamp = recordedBodyResult[1]
      return body.replace(
        /(timestamp):([0-9]+)/g,
        (match, key, value) => `${key}:${recordedTimestamp}`,
      )
    } else {
      return body
    }
  }
}

nockBack('exampleFixture.json', { before: prepareScope }, nockDone => {
  request.get('http://example.com', function (err, res, body) {
    // do your tests
    nockDone()
  })
})

Modes

To set the mode call nockBack.setMode(mode) or run the tests with the NOCK_BACK_MODE environment variable set before loading nock. If the mode needs to be changed programmatically, the following is valid: nockBack.setMode(nockBack.currentMode)

  • wild: all requests go out to the internet, don't replay anything, doesn't record anything

  • dryrun: The default, use recorded nocks, allow http calls, doesn't record anything, useful for writing new tests

  • record: use recorded nocks, record new nocks

  • update: remove recorded nocks, record nocks

  • lockdown: use recorded nocks, disables all http calls even when not nocked, doesn't record

Verifying recorded fixtures

Although you can certainly open the recorded JSON fixtures to manually verify requests recorded by nockBack - it's sometimes useful to put those expectations in your tests.

The context.query function can be used to return all of the interceptors that were recored in a given fixture.

By itself, this functions as a negative expectation - you can verify that certain calls do NOT happen in the fixture. Since assertScopesFinished can verify there are no extra calls in a fixture - pairing the two methods allows you to verify the exact set of HTTP interactions recorded in the fixture. This is especially useful when re-recording for instance, a service that synchronizes via several HTTP calls to an external API.

NB: The list of fixtures is only available when reading. It will only be populated for nocks that are played back from fixtures.

Example

it('#synchronize - synchronize with the external API', async localState => {
  const { nockDone, context } = await back('http-interaction.json')

  const syncronizer = new Synchronizer(localState)

  sycnronizer.syncronize()

  nockDone()

  context.assertScopesFinished()

  expect(context.query()).toEqual(
    expect.arrayContaining([
      expect.objectContaining({
        method: 'POST',
        path: '/create/thing',
      }),
      expect.objectContaining({
        method: 'POST',
        path: 'create/thing',
      }),
    ]),
  )
})

Common issues

"No match for response" when using got with error responses

Got automatically retries failed requests twice. That means if you have a test which mocks a 4xx or 5xx response, got will immediately reissue it. At that point, the mock will have been consumed and the second request will error out with Nock: No match for request.

The same is true for .replyWithError().

Adding { retry: 0 } to the got invocations will disable retrying, e.g.:

await got('http://example.test/', { retry: 0 })

If you need to do this in all your tests, you can create a module got_client.js which exports a custom got instance:

const got = require('got')

module.exports = got.extend({ retry: 0 })

This is how it's handled in Nock itself (see #1523).

Axios

To use Nock with Axios, you may need to configure Axios to use the Node adapter as in the example below:

import axios from 'axios'
import nock from 'nock'
import test from 'ava' // You can use any test framework.

// If you are using jsdom, axios will default to using the XHR adapter which
// can't be intercepted by nock. So, configure axios to use the node adapter.
//
// References:
// https://github.com/axios/axios/pull/5277

axios.defaults.adapter = 'http'

test('can fetch test response', async t => {
  // Set up the mock request.
  const scope = nock('http://localhost')
    .get('/test')
    .reply(200, 'test response')

  // Make the request. Note that the hostname must match exactly what is passed
  // to `nock()`. Alternatively you can set `axios.defaults.host = 'http://localhost'`
  // and run `axios.get('/test')`.
  await axios.get('http://localhost/test')

  // Assert that the expected request was made.
  scope.done()
})

For Nock + Axios + Jest to work, you'll have to also adapt your jest.config.js, like so:

const config = {
  moduleNameMapper: {
    // Force CommonJS build for http adapter to be available.
    // via https://github.com/axios/axios/issues/5101#issuecomment-1276572468
    '^axios$': require.resolve('axios'),
  },
}

Memory issues with Jest

Memory issues can be avoided by calling nock.restore() after each test suite.
One of the core principles of Jest is that it runs tests in isolation. It does this by manipulating the modules cache of Node in a way that conflicts with how Nock monkey patches the builtin http and https modules. Related issue with more details.

Debugging

Nock uses debug, so just run with environmental variable DEBUG set to nock.*.

user@local$ DEBUG=nock.* node my_test.js

Each step in the matching process is logged this way and can be useful when determining why a request was not intercepted by Nock.

For example the following shows that matching failed because the request had an extra search parameter.

nock('http://example.com').get('/').query({ foo: 'bar' }).reply()

await got('http://example.com/?foo=bar&baz=foz')
user@local$ DEBUG=nock.scope:example.com node my_test.js
...
nock.scope:example.com Interceptor queries: {"foo":"bar"} +1ms
nock.scope:example.com     Request queries: {"foo":"bar","baz":"foz"} +0ms
nock.scope:example.com query matching failed +0ms

Contributing

Thanks for wanting to contribute! Take a look at our Contributing Guide for notes on our commit message conventions and how to run tests.

Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.

Contributors

Thanks goes to these wonderful people (emoji key):

Pedro Teixeira
Pedro Teixeira

💻 🚧
n30n0v
n30n0v

💻
Richard Littauer
Richard Littauer

🚧 💻 📝
Ian Walker-Sperber
Ian Walker-Sperber

💻
Ivan Erceg
Ivan Erceg

💻 🚧
Paul Melnikow
Paul Melnikow

💻 🚧
Gregor Martynus
Gregor Martynus

💻 🚧 💼 💵 📝
Hutson Betts
Hutson Betts

💵
Jonas Lilja
Jonas Lilja

💵 💻
Benjamin Ki
Benjamin Ki

💵
Chad Fawcett
Chad Fawcett

💵
Laurence Dougal Myers
Laurence Dougal Myers

💻
Sébastien Van Bruaene
Sébastien Van Bruaene

💻 ⚠️
Aras Abbasi
Aras Abbasi

💻 ⚠️ 🚧
Saryev Rustam
Saryev Rustam

💻 ⚠️
Michael Solomon
Michael Solomon

🚧 💻 📖

This project follows the all-contributors specification. Contributions of any kind welcome!

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]

License

MIT

Copyright (c) 2011–2019 Pedro Teixeira and other contributors.

nock's People

Contributors

aleung avatar allcontributors[bot] avatar brettporter avatar dependabot[bot] avatar dscape avatar fent avatar gr2m avatar greenkeeper[bot] avatar greenkeeperio-bot avatar ianwsperber avatar ierceg avatar kelaban avatar mastermatt avatar mbad0la avatar mdlavin avatar merlinnot avatar monkeywithacupcake avatar neophob avatar paulmelnikow avatar pgte avatar pimterry avatar richardlitt avatar svnlto avatar tinogomes avatar tricknotes avatar unstubbable avatar uzlopak avatar vrinek avatar xla avatar zeevl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nock's Issues

Hook nock up on travis-ci.org

It would be great if nock would be tested on travis-ci.org. It's free and supports node, and can be set up in 5 minutes.

nocked requests don't have request.path

Normal http requests (that are not mocked with nock) have a request.path property. Requests that get nocked do not have a request.path property.

The following sample code exhibits the difference between a normal request and a nocked request:

var http = require('http');
var nock = require('nock');

var options = {
  host: "www.google.com",
  path: "/no/such/path"
};

var httpReq = http.request(options);

var scope = nock("http://www.google.com")
  .get("/no/such/path")
  .reply(200);

var nockReq = http.request(options);

console.log(httpReq.path);  // => "/no/such/path"
console.log(nockReq.path); // => undefined

[recorder] Bad quoting of json strings

nock('nodejsbug.iriscouch.com')
  .put('/v061_doc_cha/foobar', '"{\"foo\":\"bar\"}"')
  .reply(201, "{\"ok\":true,\"id\":\"foobar\",\"rev\":\"1-4c6114c65e295552ab1019e2b046b10e\"}\n", { server: 'CouchDB/1.1.1 (Erlang OTP/R14B04)',
  location: 'http://nodejsbug.iriscouch.com/v061_doc_cha/foobar',
  etag: '"1-4c6114c65e295552ab1019e2b046b10e"',
  date: 'Fri, 02 Dec 2011 00:34:58 GMT',
  'content-type': 'application/json',
  'content-length': '69',
  'cache-control': 'must-revalidate' });

'"{\"foo\":\"bar\"}"' should be '{"foo":"bar"}'

Incorrect verbs when generating recordings

Right now recordings generate code that, among other things, include a builder pattern with a function that is named after the HTTP verb. The problem is that the scope may not support that verb.

E.g.:

nock('http://ciserversdk.table.core.windows.net:80')
.filteringRequestBody(function (path) { return '';})
.merge('/tableservice12(PartitionKey=%27part1%27,RowKey=%27row1%27)', '
')
.reply(204, "", { 'cache-control': 'no-cache',
'content-length': '0',
date: 'Thu, 21 Feb 2013 00:30:56 GMT' });

A better solution would be to generate (at least for the ones that are not trivial / supported) the recordings with a generic http verb processing mechanism.

Example:

.request('merge', 'tableservice12' foobar)
.reply(foobar)

Most recent version breaks in nano


> [email protected] test /Users/dscape/Desktop/dev/nano
> ./node_modules/ensure/bin/tap.js tests/*/*.js

ok destroy.js ....................... 4/4
ok get.js ........................... 3/3
ok insert.js ........................ 4/4
ok pipe.js .......................... 2/2
ok update.js ........................ 4/4
ok changes.js ....................... 4/4
ok compact.js ....................... 4/4
ok create.js ........................ 3/3
ok destroy.js ....................... 3/3
ok get.js ........................... 5/5
ok list.js .......................... 3/3
ok replicate.js ..................... 4/4
ok bulk.js .......................... 5/5
ok destroy.js ....................... 5/5
not ok get.js ...................... 9/10
    Command: "node" "get.js"
    ok 1 No excs
    ok 2 I got rev
    ok 3 My id is foo
    ok 4 My foo is bar
    not ok 5 Nock is done
      ---
        file:   /Users/dscape/Desktop/dev/nano/nano.js
        line:   165
        column: 11
        stack:  
          - getCaller (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-assert/assert.js:370:17)
          - Function.assert (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-assert/assert.js:17:16)
          - Test._testAssert [as ok] (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-test/test.js:86:16)
          - /Users/dscape/Desktop/dev/nano/tests/doc/get.js:84:10
          - Request._callback (/Users/dscape/Desktop/dev/nano/nano.js:165:11)
          - Request.callback (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:108:22)
          - Request.<anonymous> (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:468:18)
          - Request.emit (events.js:67:17)
          - EventEmitter.<anonymous> (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:429:16)
          - EventEmitter.emit (events.js:64:17)
        unique: 4
      ...
    ok 6 Err, not here
    ok 7 Got revs info
    ok 8 Id is food
    ok 9 Bar is in foo
    ok 10 Nock is done

    1..10
    # tests 10
    # pass  9
    # fail  1

ok insert.js ...................... 10/10
not ok list.js .................... 11/14
    Command: "node" "list.js"
    ok 1 No err
    ok 2 3 Rows
    ok 3 Got rows
    not ok 4 Nock is done
      ---
        file:   /Users/dscape/Desktop/dev/nano/nano.js
        line:   165
        column: 11
        stack:  
          - getCaller (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-assert/assert.js:370:17)
          - Function.assert (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-assert/assert.js:17:16)
          - Test._testAssert [as ok] (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-test/test.js:86:16)
          - /Users/dscape/Desktop/dev/nano/tests/doc/list.js:168:10
          - Request._callback (/Users/dscape/Desktop/dev/nano/nano.js:165:11)
          - Request.callback (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:108:22)
          - Request.<anonymous> (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:468:18)
          - Request.emit (events.js:67:17)
          - EventEmitter.<anonymous> (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:429:16)
          - EventEmitter.emit (events.js:64:17)
        unique: 3
      ...
    ok 5 Not Err
    ok 6 One Row
    ok 7 Out of 3
    ok 8 I got Rows
    not ok 9 Nock is done
      ---
        file:   /Users/dscape/Desktop/dev/nano/nano.js
        line:   165
        column: 11
        stack:  
          - getCaller (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-assert/assert.js:370:17)
          - Function.assert (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-assert/assert.js:17:16)
          - Test._testAssert [as ok] (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-test/test.js:86:16)
          - /Users/dscape/Desktop/dev/nano/tests/doc/list.js:193:10
          - Request._callback (/Users/dscape/Desktop/dev/nano/nano.js:165:11)
          - Request.callback (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:108:22)
          - Request.<anonymous> (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:468:18)
          - Request.emit (events.js:67:17)
          - EventEmitter.<anonymous> (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:429:16)
          - EventEmitter.emit (events.js:64:17)
        unique: 8
      ...
    ok 10 No errs
    ok 11 Two rows returned
    ok 12 Out of 3
    ok 13 That means we got rows
    not ok 14 Nock is done
      ---
        file:   /Users/dscape/Desktop/dev/nano/nano.js
        line:   165
        column: 11
        stack:  
          - getCaller (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-assert/assert.js:370:17)
          - Function.assert (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-assert/assert.js:17:16)
          - Test._testAssert [as ok] (/Users/dscape/Desktop/dev/nano/node_modules/ensure/node_modules/tap/node_modules/tap-test/test.js:86:16)
          - /Users/dscape/Desktop/dev/nano/tests/doc/list.js:214:10
          - Request._callback (/Users/dscape/Desktop/dev/nano/nano.js:165:11)
          - Request.callback (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:108:22)
          - Request.<anonymous> (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:468:18)
          - Request.emit (events.js:67:17)
          - EventEmitter.<anonymous> (/Users/dscape/Desktop/dev/nano/node_modules/request/main.js:429:16)
          - EventEmitter.emit (events.js:64:17)
        unique: 13
      ...

    1..14
    # tests 14
    # pass  11
    # fail  3

ok update.js ........................ 5/5
ok create.js ........................ 0/0
ok delete.js ........................ 0/0
ok cfg.js ......................... 19/19
ok error.js ......................... 8/8
ok nano.js .......................... 2/2
ok compact.js ....................... 3/3
ok multi-document-fetch.js .......... 5/5
ok query.js ......................... 6/6
total ........................... 157/161

not ok

npm.log

info it worked if it ends with ok
verbose cli [ 'node', '/usr/local/bin/npm', 'test' ]
info using [email protected]
info using [email protected]
verbose config file /Users/dscape/.npmrc
verbose config file /usr/local/etc/npmrc
verbose config file /usr/local/lib/node_modules/npm/npmrc
silly testEngine { name: 'nano',
silly testEngine   description: 'minimalistic couchdb driver for node.js',
silly testEngine   homepage: 'http://github.com/dscape/nano',
silly testEngine   repository: { type: 'git', url: 'git://github.com/dscape/nano.git' },
silly testEngine   version: '1.1.0',
silly testEngine   author: 
silly testEngine    { name: 'Nuno Job',
silly testEngine      email: '[email protected]',
silly testEngine      url: 'http://nunojob.com' },
silly testEngine   contributors: 
silly testEngine    [ { name: 'Thiago Arrais',
silly testEngine        email: '[email protected]',
silly testEngine        url: 'http://thiagoarrais.com' },
silly testEngine      { name: 'Derek Perez',
silly testEngine        email: '[email protected]',
silly testEngine        url: 'http://blog.derekperez.com' },
silly testEngine      { name: 'Patrick Heneise', url: 'http://patrickheneise.me' },
silly testEngine      { name: 'Artur Konarski', url: 'http://tarantoga.com' },
silly testEngine      { name: 'Pedro Teixeira',
silly testEngine        email: '[email protected]',
silly testEngine        url: 'http://metaduck.com' } ],
silly testEngine   keywords: 
silly testEngine    [ 'couchdb',
silly testEngine      'data',
silly testEngine      'request',
silly testEngine      'json',
silly testEngine      'nosql',
silly testEngine      'micro',
silly testEngine      'nano',
silly testEngine      'database' ],
silly testEngine   dependencies: { request: '2.9.3', underscore: '1.2.3' },
silly testEngine   devDependencies: 
silly testEngine    { async: '0.1.15',
silly testEngine      ensure: '0.4.6',
silly testEngine      nock: '*',
silly testEngine      'tap-runner': '0.0.7',
silly testEngine      'tap-producer': '0.0.1',
silly testEngine      tap: '0.1.3',
silly testEngine      inherits: '1.0.0',
silly testEngine      yamlish: '0.0.2',
silly testEngine      slide: '1.1.3' },
silly testEngine   scripts: { test: './node_modules/ensure/bin/tap.js tests/*/*.js' },
silly testEngine   main: './nano.js',
silly testEngine   engines: { node: '>=0.3.6' },
silly testEngine   _npmUser: { name: 'dscape', email: '[email protected]' },
silly testEngine   _id: '[email protected]',
silly testEngine   _engineSupported: true,
silly testEngine   _npmVersion: '1.0.106',
silly testEngine   _nodeVersion: 'v0.6.1',
silly testEngine   _defaultsLoaded: true }
verbose caching /Users/dscape/Desktop/dev/nano/package.json
verbose loadDefaults [email protected]
verbose run-script [ 'pretest', 'test', 'posttest' ]
info pretest [email protected]
info test [email protected]
verbose unsafe-perm in lifecycle true
silly exec sh "-c" "./node_modules/ensure/bin/tap.js tests/*/*.js"
silly spawning [ 'sh',
silly spawning   [ '-c', './node_modules/ensure/bin/tap.js tests/*/*.js' ],
silly spawning   '/Users/dscape/Desktop/dev/nano' ]
info [email protected] Failed to exec test script
ERR! [email protected] test: `./node_modules/ensure/bin/tap.js tests/*/*.js`
ERR! `sh "-c" "./node_modules/ensure/bin/tap.js tests/*/*.js"` failed with 4
ERR! 
ERR! Failed at the [email protected] test script.
ERR! This is most likely a problem with the nano package,
ERR! not with npm itself.
ERR! Tell the author that this fails on your system:
ERR!     ./node_modules/ensure/bin/tap.js tests/*/*.js
ERR! You can get their info via:
ERR!     npm owner ls nano
ERR! There is likely additional logging output above.
ERR! 
ERR! System Darwin 11.2.0
ERR! command "node" "/usr/local/bin/npm" "test"
ERR! cwd /Users/dscape/Desktop/dev/nano
ERR! node -v v0.6.1
ERR! npm -v 1.0.106
ERR! code ELIFECYCLE
verbose exit [ 1, true ]

Wrong body comparison for JSON

It seems like with this:
30b8308

json bodies can now be specified. However, the recordings still use strings for them. Example:

nock('https://management.core.windows.net:443')
.get('/db1ab6f0-4769-4b27-930e-01e2ef9c123c/services/mobileservices/mobileservices/clitest67f3e332-a60c-436c-a459-232827f16732/scheduler/jobs/foobar')
.reply(200, "{"appName":"clitest67f3e332-a60c-436c-a459-232827f16732","name":"foobar","status":"disabled","intervalUnit":"minute","intervalPeriod":15,"startTime":"2013-04-02T18:46:54.897Z"}", { 'cache-control': 'no-cache',
pragma: 'no-cache',
'content-length': '176',
'content-type': 'application/json; charset=utf-8',
expires: '-1',
server: '33.0.6198.21 (rd_rdfe_stable.130329-1122) Microsoft-HTTPAPI/2.0',
'x-ms-servedbyregion': 'ussouth',
'x-aspnet-version': '4.0.30319',
'x-powered-by': 'ASP.NET',
'x-ms-request-id': 'a72aa96c2fa247c5a3e2fca1719475af',
date: 'Tue, 02 Apr 2013 18:47:04 GMT' });
return result; },

What happens is that now the matching is broken. Seems like the issue is in the new match_body file.

When invoked with:

== body ==
type: string
value: {"intervalPeriod":2,"intervalUnit":"hour","startTime":"2013-04-02T18:46:54.897Z","status":"enabled"}

== spec ==
type: string
value: {"intervalPeriod":2,"intervalUnit":"hour","startTime":"2013-04-02T18:46:54.897Z","status":"enabled"}

It returns false :-/

And consequently, the test run gives:
Nock: No match for HTTP request PUT /db1ab6f0-4769-4b27-930e-01e2ef9c123c/services/mobileservices/mobileservices/clitest67f3e332-a60c-436c-a459-232827f16732/scheduler/jobs/foobar {"intervalPeriod":2,"intervalUnit":"hour","startTime":"2013-04-02T18:46:54.897Z","status":"enabled"}

Ideally this method should work properly in this case (Sounds like a bug) and the recordings could be smart enough to use the new json format.

Scope

All my nock tests look like this:

if(process.env.NOCK) {
  nock....
}

I would propose to make this a feature of nock:

var nock = require('nock');
nock.only(process.env.NOCK);

Nock Recorder Outputting code Missing Protocol

The recorder in nock does not output the URL protocol in the generated code when using 'http'. As a result, when trying to reuse the code in tests, and calling .done() on a nock-ed call, the test will fail.

For example, if we record the following call:

  var req = http.request({
      host: "www.google.com"
    , path: '/'
    , port: 80
  }, function(res) {
     // whatever happens here
  });

The following code is generated by the nock recorder:

nock('www.google.com:80')
  .get('/')
  .reply(200, "<!doctype html><html itemscope=\"itemscope\" itemtype=\"http://schema.org/WebPage\"><head><meta content=\"Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for.\" name=\"description\"><meta content=\"noodp\" name=\"robots\"><meta itemprop=\"image\" content=\"/images/google_favicon_128.png\"><title>Google</title><script>(function(){\nwindow.google={kEI:\"HMsEUY7iGc7kigKXlYHQCQ\",getEI:function(a){for(var b;a&&(!a.getAttribute||!(b=a.getAttribute(\"eid\")));)a=a.parentNode;return b||google.kEI},https:function(){return\"https:\"==window.location.protocol},kEXPI:\"17259,18167,39523,39977,4000116,4001569,4001959,4001975,4002001,4002378,4002436,4002523,4002562,4002700,4002858,4002928,4003035,4003053,4003104,4003215,4003225,4003316,4003318,4003335,4003340,4003387,4003437,4003510,4003518,4003654\",kCSI:{e:\"17259,18167,39523,39977,4000116,4001569,4001959,4001975,4002001,4002378,4002436,4002523,4002562,4002700,4002858,4002928,4003035,4003053,4003104,4003215,4003225,4003316,4003318,4003335,4003340,4003387,4003437,4003510,4003518,4003654\",ei:\"HMsEUY7iGc7kigKXlYHQCQ\"},authuser:0,ml:function(){},kHL:\"en\",time:function(){return(new Date).getTime()},log:function(a,\nb,c,i){var d=new Image,f=google.lc,e=google.li,g=\"\";d.onerror=d.onload=d.onabort=function(){delete f[e]};f[e]=d;!c&&-1==b.search(\"&ei=\")&&(g=\"&ei=\"+google.getEI(i));c=c||\"/gen_204?atyp=i&ct=\"+a+\"&cad=\"+b+g+\"&zx=\"+google.time();a=/^http:/i;a.test(c)&&google.https()?(google.ml(Error(\"GLMM\"),!1,{src:c}),delete f[e]):(d.src=c,google.li=e+1)},lc:[],li:0,Toolbelt:{},y:{},x:function(a,b){google.y[a.id]=[a,b];return!1},load:function(a,b){google.x({id:\"l\"+a},function(){google.load(a,b)})}};\n})();\n(function(){var d=!1;google.sn=\"webhp\";google.timers={};google.startTick=function(a,b){google.timers[a]={t:{start:google.time()},bfr:!!b}};google.tick=function(a,b,h){google.timers[a]||google.startTick(a);google.timers[a].t[b]=h||google.time()};google.startTick(\"load\",!0);\ntry{}catch(e){}})();\nvar _gjwl=location;function _gjuc(){var a=_gjwl.href.indexOf(\"#\");if(0<=a&&(a=_gjwl.href.substring(a),0<a.indexOf(\"&q=\")||0<=a.indexOf(\"#q=\")))if(a=a.substring(1),-1==a.indexOf(\"#\")){for(var d=0;d<a.length;){var b=d;\"&\"==a.charAt(b)&&++b;var c=a.indexOf(\"&\",b);-1==c&&(c=a.length);b=a.substring(b,c);if(0==b.indexOf(\"fp=\"))a=a.substring(0,d)+a.substring(c,a.length),c=d;else if(\"cad=h\"==b)return 0;d=c}_gjwl.href=\"/search?\"+a+\"&cad=h\";return 1}return 0}\nfunction _gjp(){(!window._gjwl.hash||!window._gjuc())&&setTimeout(_gjp,500)};\nwindow._gjp&&_gjp();</script><style>#gbar,#guser{font-size:13px;padding-top:1px !important;}#gbar{height:22px}#guser{padding-bottom:7px !important;text-align:right}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}@media all{.gb1{height:22;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb4{text-decoration:underline !important}a.gb1,a.gb4{color:#00c !important}.gbi .gb4{color:#dd8e27 !important}.gbf .gb4{color:#900 !important}</style><style>.h{font-family:arial,sans-serif}body{font-family:arial,sans-serif}td{font-family:arial,sans-serif}a{font-family:arial,sans-serif}p{font-family:arial,sans-serif}body{margin:0;overflow-y:scroll}#gog{padding:3px 8px 0}.h{color:#36c}.q{color:#00c}.ts{border-collapse:collapse}td{line-height:.8em}.gac_m td{line-height:17px}form{margin-bottom:20px}.ts td{padding:0}em{font-weight:bold;font-style:normal}.lst{height:25px;width:496px;font:18px arial,sans-serif}.gsfi{font:18px arial,sans-serif}.gsfs{font:17px arial,sans-serif}.ds{display:inline-box;display: inline-block;margin:3px 0 4px;margin-left:4px}input{font-family:inherit}body{background:#fff;color:black}a.gb1{color:#11c !important}a.gb2{color:#11c !important}a.gb3{color:#11c !important}a.gb4{color:#11c !important}.sblc{padding-top:5px}.lsbb{background:#eee;border:solid 1px;border-color:#ccc #999 #999 #ccc;height:30px}a{color:#11c;text-decoration:none}a:hover{text-decoration:underline}a:active{text-decoration:underline}.fl a{color:#36c}a:visited{color:#551a8b}a.gb1{text-decoration:underline}a.gb4{text-decoration:underline}a.gb3:hover{text-decoration:none}.sblc a{display:block;margin:2px 0;margin-left:13px;font-size:11px}#ghead a.gb2:hover{color:#fff !important}.lsbb{display:block}.ftl{display:inline-block;margin:0 12px}.lsb{background:url(/images/srpr/nav_logo80.png) 0 -258px repeat-x;border:none;color:#000;cursor:pointer;height:30px;margin:0;outline:0;font:15px arial,sans-serif;vertical-align:top}#fll a{display:inline-block;margin:0 12px}.lsb:active{background:#ccc}.lst:focus{outline:none}#addlang a{padding:0 3px}</style><script></script> </head><body dir=\"ltr\" bgcolor=\"#fff\"><script>(function(){var src='/images/srpr/nav_logo80.png';var iesg=false;document.body.onload = function(){window.n && window.n();if (document.images){new Image().src=src;}\nif (!iesg){document.f&&document.f.q.focus();document.gbqf&&document.gbqf.q.focus();}\n}\n})();</script><textarea id=\"csi\" style=\"display:none\"></textarea><div id=\"mngb\"><div id=gbar><nobr><b class=gb1>Search</b> <a class=gb1 href=\"http://www.google.com/imghp?hl=en&tab=wi\">Images</a> <a class=gb1 href=\"http://maps.google.com/maps?hl=en&tab=wl\">Maps</a> <a class=gb1 href=\"https://play.google.com/?hl=en&tab=w8\">Play</a> <a class=gb1 href=\"http://www.youtube.com/?tab=w1\">YouTube</a> <a class=gb1 href=\"http://news.google.com/nwshp?hl=en&tab=wn\">News</a> <a class=gb1 href=\"https://mail.google.com/mail/?tab=wm\">Gmail</a> <a class=gb1 href=\"https://drive.google.com/?tab=wo\">Drive</a> <a class=gb1 style=\"text-decoration:none\" href=\"http://www.google.com/intl/en/options/\"><u>More</u> &raquo;</a></nobr></div><div id=guser width=100%><nobr><span id=gbn class=gbi></span><span id=gbf class=gbf></span><span id=gbe></span><a href=\"http://www.google.com/history/optout?hl=en\" class=gb4>Web History</a> | <a  href=\"/preferences?hl=en\" class=gb4>Settings</a> | <a target=_top id=gb_70 href=\"https://accounts.google.com/ServiceLogin?hl=en&continue=http://www.google.com/\" class=gb4>Sign in</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div></div><iframe name=\"wgjf\" style=\"display:none\"></iframe><center><br clear=\"all\" id=\"lgpd\"><div id=\"lga\"><img alt=\"Google\" height=\"95\" src=\"/intl/en_ALL/images/srpr/logo1w.png\" width=\"275\" id=\"hplogo\" onload=\"window.lol&&lol()\" style=\"padding:28px 0 14px\"><br><br></div><form action=\"/search\" name=\"f\"><table cellpadding=\"0\" cellspacing=\"0\"><tr valign=\"top\"><td width=\"25%\">&nbsp;</td><td align=\"center\" nowrap=\"nowrap\"><input name=\"ie\" value=\"ISO-8859-1\" type=\"hidden\"><input value=\"en\" name=\"hl\" type=\"hidden\"><input name=\"source\" type=\"hidden\" value=\"hp\"><div class=\"ds\" style=\"height:32px;margin:4px 0\"><input autocomplete=\"off\" class=\"lst\" value=\"\" title=\"Google Search\" maxlength=\"2048\" name=\"q\" size=\"57\" style=\"color:#000;margin:0;padding:5px 8px 0 6px;vertical-align:top\"></div><br style=\"line-height:0\"><span class=\"ds\"><span class=\"lsbb\"><input class=\"lsb\" value=\"Google Search\" name=\"btnG\" type=\"submit\"></span></span><span class=\"ds\"><span class=\"lsbb\"><input class=\"lsb\" value=\"I'm Feeling Lucky\" name=\"btnI\" type=\"submit\" onclick=\"if(this.form.q.value)this.checked=1; else top.location='/doodles/'\"></span></span></td><td class=\"fl sblc\" align=\"left\" nowrap=\"nowrap\" width=\"25%\"><a href=\"/advanced_search?hl=en&amp;authuser=0\">Advanced search</a><a href=\"/language_tools?hl=en&amp;authuser=0\">Language tools</a></td></tr></table><input type=\"hidden\" id=\"gbv\" name=\"gbv\" value=\"1\"></form><div id=\"gac_scont\"></div><div style=\"font-size:83%;min-height:3.5em\"><br></div><span id=\"footer\"><div style=\"font-size:10pt\"><div id=\"fll\" style=\"margin:19px auto;text-align:center\"><a href=\"/intl/en/ads/\">Advertising&nbsp;Programs</a><a href=\"/services/\">Business Solutions</a><a href=\"https://plus.google.com/116899029375914044550\" rel=\"publisher\">+Google</a><a href=\"/intl/en/about.html\">About Google</a></div></div><p style=\"color:#767676;font-size:8pt\">&copy; 2012 - <a href=\"/intl/en/policies/\">Privacy & Terms</a></p></span></center><div id=xjsd></div><div id=xjsi><script>if(google.y)google.y.first=[];(function(){var b;function c(a){window.setTimeout(function(){var d=document.createElement(\"script\");d.src=a;document.getElementById(\"xjsd\").appendChild(d)},0)}google.dljp=function(a){b=a;google.xjsi||(google.xjsu=a,c(b))};google.dlj=c;})();\nif(!google.xjs){google.dstr=[];google.rein=[];window._=window._||{};window._._DumpException=function(e){throw e};if(google.timers&&google.timers.load.t){google.timers.load.t.xjsls=new Date().getTime();}google.dljp('/xjs/_/js/hp/sb_he,pcc/rt\\x3dj/ver\\x3dib0Fx6HfNdE.en_US./d\\x3d1/sv\\x3d1/rs\\x3dAItRSTPs_I6l2vjvlHM5zh2mZbojMbgtiA');google.xjs=1;}google.pmc={sb:{\"agen\":false,\"cgen\":true,\"client\":\"heirloom-hp\",\"dh\":true,\"ds\":\"\",\"eqch\":true,\"fl\":true,\"host\":\"google.com\",\"jsonp\":true,\"msgs\":{\"lcky\":\"I\\u0026#39;m Feeling Lucky\",\"lml\":\"Learn more\",\"oskt\":\"Input tools\",\"psrc\":\"This search was removed from your \\u003Ca href=\\\"/history\\\"\\u003EWeb History\\u003C/a\\u003E\",\"psrl\":\"Remove\",\"sbit\":\"Search by image\",\"srch\":\"Google Search\"},\"ovr\":{\"l\":1,\"ms\":1},\"pq\":\"\",\"qcpw\":false,\"scd\":10,\"sce\":5,\"stok\":\"_cD49_HpGUcJ5FShNqb2HPd7cyY\"},hp:{},pcc:{}};google.y.first.push(function(){if(google.med){google.med('init');google.initHistory();google.med('history');}google.History&&google.History.initialize('/');google.hs&&google.hs.init&&google.hs.init()});if(google.j&&google.j.en&&google.j.xi){window.setTimeout(google.j.xi,0);}</script></div><script>(function(){var b,c,d,e;function g(a,f){a.removeEventListener?(a.removeEventListener(\"load\",f,!1),a.removeEventListener(\"error\",f,!1)):(a.detachEvent(\"onload\",f),a.detachEvent(\"onerror\",f))}function h(a){e=(new Date).getTime();++c;a=a||window.event;a=a.target||a.srcElement;g(a,h)}var i=document.getElementsByTagName(\"img\");b=i.length;\nfor(var j=c=0,k;j<b;++j)k=i[j],k.complete||\"string\"!=typeof k.src||!k.src?++c:k.addEventListener?(k.addEventListener(\"load\",h,!1),k.addEventListener(\"error\",h,!1)):(k.attachEvent(\"onload\",h),k.attachEvent(\"onerror\",h));d=b-c;\nfunction l(){if(google.timers.load.t){google.timers.load.t.ol=(new Date).getTime();google.timers.load.t.iml=e;google.kCSI.imc=c;google.kCSI.imn=b;google.kCSI.imp=d;void 0!==google.stt&&(google.kCSI.stt=google.stt);google.csiReport&&google.csiReport()}}window.addEventListener?window.addEventListener(\"load\",l,!1):window.attachEvent&&window.attachEvent(\"onload\",l);google.timers.load.t.prt=e=(new Date).getTime();})();\n</script></body></html>", { date: 'Sun, 27 Jan 2013 06:37:16 GMT',
  expires: '-1',
  'cache-control': 'private, max-age=0',
  'content-type': 'text/html; charset=ISO-8859-1',
  'set-cookie': 
   [ 'PREF=ID=64acdb01bb6414bd:FF=0:TM=1359268636:LM=1359268636:S=Xbza0PQd_o2cNH2J; expires=Tue, 27-Jan-2015 06:37:16 GMT; path=/; domain=.google.com',
     'NID=67=nrAXvKbIMvA3042X2mbM1yUOp_4-eu_KrIPl8vIsCX00UG2wFVulXJNXAE35p_PUT2H0z-A1GckcEFAY_qgIr0JrTsKBHsYDLWzNRosoLpBvvzdBrZfifXaGj_ddkZL9; expires=Mon, 29-Jul-2013 06:37:16 GMT; path=/; domain=.google.com; HttpOnly' ],
  p3p: 'CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info."',
  server: 'gws',
  'x-xss-protection': '1; mode=block',
  'x-frame-options': 'SAMEORIGIN',
  'transfer-encoding': 'chunked' });

However, using it will fail to match, and output the following:

AssertionError: Mocks not yet satisfied:
GET google.com://80:443/

You can recreate this problem in the tests. In the first test of test/test_intercept.js change the nock line:

  var scope = nock('www.google.com')

Note the dropping of the protocol, matching the code generated by the recorder. The tests will fail after this change though!

I will be submitting a pull request correcting this.

Mocking on proxying

Hy everyone. I'm building a proxy to forward some requests to a set of services.
The code I wrote is pretty simple and works just fine.

var nock = require('nock');
var http = require('http');
var httpProxy = require('http-proxy');

var config = {
  'devices':   'localhost:3000/devices',
  'types':     'localhost:3001/types',
  'locations': 'localhost:3002/locations'
}

var options = {
  router: {
    'localhost/devices':   config.devices,
    'localhost/types':     config.types,
    'localhost/locations': config.locations,
  }
}

var server = httpProxy.createServer(options).listen(8000);

The problem comes while trying to test it. I wanted to mock the http requests to the proxies services using nock, but somehow I'm not able to catch the requests. This is the code.

var nock    = require('nock');
var http    = require('http');
var request = require('request');

describe('when requires a resource', function() {

  var fixture = __dirname + '/fixtures/devices.json';
  var scope = nock('http://localhost:3000').get('/devices').replyWithFile('201', fixture);

  it('keeps status code', function(done) { 
    request('http://localhost:8000/devices', function(error, res, body) {
      expect(res.statusCode).toEqual('201');
      done();
    });
  });
});

As you can see I call the running proxy server, which in turn proxy the request to the correct service. The problem is that the proxiest request is executed, and so, I can't test it. Am I missing something?

a recording of a POST request has a duplicated request body if req.end(data) is called

When you record POST requests (or any other requests with request body), and use req.end(data) to send request data, the recording of the request will contain duplicated request body. For example, if the application calls req.end('ABC'), the recoding of the request body will be ABCABC and subsequently will not match the request during playback.

I believe the culprit is in https://github.com/flatiron/nock/blob/master/lib/recorder.js#L86-88. These three lines are redundant, as the subsequent call to oldEnd in https://github.com/flatiron/nock/blob/master/lib/recorder.js#L89 will eventually percolate to the overriden write method anyways.

Port Support

Are ports supported or should all mocks be on 80 by default?

I've noticed some issues on nock not intercepting on when you specify :port

Not working #filteringRequestBody

Hi,

I'm trying to use the #filteringRequestBody method but it doesn't work. Actually I tried both the function and regexp version without results. Here my code.

request = nock('http://mqtt.lelylan.com')
          .filteringRequestBody(/"nonce":"[^"]*/g, '"nonce":"30e7f667"')
          .matchHeader('X-Physical-Signature', signature)
          .put('/physicals/1', {'properties': {}, 'nonce': '30e7f667'})
          .reply(202);

I also tried with the function method as my regexp skills are a disaster, but nothing.

request = nock('http://mqtt.lelylan.com')
          .filteringRequestBody(function(path) {
             return {'properties': {}, 'nonce': '30e7f667'};
           })
          .matchHeader('X-Physical-Signature', signature)
          .put('/physicals/1', {'properties': {}, 'nonce': '30e7f667'})
          .reply(202);

What I want to do is to replace the nonce param (from JSON) to a fixed one. Unluckily it doesn't change and i get back the following message where I can see how the nonce is not updated.

Error: Nock: No match for HTTP request PUT /physicals/1 
{"properties":{},"nonce":"011dcb6d-7305-4e13-a2b4-e3e363a5a41b"}

Am I missing something?
Thanks a lot

Reply without body

It shouldn't be required to pass response body.

Example:

  var scope = nock('http://www.google.com')
     .post('/form')
     .reply(200);

Is there a way to skip asserting the request body?

I was working on the new jitsu tests (nock is fabulous for this btw), and I came across an interesting situation when trying to mock snapshot uploads here:

https://github.com/nodejitsu/jitsu/blob/flatiron-apps-tests/test/commands/apps-test.js#L99

What happens here is that jitsu tries to post a snapshot using content-type chunked, and for the life of me I can't figure out how to specify a request body in such a way that nock is cool with it.

You can see some of the errors I was getting here:

https://gist.github.com/39d0e24897c8d339722a

The easiest thing for me would be to tell nock to accept any request body, but if I can figure out how to make it handle a chunked body (copy-pasting the string-encoded .tgz didn't help) I'd be happy with that as well.

Any ideas?

Not able to test a POST request with or without body

I've been trying to do this:

claim_req = Nock("http://uri", allowUnmocked: true)
              .post("/claim", {some_data: "something"})
              .reply(200)

I expected it to be caught, but it didn't. So I did a little digging around.

The method called matchIndependentOfBody actually tries to match with the body. This line: https://github.com/flatiron/nock/blob/master/lib/scope.js#L155 does this: 'POST http://uri/claim:80 "{\"some_data\":\"something\"}"' === 'POST http://uri/claim:80' because this._key is already set with the body. Therefore the request is not intercepted.

Now if I remove the body, matchIndependentOfBody returns true, but later on, it doesn't match the interceptor because in another method named match (https://github.com/flatiron/nock/blob/master/lib/scope.js#L101) it tries to match again, but this time with the body (https://github.com/flatiron/nock/blob/master/lib/scope.js#L132)

With or without a body, it doesn't work. I've also tried what's in the protip section of the readme, but the same result happens.

Am I doing something wrong?

TypeError: Cannot call method 'listeners' of undefined in OverridenClientRequest

I'm trying a simple request, with nock catching, this seems to have broken recently am running Node v0.8.8.

Nock version: "0.13.3"
Request version: "2.11.0"

describe(".status", function() {
it("should fetch the device status", function(done) {
var scope = nock("http://example.com")
.get("/data_request?id=lu_sdata")
.replyWithFile(200, __dirname + "/fixtures/lu_sdata.json");

  client.getStatus(function(error, response, result) {
    expect(result.model).toBe("MiCasaVerde VeraLite");
    done();
  });
})

})

Client.prototype.getStatus = function(callback) {
var request_url = this.url + "/data_request?id=lu_sdata";
request({ url: request_url }, function (error, response, body) {
callback(error, response, JSON.parse(body));
});
};

/home/kevin/Source/javascript/vera/node_modules/request/main.js:517
if (response.connection.listeners('error').indexOf(self._parserErrorHandle
^
TypeError: Cannot call method 'listeners' of undefined
at OverridenClientRequest. (/home/kevin/Source/javascriptclient/node_modules/request/main.js:517:29)
at OverridenClientRequest.EventEmitter.emit (events.js:88:17)
at end (/home/kevin/Source/javascript/client/node_modules/nock/lib/request_overrider.js:206:11)
at process.startup.processNextTick.process._tickCallback (node.js:244:9)

Problem trying to intercept a request

Sorry if that is not the best to ask this.

But i am trying to use nock to mock the Shopify´s API, then i have some like this to get all blogs from a shop:

class Blog
  constructor: (key, pass, shop) ->
    throw new Error 'Blog missing parameters' if not pass? or not key? or not shop?
    @options =
      host: "http://#{key}:#{pass}@#{shop}.myshopify.com"
      port: 80
      method: "GET"
      path: '/admin/blogs.json'

  all: (cb) ->
    req = request @options, (res) ->
      response = ""
      res.setEncoding('utf8')
      res.on 'data', (data) ->
        response += data
      res.on 'end', ->
        error = new Error 'Request Error #{res.statusCode}' unless res.statusCode is 200
        process.nextTick ->
          cb(error, JSON.parse(response))
    req.end()

And parts of my testing they are some like this:

describe 'Receive a list of all Blogs', ->
    before ->
      @fixture = loadFixture 'blog/blogs'
      @api = nock "https://#{KEY}:#{PASSWORD}@#{STORE}.myshopify.com"
      @blog = new Blog KEY, PASSWORD, STORE


    it 'should be possible get a list of all blog like an Array', (done) ->
      @api.get('/admin/blogs.json').reply(200, @fixture)
      @blog.all (err, blogs) =>
        @api.done()
        blogs.should.be.an.instanceof Object
        done()

Sorry but i dont know what is wrong in my code, because allways get the response from the real server. But i need that my calls they are intercepting by nock to response my @fixture arrays of blogs.

Again sorry for ask that here.

Trying to get it working with restify's clients

Can't seem to get it working with restify's various clients, but mainly the json client. Below test shows the nock stub working fine with a plain http.request call, but in the second attempt -- using the same url/port and method -- the test hangs when making the request..

 http = require('http')
 nock = require 'nock'
 restify = require 'restify'

describe "Test mocks using nock", ->

it "should hit the mock when doing a POST to google.com using plain http.request", (done) ->
  nock.cleanAll()
  # nock.recorder.rec()

scope = nock('http://www.google.com:1234')
  .post('/')
  .reply 200, 
    hello:'hello world'
    id: 1234

req = http.request 
  host: 'www.google.com'
  method: 'POST'
  path: '/'
  port: 1234
, (res) ->
  res.on 'data', (chunk) ->
    console.log 'data1: ' + chunk
  res.on 'end', () ->
    scope.done()
    done()

req.end()

it "should hit the mock when doing a POST to google.com using restify json client", (done) ->
 # nock.recorder.rec()
 nock.cleanAll()

scope = nock('http://www.google.com:1234/')
  .defaultReplyHeaders 
    'Content-Type':'application/json'
  .post('/')
  .reply 200, { hello: 'hello universe' }

api_client = restify.createJsonClient
  url: 'http://www.google.com:1234'
  version: '*'

api_client.post '/', (err,req,res,data) ->
  console.log 'data: '+ data
  scope.done()
  done()

console output:

Test mocks using nock
◦ should hit the mock when doing a POST to google.com using plain http.request: data1:      {"hello":"hello world","id":1234}
✓ should hit the mock when doing a POST to google.com using plain http.request 
1) should hit the mock when doing a POST to google.com using restify json client


 ✖ 1 of 2 tests failed:

 1) Test mocks using nock should hit the mock when doing a POST to google.com using restify json client:
 Error: timeout of 4000ms exceeded
  at Object.<anonymous> (/usr/local/lib/node_modules/mocha/lib/runnable.js:158:14)
  at Timer.list.ontimeout (timers.js:101:19)

Accept string as http request target

The Issue

Http allows to simply give the string of a URL instead of providing host, hostname, etc. separately. It would be great if you could add support for this with nock.

The Test

tap.test("accept string as request target", function(t) {
  var scope = nock('http://www.example.com')
    .get('/')
    .reply(200, "Hello World!");
  var req = http.get('http://www.example.com', function(res) {
    t.equal(res.statusCode, 200);
    res.on('end', function() {
      t.ok(dataCalled);
      scope.done();
      t.end();
    });
    res.on('data', function(data) {
      dataCalled = true;
      t.ok(data instanceof Buffer, "data should be buffer");
      t.equal(data.toString(), "Hello World!", "response should match");
    });
  });
  req.end();
});

https support

It would be really helpful if https requests could be mocked too. Thanks for creating this amazing package.

allowUnmocked + POST requests not working

nock('https://httpbin.org', { allowUnmocked: true }).get("/abc").reply(200, "Hey!");

var options = {
  method: 'post',
  uri: 'https://httpbin.org/post',
  json: { some: 'data' }
};
request(options, function(err, resp, body) {
  console.log(resp.statusCode, body);
});

The console.log is never run. The same code works if you do any of the following:

  • remove allowUnmocked: true
  • change the request to a 'get' on '/' with no body

Error: Nock: No match for HTTP request (should be matching)

Hey guys, I'm having trouble with matched requests. I'm seeing Error: Nock: No match for HTTP request GET /1.1/users/lookup.json?user_id=&screen_name=lefnire, yet that exact URL is nocked in my code. It's weird because using .log(console.log) I see the Error: Nock: No matches, then at the end see AssertionError: Mocks not yet satisfied with all the previous no-matches verbatim.

Here's my code (stripped everything not relevant), and here's my error.

Head Method

            ^
TypeError: Object #<Object> has no method 'head'

Any reason not to support head?

POST data with different ordered keys

e.g. this fails.

nock('http://wtfjs.org')
  .post('/like-wtf', {
    foo: 'bar',
    bar: 'foo'
  });

request({
  uri: http://wtfjs.org/like-wtf,
  body: {
    bar: 'foo',
    foo: 'bar'
}, function () {

})

ENOTFOUND, Domain name not found

I ran into a problem while trying to use Nock in an existing node.js script that sends a request to a local node server.

I have reduced the problem to the code below. My server is running on localhost:5000.

Executing this will work:

http = require 'http'

options = 
  host: '127.0.0.1',
  port: 5000,
  path: '/'

http.get options, (error, reply) ->
  console.log error
  console.log reply

But from the moment I require Nock, an error raises:

http = require 'http'
nock = require 'nock'

options = 
  host: '127.0.0.1',
  port: 5000,
  path: '/'

http.get options, (error, reply) ->
  console.log error
  console.log reply

events.js:45
throw arguments[1]; // Unhandled 'error' event
^
Error: ENOTFOUND, Domain name not found
at IOWatcher.callback (dns.js:74:15)

Potential issue with nock recorder

When simply calling

nock = require 'nock'
nock.recorder.rec()

When I run my tests I get:

TypeError: Cannot call method 'apply' of undefined

This is line 64 of recorder.js and is due to the callback function being undefined.

This is a strange error, has anyone seen it before?

Nock + Request + replyWithFile

I'm having an issue mocking a request using Request (https://github.com/mikeal/request/) where I'm making multiple chained requests

scope = nock('http://localhost:3000)
  .get('/')
  .replyWithFile(__dirname + '../support/example.html')
  .get('/')
  .replyWithFile(__dirname + '../support/example2.html')

request url ->
  request url ->

The second request won't get processed for some reason I have yet to figure out. Changing replyWithFile with reply and using readFile produces the correct results, so perhaps it has something to do with streaming? I will try to reproduce it separately.

allowUnmocked + https not working

nock = require 'nock'
request = require 'request'

scope = nock('https://google.com', { allowUnmocked: true }).get("/abc").reply(200, "Hey!")

request { uri: 'https://google.com/' }, (err, resp, body) ->
  console.log resp.statusCode

Output:

Error: Nock: No match for HTTP request GET / 

the code works if you use http instead of https!

[nock] Repeating URIS

Sometimes a request will give different answers according to if that happened before.

e.g.

PUT NUNO foo -> OK
PUT NUNO BAR -> CONFLICT

OR

GET DB bar -> 404
PUT DB bar -> 201
GET DB bar -> 200

It should be possible to express this is nock. For the first request answer foo, for the second answer bar.

Real life example of code I hate to deprecate here: apache/nano@bb3f563

By the way watching a movie about the doors. Damn I'm tired :)

Stream file on `replyWithFile`

It would be more efficient if instead of buffering the entire file contents in sync, nock would stream the file through response.

Response could be truly pipeable. Currently it only emits one data event.

Support for http.ClientRequest#setHeader, #getHeader, #removeHeader

I'm developing a library to consume an HTTP API. I'm seeing the following error since adding an edge case that requires the usage of http.ClientRequest#setHeader:

     TypeError: Object #<EventEmitter> has no method 'setHeader'
      at [object Object].apiCall (/Users/james/Projects/urban-airship/lib/reques
t.js:112:9)      at [object Object].get (/Users/james/Projects/urban-airship/lib/request.js
:129:15)

The lines in question:

  if (typeof data !== 'undefined') {
    encodedData = JSON.stringify(data);

    // THE NEXT TWO ARE THE OFFENDING LINES
    req.setHeader('content-type', 'application/json');
    req.setHeader('content-length', encodedData.length);

    req.end(encodedData, 'utf8');
  } else {
    req.setHeader('content-length', 0);
    req.end();
  }

Where req is an instance of http.ClientRequest.

> 1 nock scopes, global intercept problems

I noticed that in my tests using nock, if one test failed to fulfill an expectation, subsequent tests that set up the same expectation would fail, because the requests were being hijacked by a lingering intercept from the previous test. I wrote a test that demonstrates:

tap.test("scopes are independent", function(t) {
  var scope1 = nock('http://www.google.com')
    .get('/')
    .reply(200, "Hello World!");
  var scope2 = nock('http://www.google.com')
    .get('/')
    .reply(200, "Hello World!");

  var req = http.request({
      host: "www.google.com"
    , path: '/'
    , port: 80
  }, function(res) {
    res.on('end', function() {
      t.ok(scope1.isDone());
      t.ok(scope2.isDone()); // fails
      t.end();
    });
  });

  req.end();
});

What's your preferred way to fix this?

  • One intercepted request could fulfill expectations from multiple scopes (in other words, the above test would pass without modification).

  • nock.restore() could clear out whatever is hanging around to cause this. Test would change to something like this:

    tap.test("scopes are independent", function(t) {
    var scope1 = nock(/*...*/);
    nock.restore();
    var scope2 = nock(/*...*/);
    
    var req = http.request({
      //...
    }, function(res) {
      res.on('end', function() {
        t.notOk(scope1.isDone());
        t.ok(scope2.isDone());
        t.end();
      });
    });
    req.end();
    });
  • Similar to the previous option, but give the duty to a new method rather than restore.

[recorder] Body is being appended

See

$ node tests/att/get.js 

<<<<<<-- cut here -->>>>>>

nock('nodejsbug.iriscouch.com')
  .put('/v061_att_gea')
  .reply(201, "{\"ok\":true}\n", { server: 'CouchDB/1.1.1 (Erlang OTP/R14B04)',
  location: 'http://nodejsbug.iriscouch.com/v061_att_gea',
  date: 'Thu, 01 Dec 2011 21:39:15 GMT',
  'content-type': 'application/json',
  'content-length': '12',
  'cache-control': 'must-revalidate' });

<<<<<<-- cut here -->>>>>>


<<<<<<-- cut here -->>>>>>

nock('nodejsbug.iriscouch.com')
  .put('/v061_att_gea/new/att, "\"Hello\""')
  .reply(201, "{\"ok\":true,\"id\":\"new\",\"rev\":\"1-5142a2e74e1ec33e6e5b621418210283\"}\n", { server: 'CouchDB/1.1.1 (Erlang OTP/R14B04)',
  location: 'http://nodejsbug.iriscouch.com/v061_att_gea/new/att',
  etag: '"1-5142a2e74e1ec33e6e5b621418210283"',
  date: 'Thu, 01 Dec 2011 21:39:16 GMT',
  'content-type': 'text/plain;charset=utf-8',
  'content-length': '66',
  'cache-control': 'must-revalidate' });

<<<<<<-- cut here -->>>>>>


<<<<<<-- cut here -->>>>>>

nock('nodejsbug.iriscouch.com')
  .put('/v061_att_gea/new/att?rev=1-5142a2e74e1ec33e6e5b621418210283, "\"Hello\"BM:\u0000\u0000\u0000\u0000\u0000\u0000\u00006\u0000\u0000\u0000(\u0000\u0000\u0000\u0001\u0000\u0000\u0000????\u0001\u0000\u0018\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0013\u000b\u0000\u0000\u0013\u000b\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000Zm?\u0000"')
  .reply(201, "{\"ok\":true,\"id\":\"new\",\"rev\":\"2-3b1f88c637fde74a486cf3ce5558b47e\"}\n", { server: 'CouchDB/1.1.1 (Erlang OTP/R14B04)',
  location: 'http://nodejsbug.iriscouch.com/v061_att_gea/new/att',
  etag: '"2-3b1f88c637fde74a486cf3ce5558b47e"',
  date: 'Thu, 01 Dec 2011 21:39:16 GMT',
  'content-type': 'text/plain;charset=utf-8',
  'content-length': '66',
  'cache-control': 'must-revalidate' });

<<<<<<-- cut here -->>>>>>


<<<<<<-- cut here -->>>>>>

nock('nodejsbug.iriscouch.com')
  .get('/v061_att_gea/new/att?rev=2-3b1f88c637fde74a486cf3ce5558b47e, "\"Hello\"BM:\u0000\u0000\u0000\u0000\u0000\u0000\u00006\u0000\u0000\u0000(\u0000\u0000\u0000\u0001\u0000\u0000\u0000????\u0001\u0000\u0018\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0013\u000b\u0000\u0000\u0013\u000b\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000Zm?\u0000"')
  .reply(200, "BM:\u0000\u0000\u0000\u0000\u0000\u0000\u00006\u0000\u0000\u0000(\u0000\u0000\u0000\u0001\u0000\u0000\u0000ÿÿÿÿ\u0001\u0000\u0018\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0013\u000b\u0000\u0000\u0013\u000b\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000Zm�\u0000", { server: 'CouchDB/1.1.1 (Erlang OTP/R14B04)',
  etag: '"2-3b1f88c637fde74a486cf3ce5558b47e"',
  date: 'Thu, 01 Dec 2011 21:39:17 GMT',
  'content-type': 'image/bmp',
  'content-md5': 'Ow9j2dR0Qm58Qi3z8p2w3A==',
  'content-length': '58',
  'cache-control': 'must-revalidate',
  'accept-ranges': 'bytes' });

<<<<<<-- cut here -->>>>>>

# /Users/dscape/Desktop/dev/nano/tests/att/get.js
ok 1 (unnamed assert)
ok 2 should be equal

1..2
# tests 2
# pass  2

# ok

<<<<<<-- cut here -->>>>>>

nock('nodejsbug.iriscouch.com')
  .delete('/v061_att_gea, "\"Hello\"BM:\u0000\u0000\u0000\u0000\u0000\u0000\u00006\u0000\u0000\u0000(\u0000\u0000\u0000\u0001\u0000\u0000\u0000????\u0001\u0000\u0018\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0013\u000b\u0000\u0000\u0013\u000b\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000Zm?\u0000"')
  .reply(200, "{\"ok\":true}\n", { server: 'CouchDB/1.1.1 (Erlang OTP/R14B04)',
  date: 'Thu, 01 Dec 2011 21:39:17 GMT',
  'content-type': 'application/json',
  'content-length': '12',
  'cache-control': 'must-revalidate' });

<<<<<<-- cut here -->>>>>>

Strange huh? Body is always appended and always appears even in get requests.

Persisting scopes don't actually persist unless defined twice

For example I have 3 calls to the Twitter API for verify_credentials.json

nock('https://api.twitter.com')
  .persist()
  .get('/1/account/verify_credentials.json')
  .replyWithFile(200, __dirname + '/twitter.json');

With the above the first call is correctly intercepted and the other 2 are not intercepted. But if I add the call a second time like so

nock('https://api.twitter.com')
  .persist()
  .get('/1/account/verify_credentials.json')
  .replyWithFile(200, __dirname + '/twitter.json')
  .get('/1/account/verify_credentials.json')
  .replyWithFile(200, __dirname + '/twitter.json');

Then all 3 calls will be correctly intercepted.

Allow mocking at a lower level than json

I would like to use the verbatim output of curl, including all the headers, store that in a file and use it to mock a request. Right now it seems that this is not possible (I might be mistaken)

The reason for this is that I am doing a lot of API, Oauth2 work that requires certain content present in header fields, and the only way to correctly test this is to use the exact output from curl (everything else seems to be prone to error).

Cannot set property headerSent

When using node v0.4.12 with nock and jasmine-node, I receive the following error:

Stacktrace:
TypeError: Cannot set property headerSent of # which has only a getter
at new OverridenClientRequest (./node_modules/nock/lib/intercept.js:83:22)
at Object.request (./node_modules/nock/lib/intercept.js:138:15)

The single commit in my fork, which checks hasOwnProperty() in OverridenClientRequest, fixes this.

getaddrinfo() error on osx

When I use nock in OSX (10.8.1 w/ macports), it does not work & i get this error message when using an https host :

 {"code":"ENOTFOUND","errno":"ENOTFOUND","syscall":"getaddrinfo"}
  • nock is 0.13.4
  • node is 0.8.8

Any idea?

isDone() working with mikeal/request?

I'm using mikeal/request with nock, and running this sample code doesn't pass:

nock = require 'nock'
request = require 'request'
expect = require('chai').expect

describe 'hello', ->
it 'hello', (done) ->
n = nock('https://hello.dev')
.post('/world')
.reply(200, 'hello world')
m = request.post {url: 'https://hello.dev/world'}, (err, res, body) ->
console.log body
done()
expect(n.isDone()).to.equal(true)

However, when I use https or http, it passes. Can anyone else reproduce this, or is this just an issue on my end?

Also want to add that if I change the expect to equal false, it does pass and "hello world" is indeed printed so nock does seem to catch the request, but it's still flagged as not done.

EDIT: It seems that the isDone() is running before it finishes maybe, which is weird since http works so I'll have to look into this a little more.

query strings and ordering

I'm having some trouble with query strings. I wish to intercept a path like '/path/load' which can accept multiple parameters, like '?b=123&a=456'. The trouble is that "a" "b" could be in any order (since it'd still be a valid URL). Is there some way I could do parameter matching like the POST request using an object map instead of constructing this string?

Idea: Auto nock

Simply require nock and all should work.

Nock could save tests on the first run to the file system. Then replay wouldn't be done cause it was nocked.

A way to invalidate bad results would be necessary, so max a user would do is

nock.rewind();

Any plans for VCR-like automatic recording and playback?

I see that nock lets you record http responses, but it looks like the feature is more so that you can fill in the tests yourself. Is there any support for more VCR-like functionality for recording and playing back HTTP requests and responses?

var nock = require('nock')
  , request = require('request')
  , assert = require('assert')
  ;


describe('foo', function () {
  it('requests some data and does some stuff', function (done) {
    // load the recorded request from the filename
    // or save it there if it doesn't exist
    nock.vrc('vcr-filename', function () {
      request.get('http://example.com', function (err, response, body) { assert(body == 'HURP DURP'); done(); });
    });
  });
});

It would eliminate the step of having to manually go in and fill out the mock http requests in each of your tests, and would make it much easier to drop in to an existing project.

matchHeader and multiple headers don't to play well together.

Some headers can be set multiple times on a request, I believe node supports this by allowing header values to be arrays.

Unless there's an undocumented feature I'm missing matchHeader won't work because of this snippet:

      var checkHeaders = function(header) {
        return options.getHeader(header.name) === header.value;
      };

I think changing it to the following should be sufficient:

      var checkHeaders = function(header) {
        var value = options.getHeader(header.name)
        if (Array.isArray(value)) {
          return value.indexOf(header.value) != -1
        } else {
          return value == header.value
        }
      };

Thoughts?

Can't stub out host when requesting multiple urls for that host

When you stub out a host, any legitimate requests to that host (regardless of the path matching the actual stub or not) cause errors because no matching interception exists.

A quick example:

nock("http://localhost").get("/some/stubbed/path").reply(200, message)
request.get "http://localhost/", (err, res, body) ->
  # blah blah blah

This would throw the following error:

Error: Nock: No match for HTTP request GET / undefined

The problem is that the keys in the allInterceptors array done by host and a match triggers the processRequest function which errors out when no match exists.

I made a simple workaround here: 9330dd26e393ee52dec9c094099cc72e4915fb7c

Unfortunately it's not really complete and causes the tests to fail. A fix would be to check for a complete match before processing the request, but I can't find an easy way to do that. If I can get a better idea of what would be required, I would be happy to put together a pull request when I have some time.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.