干休息 API 测试

DRY Rest API testing

我正在用 mocha 和 chai 对休息 api 进行单元测试。目前,对于每个请求,(例如 POST)我测试整个响应主体(不包括非静态数据,如 ids 等...)。问题是:如果有一天我决定更改我的资源模型,比如添加一个新字段。实际测试不会检查这个新字段。所以我必须更新与此资源相关的每个测试,随着测试数量的增加,这可能会变得复杂。

所以我的问题是:我的做法是否正确?如果不是,我应该在我的 api 回复中测试什么,我不应该测试什么?

DRY (Don't repeat yourself) principle applies to testing as well, but don't overdo it. Tests should be "DAMP not DRY".

...if some day I decide to change the model of my resource, like maybe adding a new field. The actual tests won't check this new field. So I'll have to update every test related to this resource...

在这种情况下,我通常做的是创建一个 Chai custom assertion 来为特定类型定义一组断言,例如 Todo

然后(重新)在所有相关测试中使用此自定义断言,以验证返回的对象是否确实通过了 Todo 的断言,而不必在每个测试中重复相同的断言.

这是一个例子:

const chai = require('chai')
const chaiHttp = require('chai-http')
const server = 'https://jsonplaceholder.typicode.com'

chai.should()
chai.use(chaiHttp)
// Define a custom assertion for 'Todo'. We expect that a Todo always 
// has an `id` property of type `Number` and a `title` property of
// type `String`.
chai.use((chai, utils) => {
  utils.addProperty(chai.Assertion.prototype, 'Todo', function () {
    this._obj.should.have.property('id')
    this._obj.should.have.property('title')

    this._obj.id.should.be.a('Number')
    this._obj.title.should.be.a('String')
  })
})

// Begin Tests

describe('Retrieve a Todo', () => {
  it('returns a single Todo by ID', () => {
    return chai.request(server)
      .get('/todos/1')
      .then(res => {
        res.should.have.status(200)
        // Use the custom assertion to check if returned object
        // passes the assertions for `Todo`.
        res.body.should.be.a.Todo
      })
  })
})

describe('Retrieve all Todos', () => {
  it('returns a list containing 200 Todos', () => {
    return chai.request(server)
      .get('/todos')
      .then(res => {
        res.should.have.status(200)
        res.body.should.be.an('Array')
        res.body.should.have.length(200)
        // Reuse the custom assertion to check if all returned objects
        // pass the assertions for `Todo`.
        res.body.forEach(todo => todo.should.be.a.Todo)
      })
  })
})

如果将来我在 Todo 上添加一个新字段,即 completed,我需要做的就是像这样修改自定义断言:

chai.use((chai, utils) => {
  utils.addProperty(chai.Assertion.prototype, 'Todo', function () {
    this._obj.should.have.property('id')
    this._obj.should.have.property('title')
    this._obj.should.have.property('completed')

    this._obj.id.should.be.a('Number')
    this._obj.title.should.be.a('String')
    this._obj.completed.should.be.a('Boolean')
  })
})

... what should I test in my api responses and what should I not ?

至少我会检查是否:

  • 响应 HTTP status 正确。
  • 每个 属性 的响应主体都有适当的属性和正确的类型。
  • 如果响应是一个项目列表,我会检查响应正文是否确实是一个 Array,如果它有 length 我希望它有,如果Array 具有正确的属性和类型。

这里没有"rules"。最后是 risk/time 的决定。维护测试套件需要时间。如果我正在构建一个简单的待办事项应用程序供自己使用,我不会太担心详尽的测试。但是,如果我正在构建一个 public 支付服务器,我肯定希望我的测试尽可能详尽。