NodeJs API接口单元测试
api单元测试需要用到的
assert
:断言库 (还要一些断言库比如:Chai
)supertest
: 模拟http请求
简单的例子:
const express = require('express');
const supertest = require('supertest');
const assert = require('assert');
// 创建一个 Express 应用
const app = express();
app.get('/api/user', (req, res) => {
res.status(200).json({ name: 'John Doe' });
});
// 测试用例
describe('GET /api/user', () => {
it('应该返回用户信息', async () => {
const request = supertest(app)
const res = await request()
.get('/api/user') // 发送 GET 请求
.set('Accept', 'application/json') // 设置请求头
.expect('Content-Type', /json/) // 验证响应头
.expect(200); // 验证状态码
// 验证响应体
assert.strictEqual(res.body.name, 'John Doe');
});
});
一、 断言
- Node.js 有内置的断言模块
assert
,用于在代码中验证某些条件是否为真。如果条件不满足,assert 会抛出一个 AssertionError,导致测试失败。
简单介绍一些NodeJs内置的断言assert的一些属性方法:assert.ok(value[, message])
可以直接简写为assert()
验证value 是否为真assert.ok(1);
// 通过assert.ok(0, '值不能为 0');
// 抛出 AssertionError,错误信息为 “值不能为 0”
assert.strictEqual(actual, expected[, message])
验证是否 严格相等(===)assert.strictEqual(1, 1)
; // 通过
assert.notStrictEqual(actual, expected[, message])
验证是否 不严格相等(!==)assert.deepStrictEqual(actual, expected[, message])
验证是否 深度严格相等(适用于对象或数组)assert.deepStrictEqual({ a: 1 }, { a: '1' })
; // 抛出 AssertionError,因为类型不同
assert.notDeepStrictEqual(actual, expected[, message])
验证是否 深度不严格相等(上面的例子不会抛错)assert.equal(actual, expected[, message])
验证是否 相等(==,非严格相等)。assert.notEqual(actual, expected[, message])
验证是否不相等(!=,非严格相等)。assert.throws(block[, error][, message])
验证 block 函数是否会 抛出错误。
assert.throws( () => { throw new Error('错误信息'); }, Error, // 验证错误类型 '未抛出预期错误' // 自定义错误信息 );
assert.doesNotThrow(block[, error][, message])
验证 block 函数是否 不会抛出错误。assert.fail([message])
强制抛出一个 AssertionError,标记测试失败assert.fail('测试失败')
- 总之: assert 是 Node.js 内置的断言模块,适合简单的测试场景。如果需要更丰富的功能和更友好的语法,可以考虑使用 chai 等第三方断言库。
- 还有一些第三方库,比如
chai
,它支持多种风格的断言。Chai
是一个可以在node和浏览器环境运行的BDD/TDD
断言库,可以和任何JavaScript测试框架结合。
按使用风格:
assert
风格:类似于 Node.js 内置的 assert,但功能更强大。expect
风格:链式语法,可读性更高。should
风格:基于原型链的语法,适合 BDD(行为驱动开发)。
// assert 风格
assert(res.body.success === true)
// expect 风格
expect(1 + 1).to.equal(2);
expect({ a: 1 }).to.deep.equal({ a: 1 });
// should 风格
chai.should();
(1 + 1).should.equal(2);
按测试风格:
-
BDD
(Behavior Driven Development行为驱动开发):是一种敏捷软件开发的技术,它鼓励软件项目中的开发者、QA和非技术人员或商业参与者之间的协作(做正确的事
) -
TDD
(Test-Driven Development测试驱动开发): 测试先于编写代码的思想用于指导软件开发(正确的做事
)expect和should是BDD风格的。两者使用相同的语言链
- expect使用构造函数来创建断言对象实例
- should使用Object.prototype提供一个getter方法来实现,不兼容IE
assert属于TDD
- 除语法糖外,assert和node.js的非常相似,assert是三种断言风格唯一不支持链式调用的
chai详细介绍:学习Chai断言库
二、 supertest, 用于测试 HTTP 服务的库
supertest
是一个用于测试 HTTP 服务的 Node.js 库,特别适合测试 Express 或其他基于 Node.js 的 Web 服务器。它提供了简洁的 API 来构造和发送 HTTP 请求,并验证响应结果。supertest 通常与测试框架(如 Mocha、Jest 等)结合使用,用于编写端到端(E2E)测试或集成测试。
...
// 测试用例
describe('GET /api/user', () => {
it('应该返回用户信息', async () => {
const request = supertest(app)
const res = await request()
.get('/api/user') // 发送 GET 请求
.set('Accept', 'application/json') // 设置请求头
.expect('Content-Type', /json/) // expect属于supertest内置断言,验证响应头
.expect(200); // supertest内置断言,验证状态码
});
});
...
下面列举一些supertest属性方法:
- 构造请求
.get(url)
:发送 GET 请求。.post(url)
:发送 POST 请求。.put(url)
:发送 PUT 请求。.delete(url)
:发送 DELETE 请求。.patch(url)
:发送 PATCH 请求。.head(url)
:发送 HEAD 请求。
- 设置请求头,
.set(field, value)
例如:.set('Authorization', 'Bearer token')
// 设置 Authorization 头.set('Accept', 'application/json')
; // 设置 Accept 头
- 发送请求体,使用
.send(data)
方法可以发送请求体,适用于 POST、PUT 等请求。.send({ name: 'John', age: 30 })
- 设置查询参数, 使用
.query(params)
方法可以设置查询参数。.query({ page: 1, limit: 10 })
; // 设置查询参数 ?page=1&limit=10
- 设置请求类型, 使用
.type(type)
方法可以设置请求的Content-Type
。.type('json')
// 设置 Content-Type 为 application/json.type('form')
// 设置 Content-Type 为 application/x-www-form-urlencoded.type('text')
// 设置 Content-Type 为 text/plain
- 文件上传, 使用
.attach(field, file)
方法可以上传文件。.attach('file', 'path/to/file.txt')
; // 上传文件
- 验证响应, 内置了
.expect(...)
方法,与第三方断言库库chai
的expect作用类似,只是supertest的内置方法更专注于 HTTP 响应的验证, 而 chai 的 expect 是一个通用的断言库,适用于更广泛的场景。.expect(status)
:验证状态码。.expect(header, value)
:验证响应头。.expect(body)
:验证响应体。.expect(function(res) { ... })
:自定义验证逻辑。
- 处理响应,使用 .end(callback) 方法可以处理响应结果。
.get('/api/data').end((err, res) => { if (err) throw err; ...})
- 超时设置, 使用
.timeout(ms)
方法可以设置请求的超时时间。 - 重定向, 使用
.redirects(n)
方法可以设置最大重定向次数。.redirects(2)
// 最多允许 2 次重定向
- Cookie, 使用
.set('Cookie', cookie)
方法可以设置请求的 Cookie。 - 自定义 Agent, 使用
.agent()
方法可以创建一个自定义的 superagent 实例,用于保持会话
const agent = request.agent(app);
agent
.post('/api/login')
.send({ username: 'john', password: '123456' })
.end((err, res) => {
if (err) throw err;
agent.get('/api/data').end((err, res) => {
if (err) throw err;
console.log(res.body);
});
});
- 响应对象属性, 在
.end()
或.expect()
的回调函数中,可以访问响应对象的属性。
.end((err, res) => {
if (err) throw err;
console.log(res.status); // 状态码
console.log(res.headers['content-type']); // 响应头
console.log(res.body); // 响应体
console.log( res.text); // 原始响应文本。
});
三、完整配置
我在项目中的配置:(也不全,简单参考一下)
const assert = require('assert');
const supertest = require('supertest');
const md5 = require('md5');
const elpisCore = require('../../elpis-core')
const signKey = '620b048b-8ac3-431b-845d-bcaf63ecc738'
const st = Date.now();
describe('测试 project 相关接口', function () {
this.timeout(60000);
let request;
it('启动服务', async () => {
const app = await elpisCore.start();
request = supertest(app.listen());
});
it('GET /api/project/model_list', async () => {
let tmpRequest = request.get('/api/project/model_list');
tmpRequest = tmpRequest.set('s_t', st);
tmpRequest = tmpRequest.set('s_sign', md5(`${signKey}_${st}`))
const res = await tmpRequest;
assert(res.body.success === true)
const resData = res.body.data;
assert(resData.length > 0);
for (let i = 0; i < resData.length; i++) {
const item = resData[i];
assert(item.model);
assert(item.model.key);
assert(item.model.name);
assert(item.project);
for (const projKey in item.project) {
assert(item.project[projKey].key);
assert(item.project[projKey].name);
}
}
});
})