簡體   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