node.js - 使用 Jest 要求和模拟文件的正确顺序是什么?
问题描述
我正在尝试使用 Jest 为我的 Express 应用程序创建集成测试。我想我有一个概念上的误解,因为我的测试表现得很奇怪。我的目标是测试以下场景。我正在使用 Supertest 访问特定端点,并且我想检查是否调用了错误处理程序中间件,如果存在模拟错误。如果不存在错误,我想检查是否未调用错误处理程序。我有以下测试文件:
测试.js
const request = require('supertest')
describe('Error handler', () => {
let server
let router
beforeEach(() => {
jest.resetModules()
jest.resetAllMocks()
})
afterEach(async () => {
await server.close()
})
it('should be triggered if there is a router error', async () => {
jest.mock('../../routes/')
router = require('../../routes/')
router.mockImplementation(() => {
throw new Error()
})
server = require('../../server')
const res = await request(server)
.get('')
.expect(500)
.expect('Content-Type', /json/)
expect(res.body.error).toBe('Error')
expect(res.body.message).toBe('Something went wrong!')
expect(res.body.status).toBe(500 )
})
it('should not be triggered if there is no router error', async () => {
server = require('../../server')
const res = await request(server)
.get('')
.expect(201)
.expect('Content-Type', /text/)
})
})
我认为正在发生的事情如下。在每次测试之前,我都会重置所有模块,因为我不想从第一个要求中获得服务器的缓存版本,我想覆盖它。我还重置了所有的模拟,所以当第二个测试运行时,没有使用模拟,没有强制错误,所以中间件没有被调用,我得到了一个香草 200 结果。
完成后,我会在出现错误时开始测试场景。我模拟了导出我的路由的路由文件,这样我就可以强制一个假错误。然后我需要服务器,这样,我想,它用虚假的错误抛出路由加载服务器。然后我等待 Supertest 的响应,并断言我确实收到了错误 - 因此错误处理程序中间件已被触发并工作。
afterEach 钩子被调用,服务器关闭,然后 beforeEach 钩子再次初始化一切。现在我有了没有模拟的香草实现。我需要我的服务器,通过获取请求访问主页,然后我得到正确的响应。
奇怪的是,由于某种原因,第二个测试似乎没有优雅地退出。如果我在第二个测试中将我的实现从 async - await 更改为指定完成的回调,然后如果我在测试结束时调用它,它似乎正在工作。
我尝试了很多可能的排列,包括将模拟部分放在 beforeEach 钩子中,在模拟之前/之后启动服务器,我得到了奇怪的结果。我觉得我有概念上的误解,但我不知道在哪里,因为有很多活动部件。
任何让我了解问题所在的帮助将不胜感激
编辑:
我认为大多数部分都可以被视为一个黑匣子,但现在我意识到我正在尝试使用 Socket.IO 创建一个应用程序这一事实使得设置过程更加复杂。
我不希望 Express 自动为我创建服务器,因为我想使用 socketIO。所以现在我只创建一个带有适当签名的函数,那就是'app'。这可以作为 http.Server() 的参数给出。我用我想使用的选项和中间件来配置它。我不想调用 app.listen,因为那样 Socket.IO 不能做自己的事情。
配置.js
const path = require('path')
const express = require('express')
const indexRouter = require('./routes/')
const errorHandler = require('./middlewares/express/errorHandler')
const app = express()
app.set('views', path.join(__dirname + '/views'))
app.set('view engine', 'ejs')
app.use(express.static('public'))
app.use('', indexRouter)
app.use(errorHandler)
module.exports = app
在 server.js 中,我需要这个应用程序,然后使用它创建一个 HTTP 服务器。之后,我将它提供给“socket.io”,因此它连接到正确的实例。在 server.js 中,我不调用 server.listen,我想将它导出到一个实际启动服务器的文件 (index.js),并且我想将它导出到我的测试中,以便 Supertest 可以启动它。
服务器.js
// App is an Express server set up to use specific middlewares
const app = require('./config')
// Create a server instance so it can be used by to SocketIO
const server = require('http').Server(app)
const io = require('socket.io')(server)
const logger = require('./utils/logger')
const Game = require('./service/game')
const game = new Game()
io.on('connection', (socket) => {
logger.info(`There is a new connection! Socket ID: ${socket.id}`)
// If this is the first connection in the game, start the clock
if (!game.clockStarted) {
game.startClock(io)
game.clockStarted = true
}
game.addPlayer(socket)
socket.on('increaseTime', game.increaseTime.bind(game))
})
module.exports = server
如果我正确理解了所有内容,则基本上会发生相同的事情,请在您提供的示例中执行一些额外的步骤。不需要启动服务器,然后在上面使用Supertest,Supertest在我使用request(server).get等时处理启动服务器的过程。
编辑 2
现在我不确定这样的嘲笑是否足够。一些神秘的事情让 Supertest 的请求悬而未决,并且可能在某个地方无法结束,尽管我不明白为什么会这样。无论如何,这是路由器:
路线/index.js
const express = require('express')
const router = express.Router()
router.get('', (req, res, next) => {
try {
res.status(200).render('../views/')
} catch (error) {
next(error)
}
})
router.get('*', (req, res, next) => {
try {
res.status(404).render('../views/not-found')
} catch (error) {
next(error)
}
})
module.exports = router
解决方案
要求和模拟的顺序是正确的,但设置和关闭服务器的顺序可能不是。
一种安全的方法是在发出请求之前确保服务器可用。由于 Nodehttp
是异步且基于回调的,因此不能期望在没有承诺的情况下在async
函数中处理错误。考虑到它server.listen(...)
是在 server.js 中调用的,它可以是:
...
server = require('../../server')
expect(server.listening).toBe(true);
await new Promise((resolve, reject) => {
server.once('listening', resolve).once('error', reject);
});
const res = await request(server)
...
close
是异步的,并且不返回承诺,所以没有什么要await
. 由于它位于专用块中,因此一种简短的方法是使用done
回调:
afterEach(done => {
server.close(done)
})
error
如果侦听器中的错误被抑制,server.on('error', console.error)
可以使故障排除更容易。
Supertest 可以自己处理服务器创建:
您可以将 http.Server 或 Function 传递给 request() - 如果服务器尚未侦听连接,则它会为您绑定到临时端口,因此无需跟踪端口。
并且可以提供 Express 实例而不是 Node 服务器,这消除了手动处理服务器实例的需要:
await request(app)
...
推荐阅读
- encryption - Python:boto3解密
- reactjs - 如何使用 firebase 条带扩展添加多个订阅?
- powershell - Powershell 将文件夹内容移动到另一个文件夹
- html - 如何只输入一个数字?
- python - Pandas dtype=datetime64[ns] 和日期错误
- sql - Pony ORM 中的动态分组 -> 聚合
- json - MongoDB:分别对每个文档上的嵌套数组进行排序
- android - 资产包无法指定版本代码,但“基础”可以,我做错了什么?
- mongodb - Mongoose Schema 如何映射到 MongoDB 中的集合?
- linux - fork()之后,如何防止子进程读取不必要的数据?