繁体   English   中英

测试 express.js API 的最佳方法是什么

[英]What's the best way to test express.js API

我是使用 JavaScript 进行 API 测试的新手。 我找到了许多测试 REST API 的解决方案,但不确定什么是最好的。 我在后端和测试中使用 express.js 开玩笑。

我已经看到我可以开玩笑地测试,通过模拟函数或者我也可以直接模拟 API。

我有一个存储对象的 data.js:

let data = [
{
    id: 0,
    name: "empty shopppinglist",
    location: "",
    targetDate: "",
    priority: "",
    isFinished: false,
    items: ["vodka"
    ]
}
]
module.exports = data

然后在后端文件夹中,我有这个来提供端点并读取/返回文件:

function getData(){
  let shoppingLists = data
  return shoppingLists
}
module.exports.getData = getData
let shoppingLists = data



app.get('/api/shoppingLists', (req, res) => {
   const listsWithoutItems = getData().map(({ items, ...rest }) => rest)
   res.status(200).send(listsWithoutItems)
})

我在这里也不确定,如果我可以将 app.get 调用移动到一个函数。

在我的测试中,我想测试 API 的行为,因此如果数据无效,我将收到错误 500 等。为此,我已经尝试过此测试:

describe('Get Endpoint for all Lists', () => {

it('should return 2 lists', async () => {
    myapp.getData = jest.fn(() => [
        {
            "id": 0,
            "name": "filled shopping list",
            "location": "lidl",
            "targetDate": "22.03.1986",
            "priority": "1",
            "isFinished": false,
            "items": ["vanille"
            ]
        }
    ])

    const res = await request(myapp)
        .get('/api/shoppingLists')
    expect(res.statusCode).toEqual(200)
    expect(res.body).toHaveProperty('2')
})
})

不幸的是,我总是从 data.js 获取原始条目,而不是模拟结果。 通过搜索文档,我还看到,我可以模拟定义我的 API 的整个 app.js。 但现在我不确定什么是更好的方法。

getData模拟为myapp.getData是不可能的。 module.exports.getData = getData不能很好地用于测试,因为它允许模拟getData仅当它在任何地方用作exports.getData()而不是getData() ,这是不切实际的。

data需要在它定义的模块中进行模拟。 然后,每个测试都应该重新导入依赖于它的所有模块,以便受到模拟的影响。 jest.isolateModulesjest.resetModules可以与require结合使用以提供特定于测试的模块模拟​​。

beforeEach(() => {
  jest.resetModules();
});

it('should return 2 lists', async () => {
  jest.mock('.../data', () => [...]);
  const myapp = require('.../app');

  const res = await request(myapp)
  ...
});

我想我已经达到了我想要的设置:

  • 到处await ,没有无限深度嵌套的回调
  • 除了 mocha 和 express 没有外部库,类似的设置适用于其他系统
  • 没有嘲笑。 没有嘲笑额外的努力。 它只是在每个测试的随机端口上启动一个干净的服务器,并在测试结束时关闭服务器,就像真实的一样

在此示例中未显示,当使用NODE_ENV=test时,您可能希望通过使用每个测试唯一的临时内存 SQLite 数据库运行应用程序来完成任务。 真正的生产服务器会在 PostgreSQL 之类的东西上运行,并且会使用像 sequelize 这样的 ORM,以便相同的代码可以在两者上运行。 或者,您可以设置一次创建数据库并在每次测试之前截断所有表。

应用程序.js

#!/usr/bin/env node

const express = require('express')

async function start(port, cb) {
  const app = express()
  app.get('/', (req, res) => {
    res.send(`asdf`)
  })
  app.get('/qwer', (req, res) => {
    res.send(`zxcv`)
  })
  return new Promise((resolve, reject) => {
    const server = app.listen(port, async function() {
      try {
        cb && await cb(server)
      } catch (e) {
        reject(e)
        this.close()
        throw e
      }
    })
    server.on('close', resolve)
  })
}

if (require.main === module) {
  start(3000, server => {
    console.log('Listening on: http://localhost:' + server.address().port)
  })
}

module.exports = { start }

测试.js

const assert = require('assert');
const http = require('http')

const app = require('./app')

function testApp(cb) {
  return app.start(0, async (server) => {
    await cb(server)
    server.close()
  })
}

// https://stackoverflow.com/questions/6048504/synchronous-request-in-node-js/53338670#53338670
function sendJsonHttp(opts) {
  return new Promise((resolve, reject) => {
    const body = JSON.stringify(opts.body)
    const options = {
      hostname: 'localhost',
      port: opts.server.address().port,
      path: opts.path,
      method: opts.method,
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
      }
    }
    const req = http.request(options, res => {
      res.on('data', data => {
        resolve([res, data.toString()])
      })
    })
    req.write(body)
    req.end()
  })
}

it('test root', () => {
  // When an async function is used, Mocha waits for the promise to resolve
  // before deciding pass/fail.
  return testApp(async (server) => {
    let res, data

    // First request, normally a POST that changes state.
    ;[res, data] = await sendJsonHttp({
      server,
      method: 'GET',
      path: '/',
      body: {},
    })
    assert.strictEqual(res.statusCode, 200)
    assert.strictEqual(data, 'asdf')

    // Second request, normally a GET to check that POST.
    ;[res, data] = await sendJsonHttp({
      server,
      method: 'GET',
      path: '/',
      body: {},
    })
    assert.strictEqual(res.statusCode, 200)
    assert.strictEqual(data, 'asdf')
  })
})

it('test /qwer', () => {
  return testApp(async (server) => {
    let res, data
    ;[res, data] = await sendJsonHttp({
      server,
      method: 'GET',
      path: '/qwer',
      body: {},
    })
    assert.strictEqual(res.statusCode, 200)
    assert.strictEqual(data, 'zxcv')
  })
})

包.json

{
  "name": "tmp",
  "version": "1.0.0",
  "dependencies": {
    "express": "4.17.1"
  },
  "devDependencies": {
    "mocha": "6.2.2"
  },
  "scripts": {
    "test": "mocha test test.js"
  }
}

有了这个,运行:

npm install
./app

根据需要正常运行服务器。 并运行:

npm test

使测试按需要工作。 值得注意的是,如果您将任何断言破解为错误的值,它们将抛出,服务器关闭而不挂起,最后失败的测试显示为失败。

异步 http 请求也在: Node.js 中的同步请求

在 Node.js 14.17.0、Ubuntu 21.10 上测试。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM