test2doc v0.4.2
test2doc.js - Build API docs from your tests
test2doc.js helps you seamlessly integrate API documentation generation to your test flow.
You write something like this:
const doc = require('test2doc')
const request = require('supertest') // We use supertest as the HTTP request library
require('should') // and use should as the assertion library
// For Koa, you should exports app.listen() or app.callback() in your app entry
const app = require('./my-express-app.js')
after(function () {
  doc.emit('api-documentation.apib') // Or doc.emit('api-documentation.yaml', 'swagger') if you like Swagger
})
doc.group('Products').is((doc) => {
  describe('#Products', function () {
    doc.action('Get all products').is((doc) => {
      it('should get all products', function () {
        // Write specs towards your API endpoint as you would normally do
        // Just decorate with some utility methods
        return request(app)
          .get(doc.get('/products'))
          .query(
            doc.query({
              minPrice: doc
                .val(
                  10,
                  'Only products of which price >= this value should be returned'
                )
                .required(),
            })
          )
          .expect(200)
          .then((res) => {
            doc.resHeaders(res.headers)
            body = doc.resBody(res.body)
            body.desc('List of all products').should.not.be.empty()
            body[0].should.have.properties('id', 'name', 'price')
            body[0].price.desc('Price of this product').should.be.a.Number()
          })
      })
    })
  })
})And then test2doc.js will capture all the info provided by you via doc.get / doc.query / doc.resBody / myVar.desc. You can choose a generator to generate the final documents based on these collected information. Since we capture every thing we need from tests, you will always get up-to-date documents.
Currently we provide API Blueprint generator and Swagger generator.
test2doc.js is not designed to run on a specified test framework, which means you can use this in conjunction with any test frameworks and assertion libraries.
We provide an extension for supertest called supertest-test2doc, which makes it much easier to integrate test2doc.js with supertest.
Installation
Install test2doc.js as an npm module and save it to your package.json file as a development dependency:
npm install test2doc --save-devOnce installed it can now be referenced by simply calling require('test2doc').
The npm package name is test2doc without .js suffix.
Getting Started
First require this library, for convenience we use doc as the imported variable name:
const doc = require('test2doc')test2doc.js has two core concepts: group and action. A group is a collection of actions and child groups, and an action describes a real API endpoint (HTTP method / url / queries / request body / response body / headers). Like a tree, actions are leaf nodes and groups are non-leaf nodes.
The doc variable imported here is the root group. When you call doc.group('Name of this subgroup'), it returns a new object represents the sub group, which has exactly the same interfaces as the root group.
When you call doc.action('Name of this action'), it returns a new object represents the action, which has different interfaces compared to group.
For convenience, we usually use the variable name doc all the way to make it easy to write clean codes.
Some methods of group object are:
doc.title(title)- set title of this groupdoc.desc(...descriptions)- give descriptions for this groupdoc.basePath(basePath)- set base path for this groupdoc.group(childGroupTitle)- create a child group, returns the child group
Some methods of action object are:
doc.get(url, parameters)- Capture a string as the url, returns this url so you call pass to your HTTP request librarydoc.resBody(body)- Capture an object as the response body, returns an proxy of this object
Full list can be found at API references section.
Methods like doc.resBody(body) / doc.val(value, ...descriptions) and so on returns an ES6 Proxy of the object passed in. So we can add custom methods to these objects like desc(...descriptions), required(), etc. However because JS doesn't allow proxying non-object value (number / null / undefined etc.), we will create wrapper objects around these values. If these wrapper objects don't work well with your assertion / request library, you can use doc.uncapture(object) to get the original value.
Once you have collected all the info needed to build the documentations, call emit on the root group to emit the actual documentation file. Generally you will do this in a global after hook.
API references
Group
Methods
title (title)- set title of this group- return: this group
 
desc (...descriptions)- give descriptions for this group- A group can have multiple descriptions. Generally each description will be rendered as a paragraph.
 - return: this group
 
scheme (...schemes)- set supported schemes on this group- e.g. 
scheme('http', 'https', 'ws', 'wss') - return: this group
 
- e.g. 
 host (host)- set hostname for this group- Only hostname, without 'http://' or trailing slash.
 - return: this group
 
version (version)- set API version for this group- return: this group
 
basePath (basePath, parameters)- set base path for this group- e.g. 
basePath('/v0/product/:id', { id: doc.val('123', 'Product ID') }) - return: this group
 
- e.g. 
 val (value, ...descriptions)- capture an value and give it descriptions- return: a proxy of 
value 
- return: a proxy of 
 params (parameters)- describe parameters for the base path- Same as the second parameter in 
basePath(basePath, parameters) - return: this group
 
- Same as the second parameter in 
 query (queries)- describe queries for the base path- return: this group
 
reqHeaders (headers)- describe common request headers for all actions in this group- e.g. 
doc.reqHeaders({ 'x-my-header': 'foobar', 'x-my-array-header': ['value1', 'value2'] }) - return: this group
 
- e.g. 
 group (title)- create a child group titledtitle- return: the newly created child group
 
action (title)- create an action titledtitlebelonging to this group- return: the newly created action
 
is (collectFn)- callcollectFnwith this group as the first argument- Usually used in chaining call.
 - e.g. 
doc.group('a child group').is(doc => { ... }), firstdocrefers to the parent group, seconddocrefers to the child group - return: this group, or a promise if 
collectFnreturns a promise, which will be resolved with this group when the promise returned bycollectFnfinishes. 
uncapture (object, shouldSliceArray = false)- uncapture an object (strip the proxy)- If 
shouldSliceArrayis true, then any array inobjectwill be subsetted according to offset() and limit() set on it. - return: an uncaptured object, with all its children and nested children uncaptured
 
- If 
 emit (file, generator = 'apib', options = {})- generate the actual documentation filefilecan be a filename or a file descriptor. It's the same object passed intofs.writeFileSync.- If 
fileis omitted, the generated text will be the return value. - Available generators: 
apib/swagger - return: void
 
Action
Methods
get/post/put/delete/...(url, parameters)- shortcut ofmethod(method).url(url, parameters)- Supports all methods provided by npm package methods.
 - return: the url with parameters filled in
 
method (method)- set request method of this action- return: this action
 
title (title)- set title of this action- return: this action
 
desc (...descriptions)- give descriptions for this action- return: this action
 
url (url, parameters)- describe url and url parameters for this actionurlcan be an express-route-style path, which can include parameters- use 
parametersto describe parameters in the url - return: the url with parameters filled in
 
val (value, ...descriptions)- Same asgroup.valanotherExample ()- as a seperator between different exapmles- an example = params + query + reqBody + resBody
 - return: this action
 - e.g.
 
doc.params ...blahblah... doc.query ...blahblah... doc.reqBody ...blahblah... doc.resBody
doc.anotherExample() // Divide into two examples in a same code block
doc.params ...blahblah... doc.query ...blahblah... doc.reqBody ...blahblah... doc.resBodyparams (parameters, returnProxy = false)- Same asgroup.params, except this is for actionquery (queries, returnProxy = false)- Same asgroup.query, except this is for actionreqHeader (name, value, returnProxy = false)- describe a single header for this action- e.g. 
doc.reqHeader('authorization', 'Bearer 123456')returns['authorization', 'Bearer 123456'] - return: an array with two items, 
nameandvalue 
- e.g. 
 reqHeaders (headers, returnProxy = false)- describe request headers for this action- e.g. 
doc.reqHeaders({ 'x-my-header': 'foobar', 'x-my-array-header': ['value1', 'value2'] }) - return: the 
headersobject passed in, or a proxy ofheadersifreturnProxyis true 
- e.g. 
 reqBody (body, description, returnProxy = false)- Capture an object as the request body and give it a description- return: the 
bodyobject passed in, or a proxy ofbodyifreturnProxyis true 
- return: the 
 resHeaders (headers)- describe response headers for this action- e.g. 
doc.resHeaders({ 'content-type': 'application-json', 'x-total-page': '16' }) - return: a proxy of 
headers 
- e.g. 
 status (statusCode)- describe the response HTTP status code for this action- return: the 
statusCodepassed in 
- return: the 
 resBody (body)- Capture an object as the response body- return: a proxy of 
body 
- return: a proxy of 
 is (collectFn)- Same asgroup.is, except the parameter passed tocollectFnis this actionuncapture (object)- Same asgroup.uncapture
Captured Objects
Methods
desc (...descriptions)- give descriptions for this object- return: this captured object
 
required ()- mark this as required- return: this captured object
 
optional ()- mark this as optional (opposite torequired())- return: this captured object
 
nullable (nullable = true)- mark this as nullable or non-nullable- return: this captured object
 
fixed (fixed = true)- mark this as fixed- return: this captured object
 
fixedType (fixedType = true)- mark this as fixed-type- return: this captured object
 
enum (...possibleValues)- mark this should be a member ofpossibleValues- return: this captured object
 
default (defaultValue)- mark the default value for this- return: this captured object
 
sample (...sampleValues)- give a sample value for this- return: this captured object
 
offset (offset)- specify the offset in the array to be included in the documents- Only makes sense if this is an array
 - Useful if you only want to include a subset of this array in the documents
 - return: this captured object
 
limit (limit)- specify the number of items from the offset in the array to be included in the documents- Only makes sense if this is an array
 - Useful if you only want to include a subset of this array in the documents
 - return: this captured object
 
uncapture (shouldSliceArray = false)- uncapture this- If 
shouldSliceArrayis true, then any array in this object will be subsetted according to offset() and limit() set on it. - return: the uncaptured original object for this
 
- If 
 
Roadmap
- Add tests and integrate with Travis CI
 - Swagger support
 - Write an extension for supertest to simplify grammer
 - Incremental document generation
 - CONTRIBUTION guide
 - An official website
 
License
The project is released under MIT License.
4 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago