Nock
Nock is an HTTP 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.
This does NOT work with Browserify, only node.js
Install
$ npm install nock
Use
On your test, you can setup your mocking object like this:
var nock = require('nock');
var couchdb = nock('http://myapp.iriscouch.com')
.get('/users/1')
.reply(200, {
_id: '123ABC',
_rev: '946B7D1C',
username: 'pgte',
email: 'pedro.teixeira@gmail.com'
});
This setup says that we will intercept every HTTP call to http://myapp.iriscouch.com
.
It will intercept an HTTP GET request to '/users/1' and reply with a status 200, and the body will contain a user representation in JSON.
Then the test can call the module, and the module will do the HTTP requests.
READ THIS
When you setup an interceptor for an 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.
Specifying request body
You can specify the request body to be matched as the second argument to the get
, post
, put
or delete
specifications like this:
var scope = nock('http://myapp.iriscouch.com')
.post('/users', {
username: 'pgte',
email: 'pedro.teixeira@gmail.com'
})
.reply(201, {
ok: true,
id: '123ABC',
rev: '946B7D1C'
});
The request body can be a string or a JSON object.
Specifying replies
You can specify the return status code for a path on the first argument of reply like this:
var scope = nock('http://myapp.iriscouch.com')
.get('/users/1')
.reply(404);
You can also specify the reply body as a string:
var scope = nock('http://www.google.com')
.get('/')
.reply(200, 'Hello from Google!');
or as a JSON-encoded object:
var scope = nock('http://myapp.iriscouch.com')
.get('/')
.reply(200, {
username: 'pgte',
email: 'pedro.teixeira@gmail.com',
_id: '4324243fsd'
});
or even as a file:
var scope = nock('http://myapp.iriscouch.com')
.get('/')
.replyWithFile(200, __dirname + '/replies/user.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:
var scope = nock('http://www.google.com')
.filteringRequestBody(/.*/, '*')
.post('/echo', '*')
.reply(201, function(uri, requestBody) {
return requestBody;
});
A Stream works too:
var scope = nock('http://www.google.com')
.get('/cat-poems')
.reply(200, function(uri, requestBody) {
return fs.createReadStream('cat-poems.txt');
});
You can specify the reply headers like this:
var scope = nock('http://www.headdy.com')
.get('/')
.reply(200, 'Hello World!', {
'X-My-Headers': 'My Header value'
});
You can also specify default reply headers for all responses like this:
var scope = nock('http://www.headdy.com')
.defaultReplyHeaders({
'X-Powered-By': 'Rails',
'Content-Type': 'application/json'
})
.get('/')
.reply(200, 'The default headers should come too');
HTTP Verbs
Nock supports any HTTP verb, and it has convenience methods for the GET, POST, PUT, HEAD, DELETE, PATCH and MERGE HTTP verbs.
You can intercept any HTTP verb using .intercept(path, verb [, requestBody [, options]])
:
scope('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:
var scope = nock('https://secure.my.server.com')
Non-standard ports
You are able to specify a non-standard port like this:
var 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.
nock('http://zombo.com').get('/').times(4).reply(200, 'Ok');
http.get('http://zombo.com/');
http.get('http://zombo.com/');
http.get('http://zombo.com/');
http.get('http://zombo.com/');
http.get('http://zombo.com/');
Sugar sintaxe
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');
Delay the response
You are able to specify the number of milliseconds that your reply should be delayed.
nock('http://my.server.com')
.get('/')
.delay(2000)
.reply(200, '<html></html>')
NOTE: the 'response'
event will occur immediately, but the IncomingMessage not emit it's 'end'
event until after the delay.
Chaining
You can chain behaviour like this:
var scope = nock('http://myapp.iriscouch.com')
.get('/users/1')
.reply(404)
.post('/users', {
username: 'pgte',
email: 'pedro.teixeira@gmail.com'
})
.reply(201, {
ok: true,
id: '123ABC',
rev: '946B7D1C'
})
.get('/users/123ABC')
.reply(200, {
_id: '123ABC',
_rev: '946B7D1C',
username: 'pgte',
email: 'pedro.teixeira@gmail.com'
});
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:
var 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:
var scope = nock('http://api.myservice.com')
.filteringPath(function(path) {
return '/ABC';
})
.get('/ABC')
.reply(200, 'user');
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:
var 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:
var scope = nock('http://api.myservice.com')
.filteringRequestBody(function(path) {
return 'ABC';
})
.post('/', 'ABC')
.reply(201, 'OK');
If you need to match requests only if certain request headers match, you can.
var scope = nock('http://api.myservice.com')
.matchHeader('accept', 'application/json')
.get('/')
.reply(200, {
data: 'hello world'
})
You can also use a regexp for the header body.
var scope = nock('http://api.myservice.com')
.matchHeader('User-Agent', /Mozilla\/.*/)
.get('/')
.reply(200, {
data: 'hello world'
})
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:
options = {allowUnmocked: true};
var scope = nock('http://my.existing.service.com', options)
.get('/my/url')
.reply(200, 'OK!');
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:
var google = nock('http://google.com')
.get('/')
.reply(200, 'Hello from Google!');
setTimeout(function() {
google.done();
}, 5000);
.isDone()
You can also call isDone()
, which will return a boolean saying if all the expectations are met or not (instead of throwing an exception);
.cleanAll()
You can cleanup all the prepared mocks (could be useful to cleanup some state after a failed test) like this:
nock.cleanAll();
.persist()
You can make all the interceptors for a scope persist by calling .persist()
on it:
var scope = nock('http://persisssists.con')
.persist()
.get('/')
.reply(200, 'Persisting all the way');
pendingMocks
If a scope is not done, you can inspect the scope to infer which ones are still pending using the scope.pendingMocks
property:
if (!scope.isDone()) {
console.error('pending mocks: %j', scope.pendingMocks);
}
Logging
Nock can log matches if you pass in a log function like this:
var google = nock('http://google.com')
.log(console.log)
...
Restoring
You can restore the HTTP interceptor to the normal unmocked behaviour by calling:
nock.restore();
Turning Nock Off (experimental!)
You can bypass Nock completely by setting 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 request
As default, if you do not mock a host, a real HTTP request will do, but sometimes you should not permit real HTTP request, so...
For disable real http request.
nock.disableNetConnect();
So, if you try to request any host not 'nocked', it will thrown an NetConnectNotAllowedError.
nock.disableNetConnect();
http.get('http://google.com/');
For enabled real HTTP requests.
nock.enableNetConnect();
You could restrict real HTTP request...
nock.enableNetConnect('amazon.com');
nock.enableNetConnect(/(amazon|github).com/);
http.get('http://www.amazon.com/');
http.get('http://github.com/');
http.get('http://google.com/');
Recording
This is a cool feature:
Guessing what the HTTP calls are is a mess, specially 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();
If you just want to capture the generated code into a var as an array you can use:
nock.recorder.rec({
dont_print: true
});
var nockCalls = nock.recorder.play();
The nockCalls
var will contain an array of strings representing the generated code you need.
Copy and paste that code into your tests, customize at will, and you're done!
(Remember that you should do this one test at a time).
In case you want to generate the code yourself or pass the test data in some other way, you can pass the output_objects
option to rec
:
nock.recorder.rec({
output_objects: true
});
var nockCallObjects = nock.recorder.play();
The returned call objects have the following properties:
scope
- the scope of the call (e.g. https://github.com
)
port
- the port of the call (e.g. 80
, 443
or undefined
if the HTTP request didn't have options.port
defined)
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
reply
- the HTTP status as string of the reply (e.g. '200'
)
response
- the body of the reply
headers
- the headers of the reply
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.
PROTIP
If you don't want to match the request body you can use this trick (by @theycallmeswift):
var scope = nock('http://api.myservice.com')
.filteringRequestBody(function(path) {
return '*';
})
.post('/some_uri', '*')
.reply(200, 'OK');
License
(The MIT License)
Copyright (c) 2011 Pedro Teixeira. http://about.me/pedroteixeira
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.