【问题标题】:What's the best way to test express.js API测试 express.js API 的最佳方法是什么
【发布时间】:2020-12-25 00:12:57
【问题描述】:

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

我发现我可以用 jest 进行测试,通过模拟函数或者我也可以直接模拟 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。但现在我不确定有什么更好的方法来做到这一点。

【问题讨论】:

  • 标题具有误导性,因为问题不是最好的方法是什么(这种问题在 SO 上是题外话),而是如何测试特定功能。请求是从超测导入的吗?请说明这一点。

标签: javascript express testing jestjs


【解决方案1】:

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

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

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

app.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 }

test.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) => {
    try {
      let body
      if (opts.body) {
        body = JSON.stringify(opts.body)
      } else {
        body = ''
      }
      const headers = {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
        'Accept': 'application/json',
      }
      if (opts.token) {
        headers['Authorization'] = `Token ${opts.token}`
      }
      const options = {
        hostname: 'localhost',
        port: opts.server.address().port,
        path: opts.path,
        method: opts.method,
        headers,
      }
      const req = http.request(options, res => {
        res.on('data', data => {
          let dataString
          let ret
          try {
            dataString = data.toString()
            if (res.headers['content-type'].startsWith('application/json;')) {
              ret = JSON.parse(dataString)
            } else {
              ret = dataString
            }
            resolve([res, ret])
          } catch (e) {
            console.error({ dataString });
            reject(e)
          }
        })
        // We need this as there is no 'data' event empty reply, e.g. a DELETE 204.
        res.on('end', () => resolve([ res, undefined ]))
      })
      req.write(body)
      req.end()
    } catch (e) {
      reject(e)
    }
  })
}

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')
  })
})

package.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 请求也在:Synchronous request in Node.js

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

【讨论】:

    【解决方案2】:

    不可能将getData 模拟为myapp.getDatamodule.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)
      ...
    });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-15
      • 1970-01-01
      • 2010-10-11
      • 1970-01-01
      • 1970-01-01
      • 2020-11-19
      • 1970-01-01
      相关资源
      最近更新 更多