The 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...
In this case what I usually do is create a Chai custom assertion that defines a set of assertions for a specific type, e.g a Todo
.
This custom assertion is then (re)used in all relevant tests to verify that the returned object(s) actually pass the assertions for Todo
, without having to repeat the same assertions in each and every test.
Here's an example:
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)
})
})
})
If in the future I add a new field on Todo
, i.e completed
, all I need to do is modify the custom assertion like so:
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 ?
As a minimum I would check if:
- The response HTTP status is correct.
- The response body has the appropriate properties and correct types for each property.
- If the response is a list of items, I would check if the response body is indeed an
Array
, if it has the length
I expect it to have and if each element in the Array
has the correct properties and types.
There's no "rules" here. At the end it's a risk/time decision. Maintaining a test suite takes time. If I'm building a simple todo app for my own use, I won't concern myself too much with exhaustive testing. However if I'm building a public payment server, I'd definitely want my tests to be as exhaustive as possible.