Unit-test helper for network request assertions, based on Mikael's request and should.js of visionmedia, designed for mocha-ui-exports
npm install mocha-ui-exports-requestmocha-ui-exports-request 
=========================
Brief
------
Use a DECLEARATIVE language for your HTTP request assertions,
and get DESCRIPTIVE spec output from your mocha tests.
This is a unit-test helper for network request assertions,
based on Mikael's request and should.js of visionmedia,
designed for the mocha-ui-exports plugin for mocha test framework.
Content
-------
- mocha-ui-exports-request
- Brief
- Content
- Use Case
- Install
- Test
- API
- request
- RequestTester#responds
- Contribute
- Future
- Lisence
Use Case
---------
Assume you have a cool http server that you want covered in BDD unit-tests,
and you want:
* high resolution DESCRIPTIVE test results
* generated by a DECLARATIVE language
Prequisites - you're programming in node,
and using mocha as test framework.
Example
Lets just take for example - a notes application, that uses couch-db.
To make the example short, lets just assume 2 APIs:
* homepage - an HTML page
* list-notes - an AJAX call
* post-note - an AJAX call
You want DESCRIPTIVE test results that follow the BDD principal that makes it
as readable as specifications.
One that looks, for example, like that:
``
./lib/server.js
homepage - /
with no parameters
√ should return status 200
√ should emit http-header: 'content-type' as text/html
√ body should match : /Your notes<\/h1>/
ajax - /ajax/listnotes
with no parameters - should return the 5 latest notes
√ should return status 200
√ should emit http-header: 'content-type' as text/json
√ body should be : '{"notes":{"date":1396257...'
with 'to' - should return the next page in fixture
√ should return status 200
√ should emit http-header: 'content-type' as text/json
√ body should be : '{"notes":[{"date":1396257...'
with 'to' and 'from' - should return the cut
√ should return status 200
√ should emit http-header: 'content-type' as text/json
√ body should be : '{"notes":[{"date":1396257...'
ajax - /ajax/postnote
with valid form - should accept the note
√ should return status 200
all expected couch-db hits
√ should have been called
14 passing (81ms)
`
But you want to express your mocha tests in a DECLERATIVE way.
Declarative - means:
- avoid flow-control
- avoid logic
- stick with declaration
- stick with descriptions
No ifs, no elses, no loops, and as few add-hock custom callbacks as possible.
Ah, assume you want to cover the back-end requests too, and use for that purpose, for example [nock.
How would you feel if I tould you that you can do it this way:
`
// the test target
var svr = require('../server')
//the helper
, request = require('mocha-ui-exports-request')
//plugs the recorded scenario of http requests to couch
, nock = require('fixtures/nock')
//System Under Test
, SUT = "http://127.0.0.1:1234"
;
svr.listen(1234);
module.exports =
{ "./lib/server.js" :
{ "homepage - /" :
{ "with no parameters" :
request( SUT + "/")
.responds(
{ status: 200
, headers:
{ "content-type" : "text/html"
}
, body: /
`
I want to use the standard BDD mocha UI
----------
Ok. here:
(since 1.1.0)
If you set to --ui bdd, or if your --ui switch is not providedtest/mocha.opts
(not in your CLI command, nor in your file).responds({..})
then will use the describe(..) and it(..) APIs for you,
loading the generated tests with the generated descriptive titles to the tests tree.
In this case, the .responds({..}) will return a context object instead of the it(..)
suite that the mocha-ui-exports plugin expects.
The context object is described right after the snippet, and can be used to writing tests using headers:
instead of providing , responseBody:, or and: blocks.
`
var request = require('mocha-ui-exports-request')
, SUT = 'http://localhost:4321'
;
describe('/my-path', function() {
describe('called with no parameters', function() {
var ctx = request(SUT + '/my-path')
.responds({
status: 200,
});
it('should foo', function() {
ctx.res.should.be...
})
});
})
`
This will registger all the test handlers using describe, before, it and after, using the same titles and structure.
If for some reason you're using hybrid UI and your --ui switch is set to bdd
any value that is not this behavior will not trigger automatically, however, you can .bddCtx()
still trigger this behavior manually by calling .
``
suite('/my-path', function() {
suite('called with bad parameter value', function() {
var ctx = request(SUT + '/my-path?param=bad')
.responds({
status: 400
}).bddCtx()
test('should ...' )
})
})
The returned ctx object has:ctx.err
- - when the http-request fails abruptly for networking issues (dns, network).ctx.res
mind that if you have ANY statusCode - it means there was no error, and a response
object is passed.
- - the response object (http.IncomingMessage)ctx.body
- - the body on the passed response object, as extracted by the request/request
package.
Install
--------
``
npm install mocha-ui-exports-request
ok, long name. I will accept better offers. But until then... :P
Test
----
the published package is slim: it does not contain the test suite.
You'll have to clone this repo, to npm install from within the cloned folder, and then to npm test.
API
======
request
-------
request(reqOptions) : RequestTesterreqOptions
It builds a requet-tester for the provided .
reqOptions - any options setting valid for mikael's request module, including form, post-data, multiplart, and whatever you want.
Starting from 1.0.1 - it could be a handler that returns such options settings.
RequestTester - implements one method: RequestTester#responds, that returns a mocha-ui-exports suite, who's setup function will fire the request, and hold it in context closure for all the asserts that follow.
RequestTester#Responds
----------------------
reqTester#responds(options) : suite
It builds an asserter for every one of the options provided.
Supported options:
assert a status code. generates "should return status ..." asserts.
Example:
``
module.exports = {
'/my/resource' :
request( 'http://my-sut-server.com/my/resource' )
.responds( {
status: 200
})
}
becomes:
``
/my/resource
√ should return status 200
asserts against headers in the headers collection. generates "should emit http-header: 'xxx' as yyy" asserts
keys are expected HTTP-headers, values can be strings to compare to, or RegExps to match with.
Example:
``
module.exports = module.exports = {
'/my path' :
request( 'http://my-sut-server.com/my-path' )
.responds( {
headers: {
'x-powered-by' : 'my cool server'
etag : /.*/
}
})
}
becomes:
`
/my-path
√ should emit http-header: 'x-powered-by' as 'my cool server'
√ should emit http-header: 'etag' as /.*/
`
$3
For add-hock assertions that should be performed against the entire headers collection,
and cannot be expressed as descrete assertions against a single http header.
The test titles in this case are your responsibility. Your tests will be grouped under 'response headers' section
The value of this entry should be an object where every key is a test-tile, and every value
is a function that accepts the response.headers collection and performs add-hock assertions against it.
Example:
``
module.exports = module.exports = {
'/my path' :
request( 'http://my-sut-server.com/my-path' )
.responds( {
responseHeaders: {
'must contain either x-powered-by or x-server-type' : function(headers) {
Should( headers['x-powered-by'] || headers['x-server-type']).be.ok
}
}
})
}
becomes:
`
/my-path
response headers
√ must contain either x-powered-by or x-server-type
`
an array of body asserts. Assertions may be:
- string - asserts that the body is equal to the given string value, under title like : "body should be : {your assertion value}".RegExp
When the given value is longer than 20 chars the remnant is cut and appended with ellipis (...).
- - asserts that the body matches the given RegExp value, under title like "body should match: ..."object
- - this is a suite object, where every key is a test tile, and every value is a test-funciton that expects the body, Array
and lets you write whatever add-hock tests you need. In this case, the test titles are your responsibility.
- - a combination of any of the above.`
Example:`
module.exports = module.exports = {
'/my path' :
request( 'http://my-sut-server.com/index.html' )
.responds( {
body: [
/Hello Anonymous
/,
/Our Catalog
/,
{ 'should contain 3 promoted products' : function(body) {
var n = 0;
body.replace(/class="promotedProduct"/, function() { n++ } );
n.should.eql(3)
}
}
]
})
}
becomes:
``
/my-path
√ body should match: /Hello Anonymous
/
√ body should match: /Our Catalog
/
response body
√ sould contain 3 promoted products
$3
When provided - it is stringified and treated a a string body asserter.
Example:
``
module.exports = {
'/api/search' :
'search for non existing product' :
request( 'http://my-sut-server.com/search?no-such-product' )
.responds( {
json: { products: [ ] }
})
},
'search for specific existing product' :
request( 'http://my-sut-server.com/search?yellow%20t%20shirt' )
.responds( {
json: { products: [ { name: 'XL yellow T-Shirt', description: 'a very cool shirt, organic materials, very comfortable, loved by our customers' } ] }
})
}
}
}
becomes:
``
/api/search
search for non existing product
√ body should be '{ products: [ ] }'
search for specific existing product
√ body should be "{"products":[{"name"..."
.
- it's useful in wierd edge-cases, for example, when you want to test that the server instance terminates
in response to a shutdown request by an authenticated administrator.$3
a subsuite of add-hock custom assertions, performed against the entire response object.
The value should be an object where every key is a test-tile, and every value is a function that accepts the
response
object and performs add-hock assertions against it.
Example:
`
module.exports = {
'/api/wierd' : {
request('http://my-sut-server.com/search?no-such-product')
.responds({
headers: {
'x-powered-by' : 'my cool server'
etag : /.*/
},
and: {
'should redirect header, or the resource' : function(res) {
Should.be.ok(
res.status == 302 ||
res.status == 200 && res.headers['content-type'] == 'application/json'
)
}
}
})
}
}
`becomes:
`/api/wierd
√ should emit http-header: 'x-powered-by' as 'my cool server'
√ should emit http-header: 'etag' as /.*/
and
√ should give a redirect header, or the resource
`$3
marks the suite as skipped
Example:
`
module.exports = module.exports = {
'/my path' :
request({
url: 'http://my-sut-server.com/my-path',
skip: true
})
.responds({
status: 200
})
}
`becomes:
`
/my-path
- should return status 200
`request.skip
==============
same as passing
options.skipExample:
`
module.exports = module.exports = {
'/my path' :
request.skip( 'http://my-sut-server.com/my-path' )
.responds( {
status: 200
})
}
`becomes:
`
/my-path
- should return status 200
``
For more detailed API spec - don't dig for docs - run the test, just like the BDD lore sais...
Have fun ;)
Contribute
==========
Sure, why not. That's why it's here ;)
Future
------
* negated assertions (status is not..., body does not contain...)
* handle timeouts
Lisence
-------
* MIT