Why a health route?
I want to begin with the end in mind. When our API is deployed in a cloud environment, the automated, cloud deployment will need a way to know that our API has started correctly. That is the purpose of the GET /health
route.
I have created a workspace on Codesandbox.io with the completed code. And I'll walk you through the code.
If you want to skip ahead, you can take a look at the "Getting Started" section in the README.md.
Acceptance criteria โ
Well written tests are like documentation that is always in sync with operational code. So I like to use a style called Acceptance criteria from Behaviour-Driven Design (BDD) when describing my tests. This takes the format of -
- Given: the initial context at the beginning of the scenario, in one or more clauses;
- When: the event that triggers the scenario;
- Then: the expected outcome, in one or more clauses.
Red ๐ด
The entry point of our application is index.ts
. A typical NodeJs server is started with a command like node ./build/index.js
. I follow the pattern of placing test files next to the code they test. This makes them easier to find and shows which files are missing tests.
index.test.ts
contains -
// helper method to make a HTTP request
const asyncGet = (
options: http.RequestOptions
): Promise<http.IncomingMessage> =>
new Promise((resolve, reject) => {
http.get(options, (res) => resolve(res));
});
describe(`Given start`, () => {
let server: FastifyInstance;
let port: number;
// SETUP
beforeAll(async () => {
// generate a random port number for the server
port = await getPort({ port: getPort.makeRange(3000, 3100) });
// configure the server from outside using params
// this makes it much easier to test
server = await start({ port });
});
// CLEAN-UP
afterAll(async () => {
await server.close();
});
// ACT
test(`Server is listening`, async () => {
// make a request to the server port
// does it work?
const res = await asyncGet({
hostname: 'localhost',
port,
path: '/health',
});
// check the response code
expect(res.statusCode).toEqual(200);
});
});
Green ๐ข
The minimum viable code to get the tests to pass is a simple route to respond to GET '/health'
import fastify from 'fastify';
// for testing
export async function start(options: { port: number }) {
const server = fastify({ logger: true });
// basic health response can be anything
server.get('/health', async () => 'OK');
const { port } = options;
await server.listen(port);
return server;
}
// so we can node ./build/index.js to start the server
(async () => start({ port: Number(process.env.PORT) }))();
Refactor
There isn't anything to do here since the code is so simple. But in the next post, ๐ฎ we'll add more routes that will require some refactoring.