Generate HTTP API documentation from your tests: a simple addition to Go's testing pkg

go get



wercker status

Automatically generate HTTP API documentation from your Go unit tests: a simple addition to Go's testing pkg

Diving right in..

Given a handler func:

// GetWidget retrieves a single Widget
func GetWidget(w http.ResponseWriter, req *http.Request) {
	// get widget
	// respond with widget JSON
	// ...

And a test for this handler func:

func TestGetWidget(t *testing.T) {
	urlPath := fmt.Sprintf("/widgets/%d", 2)

	resp, err := http.Get(server.URL + urlPath)
	// assert all the things...

Test2doc will generate markdown documentation for this endpoint in the API Blueprint format, like so:

# Group widgets

## /widgets/{id}

+ Parameters
    + id: `2` (number)

### Get Widget [GET]
retrieves a single Widget

+ Response 200 

    + Body

                "Id": 2,
                "Name": "Pencil",
                "Role": "Utensil"

Which you can then parse and host w/, eg here. Or use a custom parser and host yourself.


Things to note:

  1. Go pkg name becomes Group name
  2. Go handler name becomes endpoint title
  3. Go handler godoc string becomes endpoint description
  4. Everything else is recorded & interpreted directly from the requests and responses


package widget

// GetWidget retrieves a single Widget
func GetWidget(w http.ResponseWriter, req *http.Request)


# Group widget

### Get Widget [*]
retrieves a single Widget


go get

Integrating test2doc

Very few additions, and only to your testing code.

1. Add 3 things to your TestMain:

import (

var server *test.Server

func TestMain(m *testing.M) {
	// 1. Tell test2doc how to get URL vars out of your HTTP requests
	//    The 'URLVarExtractor' function must have the following signature:
	//      func(req *http.Request) map[string]string
	//      where the returned map is of the form map[key]value

	// 2. You must use test2doc/test's wrapped httptest.Server instead of
	//    the raw httptest.Server, so that test2doc can listen to and
	//    record requests & responses.
	//    NewServer takes your HTTP handler as an argument
	server, err := test.NewServer(router)
	if err != nil {

	// .. then run your tests as usual
	// (remember that os.Exit does not respect defers)
	exitCode := m.Run()

	// 3. Finally, you must tell the wrapped server when you are done testing
	//    so that the buffer can be flushed to an apib doc file

	// note that os.Exit does not respect defers.

Router-specific configurations

gorilla/mux configurations

	// NOTE: if you are using gorilla/mux, you must set the router's 
	//  'KeepContext' to true, so that url parameters can be accessed
	//  after the request has been handled.
	router := NewRouter()
	router.KeepContext = true
	// Use mux.Vars func as URLVarExtractor

julienschmidt/httprouter configurations

// MakeURLVarExtractor returns a func which extracts 
// url vars from a request for test2doc documentation generation
func MakeURLVarExtractor(router *httprouter.Router) parse.URLVarExtractor {
	return func(req *http.Request) map[string]string {
		// httprouter Lookup func needs a trailing slash on path
		path := req.URL.Path
		if !strings.HasSuffix(path, "/") {
			path += "/"

		_, params, ok := router.Lookup(req.Method, path)
		if !ok {
			return nil

		paramsMap := make(map[string]string, len(params))
		for _, p := range params {
			paramsMap[p.Key] = p.Value

		return paramsMap

// and then..

2. Combine the output .apib files

test2doc will spit out one doc file per package.

Eg. A package tree like:

├── foos
│   ├── foos.go
│   └── foos_test.go
└── widgets
    ├── widgets.go
    └── widgets_test.go

Will produce separate apib files, eg:

├── foos
│   ├── ...
│   └── foos.apib
└── widgets
    ├── ...
    └── widgets.apib

You will need to add the doc header (below) and combine all of the package doc files after your tests run.


# find all *.apib files (after tests have run, generated files)
files=`find . -type f -name "*.apib"`

# copy template file to new apiary.apib file
cp apib.tmpl apiary.apib

# copy contents of each generated apib file into apiary.apib
# and delete the apib file
for f in ${files[@]}; do
	cat $f >> apiary.apib
	rm $f

where apib.tmpl includes the doc header information. Something like:


# The API for My Site

My Site is a fancy site. The API is also fancy.