概述

API 测试是一种软件测试,它涉及测试应用程序的 API 层。

API 测试包括测试客户端应用程序和服务器之间的请求和响应。这通常通过向 API 端点发送 HTTP 请求并验证返回的响应来完成。API 测试的主要目标是确保 API 按预期运行,并且它在不同的输入情况下返回正确的数据和错误。

总的来说,API 测试是软件测试中一个重要的方面,它可以确保应用程序 API 层的可靠性和功能,使开发人员能够构建健壮且可扩展的软件应用程序。

它是如何工作的?

要执行 API 测试,需要安装官方的 @nightwatch/apitesting 插件。该插件提供了以下功能

  1. supertest 集成,用于测试 HTTP 请求
  2. 基于 express 的内置模拟服务器,支持对模拟 HTTP 请求进行 sinon 断言

需要 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) 禁用浏览器会话

我们还需要关闭浏览器会话,因为我们只进行 API 测试。这可以通过在 nightwatch.conf.js 中添加一个用于 API 测试的新环境来实现,如下所示

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 测试是在 environment 中运行的,其中 start_sessionwebdriver -> start_process 设置为 false

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 间谍

断言传入请求

使用 mockServer.route(path) 方法检索指定路由上的间谍。然后,您可以使用 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,它基于 chai

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

断言传入的发布数据

我们还可以断言传入的发布数据

  1. 首先,为模拟服务器设置一个发布路由
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) 方法检索指定路由上的间谍。然后,您可以使用 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.`));
});