这是原贴地址: https://cnodejs.org/topic/586e1810df04f6ab76081dc5
最近在用eggjs写后台,也想发布api之类的文档。以前看到过通过单元测试改造生成API文档,自己还是比较认可这个理念的,于是将其拿过来,改造了一下,适用于eggjs。
首先看看现在单测的写法:
test\controller\my.test.js
'use strict';
const test = require('../../../app/common/test');
describe('test/app/controller/home.test.js', () => {
before(test.before);
afterEach(test.afterEach);
it('should GET doc', () => {
test({
file: 'user',
group: '用户相关API',
title: '获取用户信息',
method: 'get',
url: '/user/:id',
})
.get('/', {
id: { value: '123abc', type: 'String', required: true, desc: '' },
})
.expect('hi, egg')
.expect(200)
});
});
单测代码书写方式没有太大变化,只是为了方便,做了一点点包装。
app\common\test.js
'use strict';
const mm = require('egg-mock');
const assert = require('assert');
let app;
let dir;
if(!global.docTimeout) {
after( ()=>{
fs.writeFileSync(path.resolve(dir, './docs/index.js'), JSON.stringify(global.docs, null, 4));
mdRender(global.docs)
console.log('文档生成完毕')
} )
global.docTimeout = true
}
module.exports = (opt) => {
class Request {
constructor(opt) {
this.req = app.httpRequest();
this.opt = opt;
}
request(type, url, params) {
return this.req[type](url).send(params).expect(200, (err, res) => {
if(!global.docs) global.docs = []
this.opt.params = params
this.opt.res = res.text
global.docs.push(this.opt)
});
}
get(url, params) {
return this.request('get', url, params);
}
post(url, params) {
return this.request('post', url, params);
}
}
return new Request(opt)
};
module.exports.before = () => {
app = mm.app();
//记录根目录
if(!dir){
dir = app.config.baseDir
}
return app.ready();
};
module.exports.afterEach = mm.restore;
// markdown 渲染
const fs = require('fs');
const path = require('path');
function mdRender(docs){
const mdStr = {};
docs.forEach((obj) => {
if (!mdStr[obj.group]) {
mdStr[obj.group] = '';
mdStr[obj.group] += '## ' + obj.group + '\n\n';
}
const fields = {};
mdStr[obj.group] += `### ${ obj.title } \`${ obj.method }\` ${ obj.url } \n\n#### 参数\n`;
mdStr[obj.group] += '\n参数名 | 类型 | 是否必填 | 说明\n-----|-----|-----|-----\n';
Object.keys(obj.params).forEach(function (param) {
const paramVal = obj.params[param];
fields[param] = paramVal['value'];
mdStr[obj.group] += `${ param } | ${ paramVal['type'] } | ${ paramVal['required'] ? '是' : '否' } | ${ paramVal['desc'] } \n`;
});
mdStr[obj.group] += '\n#### 使用示例\n\n请求参数: \n\n';
mdStr[obj.group] += '```json\n' + JSON.stringify(fields, null, 2) + '\n```\n';
mdStr[obj.group] += '\n返回结果:\n\n';
if (obj.url.indexOf(':') > -1) {
obj.url = obj.url.replace(/:\w*/g, function (word) {
return fields[word.substr(1)];
});
}
mdStr[obj.group] += '```json\n' + JSON.stringify(obj.res, null, 2) + '\n```\n';
mdStr[obj.group] += '\n';
fs.writeFileSync(path.resolve(dir, './docs/', obj.file + '.md'), mdStr[obj.group]);
})
}
代码编写仓促,难免有疏漏之处。
起初另外由于不知道怎么在所有单测执行完毕后触发事件,开始尝试在process上监听exit事件,输出最后的文档。后来去eggjs提了一个issue,才知道可以直接用after。
感觉eggjs的团队,问题回答总是这样高效、快速。