概觀

API 測試是一種軟體測試,涉及測試應用程式的 API 層。

API 測試包括測試用戶端應用程式和伺服器之間的請求和回應。這通常透過將 HTTP 請求傳送到 API 端點並驗證傳回的回應來完成。API 測試的主要目標是確保 API 行為符合預期,並針對不同的輸入情境傳回正確的資料和錯誤。

總體而言,API 測試是軟體測試的重要環節,可確保應用程式 API 層的可靠性和功能,讓開發人員能夠建置穩健且可擴充的軟體應用程式。

運作方式為何?

若要執行 API 測試,需要安裝官方的 @nightwatch/apitesting 外掛程式。此外掛程式提供以下功能

  1. supertest 整合以進行 HTTP 請求測試
  2. express 為基礎的內建模擬伺服器,支援 sinon 對模擬 HTTP 請求的斷言

需要 Nightwatch 2.6.4 或更高版本。

安裝

1) 從 NPM 安裝外掛程式

npm i @nightwatch/apitesting --save-dev

2) 將外掛程式新增至清單

更新 Nightwatch 組態以將此外掛程式新增至清單

nightwatch.conf.js
module.exports = {
  plugins: ['@nightwatch/apitesting']
  
// other Nightwatch settings... }

3) 停用瀏覽器 Session

我們還需要關閉瀏覽器 Session,因為我們只執行 API 測試。這可以透過為 API 測試新增一個新環境來達成,如下所示在 nightwatch.conf.js 中。

nightwatch.conf.js
module.exports = {
  // ....
  api_testing: {
    start_session: false,
    webdriver: {
      start_process: false,
    }
  }
}

組態設定

此外掛程式目前只有一個組態選項,即是否要將 HTTP 回應記錄到主控台。這可以在 nightwatch.json (或 nightwatch.conf.js) 組態檔中設定。

nightwatch.conf.js
{
  "@nightwatch/apitesting" : {
    "log_responses": true
  }
}

測試 API 標頭和回應

由於 Nightwatch 在幕後使用 supertest,您可以測試不同類型的 REST API 標頭和回應。

GET 請求

get-api-test.js
describe('api testing', function () {
  it('get api test', async function({supertest}) {
    await supertest
      .request("https://petstore.swagger.io/v2")
      .get("/pet/findByStatus?status=available")
      .expect(200)
      .expect('Content-Type', /json/)
      .then(function(response){
          expect(response._body.length).to.be.greaterThan(0);
      });
  });
});

POST 請求

post-api-test.js
describe('api testing', function () {
  it('post api test', async function({supertest}) {
    await supertest
      .request("https://petstore.swagger.io/v2")
      .post("/pet")
      .send({
        "id": 0,
        "category": {
          "id": 0,
          "name": "string"
        },
        "name": "doggie",
        "photoUrls": [
          "string"
        ],
        "tags": [
          {
            "id": 0,
            "name": "string"
          }
        ],
        "status": "available"
      })
      .expect(200)
      .expect('Content-Type', /json/)
      .then(function(response){
          expect(response._body.name).to.be.equal("doggie");
      });
  });
});

執行 API 測試

確保 API 測試針對 start_sessionwebdriver -> start_process 設定為 falseenvironment 執行。

npx nightwatch <path to tests> --env api_testing

HTML 報告

執行測試後,可以在 HTML 報告中檢閱結果。

HTML report

整合式模擬伺服器

@nightwatch/apitesting 外掛程式還提供以 express 為基礎的內建模擬伺服器,可用於斷言傳入的 http 請求。

以下是一個範例模擬伺服器

mock-server.js
describe('api testing with supertest in nightwatch POST', function () {
  
let server;
before(async function(client) { server = await client.mockserver.create(); server.setup((app) => { app.post('/api/v1/datasets/', function (req, res) { res.status(200).json({ id: 'test-dataset-id' }); }); });
await server.start(3000); });
after(() => { server.close(); });
it('demo test', async function(client) { const req = await server.request() .post('/api/v1/datasets/') .send({name: 'medea'}) .set('Accept', 'application/json') .expect(200) .expect('Content-Type', /json/);
await client.assert.deepStrictEqual(server.route.post('/api/v1/datasets/').requestBody, {name: 'medea'}); });
});

模擬伺服器 API

  • const mockServer = await client.mockserver.create() – 建立新的模擬伺服器執行個體
  • await mockServer.setup(definition) – 使用提供的路由定義設定現有的模擬伺服器執行個體 範例
    await mockServer.setup((app) => {
        app.get('/api/v1/schemas', function (req, res) {
          console.log('GET /api/v1/schemas called');
          
    res.status(200).json([ { id: 'test-schema-id1' }, { id: 'test-schema-id2' } ]); }) });
  • await mockServer.start(port) – 在指定的連接埠上啟動現有的模擬伺服器執行個體
  • await mockServer.route(path) – 傳回指定路由上的 sinon spy

斷言傳入的請求

使用 mockServer.route(path) 方法擷取指定路由上的 Spy。然後,您可以使用 sinon 斷言來斷言傳入的請求。

範例

考慮先前的模擬伺服器設定範例。如果我們想要斷言呼叫了 GET /api/v1/schemas 路由,我們可以執行以下操作

it('demo test', async function(client) {
    client
      .assert.strictEqual(mockServer.route.get('/api/v1/schemas').calledOnce, true, 'called once')
      .assert.strictEqual(mockServer.route.get('/api/v1/schemas').calledTwice, false);
  });

斷言請求標頭

我們也可以斷言請求標頭,例如使用內建的 expect() 斷言 API,此 API 使用 chai

it('demo test', async function(client) {
    const {requestHeaders} = mockServer.route.get('/api/v1/schemas');
    
client.expect(requestHeaders).to.have.property('connection', 'close'); });

斷言傳入的 Post 資料

我們也可以斷言傳入的 Post 資料

  1. 首先,為模擬伺服器設定 Post 路由
await mockServer.setup((app) => {
  app.post('/api/v1/datasets/', function (req, res) {
    res.status(200).json({
      id: 'test-dataset-id'
    });
  });
});
  1. 然後,使用 mockServer.route.post(path) 方法擷取指定路由上的 Spy。然後,您可以使用 sinon 斷言來斷言傳入的請求。
it('demo test', async function(client) {
    const {requestBody} = mockServer.route.post('/api/v1/schemas');
    
await client.assert.deepStrictEqual(requestBody, {name: 'medea'}); });

若要等待傳入的請求測試,您可以使用 waitUntil() 指令。

使用 waitUntil 的範例

it('demo test', async function(client) {
    const timeoutMs = 15000;
    const retryIntervalMs = 500;
    
await client.waitUntil(async function () { const spy = server.route.get('/api/v1/schemas');
if (spy) { return spy.calledOnce; }
return false; }, timeoutMs, retryIntervalMs, new Error(`time out reached (10000ms) while waiting for API call.`));
});