使用Jest要求和模拟文件的正确顺序是什么?

问题描述

我正在尝试为我的Express应用程序使用Jest创建集成测试。我的测试表现异常,因此我在概念上存在误解。我的目标是测试以下情况。我正在使用Supertest命中特定的端点,并且我想检查是否存在模拟错误,是否调用错误处理程序中间件。我想检查是否没有错误处理程序不被调用我有以下测试文件

test.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挂钩再次初始化所有内容。现在,我有了没有模拟的香草实现。我需要我的服务器,使用get请求访问主页,然后获得正确的响应。

奇怪的是,由于某种原因,第二项测试似乎无法正常退出。如果我将实现从async更改为-在第二个测试中等待,以指定完成的回调,然后在测试结束时调用它,则似乎可以正常工作。

我尝试了许多可能的排列,包括将模拟部分放在beforeEach挂钩上,在模拟之前/之后启动服务器,结果很奇怪。我觉得我在概念上有误解,但我不知道在哪里,因为有很多活动部件。

任何帮助我了解问题所在的帮助都将不胜感激

编辑:

我认为大多数零件都可以视为黑匣子,但是现在我意识到我试图使用Socket.IO创建应用程序这一事实使设置过程更加复杂。

我不希望Express为我自动创建服务器,因为我想使用socketIO。因此,现在我只创建带有适当签名的函数,即“ app”。可以将其作为http.Server()的参数。我使用选项和要使用的中间件对其进行配置。我不想调用app.listen,因为那样Socket.IO无法做自己的事情。

config.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可以启动它。 >

server.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,当我使用request(server).get等时,Supertest会处理启动服务器的过程。

编辑2

现在我不确定这样的嘲笑是否足够。一些神秘的事情使“超级测试”请求挂起了,这可能是它无法结束的某个地方,尽管我不知道为什么会这样。无论如何,这是路由器:

routes / 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('*',next) => {
  try {
    res.status(404).render('../views/not-found')
  } catch (error) {
    next(error)
  }  
})

module.exports = router

解决方法

需求和模拟的顺序是正确的,但是设置和关闭服务器的顺序可能不正确。

一种安全的方法是在进行请求之前确保服务器可用。由于节点http是异步的并且是基于回调的,因此在没有承诺的情况下,不能期望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)
...

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...