Express集成Supertest实践

起因

最近在看nodebestpractice,秉持着凡事都要试试的原则,简单练了练手,本文完整代码参见链接node-practice

关于自动化测试,原文是这么描述的。

这里超快的流行的测试包是指Supertest这个包,专门用于对HTTP服务进行集成测试的。

安装

我用的是Typescript,所以开发依赖当中有很多类型申明的包。

json 复制代码
"devDependencies": {
  "@types/body-parser": "^1.19.4",
  "@types/express": "^4.17.20",
  "@types/node": "^20.8.8",
  "@types/supertest": "^2.0.15",
  "mocha": "^10.2.0",
  "supertest": "^6.3.3",
  "ts-node": "^10.9.1"
},
"dependencies": {
  "express": "^4.18.2"
}

指令

测试相关的命令有一些Typescript相关的配置。

json 复制代码
"scripts": {
  "tsc": "tsc",
  "start": "node build/app.js",
  "dev": "nodemon src/app.ts",
  "build": "tsc -p .",
  "test": "mocha -r ts-node/register src/**/test.ts"
},

实现一个简单的GET接口

ts 复制代码
// src/components/blog/controller.ts
import { Router } from 'express';

const router = Router();

router.get('/', (res, req, next) => {
  req.status(200).send('Hello world!');
});

export default router;

// src/app.ts
import express from 'express'
import blogRouter from './components/blog/controller'

const app = express();

app.use(
  blogRouter,
);

const server = app.listen(8088, function () {
  console.log('node-practice app listening on port http://localhost:8088');
});

export default server;

使用Supertest测试

踩坑

手动启动的服务Supertest无法在测试完成后将其关闭

官方提供的方法是直接将app这个express实例传入到request函数当中去,request会帮助我们创建一个HttpServer,并且会在调用end方法时在内部调用HttpServer的close方法。

js 复制代码
// 官方推荐使用方法
const request = require('supertest');
const assert = require('assert');
const express = require('express');

const app = express();

app.get('/user', function(req, res) {
  res.status(200).json({ name: 'john' });
});

request(app)
  .get('/user')
  .expect('Content-Type', /json/)
  .expect('Content-Length', '15')
  .expect(200)
  .end(function(err, res) {
    if (err) throw err;
  });

// 服务创建源码
serverAddress(app, path) {
  const addr = app.address();

  if (!addr) this._server = app.listen(0);
  const port = app.address().port;
  const protocol = app instanceof Server ? 'https' : 'http';
  return protocol + '://127.0.0.1:' + port + path;
}

// 服务销毁源码
end(fn) {
  const server = this._server;

  super.end((err, res) => {
    const localAssert = () => {
      this.assert(err, res, fn);
    };

    if (server && server._handle) return server.close(localAssert);

    localAssert();
  });

  return this;
}

可以看到在销毁的时候会先判断server是否存在以及_handle属性是否被定义,如果没有做应用和服务的分离,也就是手动启动服务,将会导致_handle为null,服务将不会被关闭,测试的进程也不会自动结束。

手动创建的server输出

所以在app.ts文件中需要导出的是服务,而不是express实例,并且在test文件中手动关闭当前服务。

当然,也可以通过应用和服务的分离避免这样的问题。

node版本导致的node:test当中it函数入参TestFn的入参变化

Supertest官方的代码示例当中it函数第二个入参,也就是测试函数的入参和node18对应的测试函数的入参有所区别。

js 复制代码
// 官方推荐的使用方法
describe('GET /user', function() {
  it('responds with json', function(done) {
    request(app)
      .get('/user')
      .set('Accept', 'application/json')
      .expect('Content-Type', /json/)
      .expect(200, done);
  });
});

// Node18中的类型定义
type TestFn = (t: TestContext, done: (result?: any) => void) => void | Promise<void>;

实现

ts 复制代码
import { describe, it } from 'node:test';
import request from 'supertest';
import server from '../../app';

describe('GET /', () => {
  it('should response status 200', (_, done) => {
    request(server)
      .get('/')
      .send()
      .expect(200, () => {
        done();
      });
  });
});

server.close();
相关推荐
dingdingfish2 天前
关于 Oracle Database Express Edition 的功能和安装
oracle·express·database·vagrant·edition
知难行难3 天前
福昕阅读器高级版解决文件上传IEEE PDF eXpress字体未嵌入
pdf·express
ch_s_t5 天前
基于 Express+JWT + Vue 的前后端分离架构
vue.js·架构·express
caridle7 天前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
胡西风_foxww12 天前
nodejs爬虫系统
爬虫·nodejs·node·系统·express·request·cheerio
松果猿15 天前
场地污染在线计算可视化平台,获得易智瑞GIS开发竞赛C组优胜奖,内附易智瑞GIS开发竞赛与全国大学生测绘学科创新创业智能大赛提交材料。
vue·express
js_user20 天前
在 Vue 渲染模板时,如何保留模板中的 HTML 注释?
开发语言·前端·javascript·css·vue.js·html·express
瑕、疵23 天前
使用Node.js和Express构建RESTful API
node.js·restful·express
js_user24 天前
css中,我想把文章的第一行设置单独的样式
开发语言·前端·javascript·css·node.js·css3·express
傻啦嘿哟1 个月前
Plotly Express 详解:快速创建精美交互式可视化图表的最佳实践
信息可视化·plotly·express