Reduce Test Boilerplate with Generic Test Functions
I wanted to quickly mention a trick I use to reduce the boilerplate code you'll often encounter when executing similar test cases. Instead of defining each test separately:
const { describe, it } = require('mocha');
const { expect } = require('chai');
const app = require('../../index.js');
const request = require('supertest');
describe('Orders API Endpoint', () => {
it('should return 400 if orderId is not a UUID', (done) => {
request(app)
.get('/orders/foobar')
.set('Accept', 'application/json')
.expect(400, done);
});
it('should return 404 if order is not found', (done) => {
request(app)
.get('/orders/effbf2e6-abb4-4d9b-82fa-fc1b8cca61d3')
.set('Accept', 'application/json')
.expect(404, done);
});
// ...
});
You can define a generic test function and then call it repeatedly for the different cases you want to verify:
const { describe, it } = require('mocha');
const { expect } = require('chai');
const app = require('../../index.js');
const request = require('supertest');
function testRoute({ description, url, expectedStatus }) {
it(description, (done) => {
request(app)
.get(url)
.set('Accept', 'application/json')
.expect(expectedStatus, done);
});
}
describe('Orders API Endpoint', () => {
[
{
description: 'should return 400 if orderId is not a UUID',
url: '/orders/foobar',
expectedStatus: 400,
},
{
description: 'should return 404 if order is not found',
url: '/orders/effbf2e6-abb4-4d9b-82fa-fc1b8cca61d3',
expectedStatus: 404,
},
].forEach(testRoute);
});
A friend of mine mentioned that he very much disliked defining test cases in an array and calling .forEach(testRoute)
for each element. You could instead move the function parameters around and make the it look like an it
declaration:
const { describe, it } = require('mocha');
const { expect } = require('chai');
const app = require('../../index.js');
const request = require('supertest');
function theRoute(description, { url, expectedStatus }) {
it(description, (done) => {
request(app)
.get(url)
.set('Accept', 'application/json')
.expect(expectedStatus, done);
});
}
describe('Orders API Endpoint', () => {
theRoute('should return 400 if orderId is not a UUID', {
url: '/orders/foobar',
expectedStatus: 400,
});
theRoute('should return 404 if order is not found', {
url: '/orders/effbf2e6-abb4-4d9b-82fa-fc1b8cca61d3',
expectedStatus: 404,
});
});
The neat thing is that you can still use the Regex match parameter to execute a subset of test cases:
./node_modules/.bin/mocha test -g 'Orders API Endpoint should return 404 if order is not found'
Of course, it doesn't make sense to use this strategy if you only have a couple of test cases. I find it useful when I'm writing integration tests or verifying a function with an enumerated set of inputs in which I want to verify each case.
Stumbling my way through the great wastelands of enterprise software development.