Mocking API calls is a tried and true method of testing. But during development, an API is often inaccessible to due being offline or might not even be built yet. There are several ways you could resolve this. For example, you could detect that you’re in development and simply return a fake payload instead of the method that calls the API. But this doesn’t ensure that the API call works appropriately.
Several years ago, I wrote a Serverless Framework plugin that integrated with nock to intercept HTTP calls and serve up mock data. This worked great at the time, especially because it prevented me from needing to run a separate server to receive these calls. However, it was limited to API calls within the same thread. The Serverless Framework soon supported Docker and worker threads and this was no longer a viable solution.
This left me with no choice but to spin up a separate server to respond to these requests. I did not relish the possibility of maintaining a system for local mock data, so I looked around at existing solutions. For some reason, most solutions I found for this were written in Java - like wiremock and mockserver. While they included ways to run within Docker, I wasn’t thrilled with using a solution in a language I wasn’t familiar with.
Then I came across Mocks Server, a Node.JS mock server. I converted my mocks to its format, but I still wasn’t satisfied. While I could live with its variants and collections, which were overkill for my needs, I ran into lots of issues when I converted my code to Typescript. Mocks Server doesn’t provide first-class support for TS, but instead requires Babel. This conflicted with my use of esbuild, and so I finally gave in a built my own mock server.
Building our mock server with Fastify
If I was going to build my own mock server, it had to require the lowest amount of maintenance possible. It just had to work. My task was to build the actual app, not a mock server. And if this was going to benefit from type-safety, it would have to support TypeScript. I didn’t really care if I used Express, Hapi, or Fastify. But I did care about file structure. To help with organization, I wanted to have separate folders for API I was calling. The Fastify Autoload plugin made this super easy, so I went with Fastify. It was pleasant experience using its methods and I wrote this mock server in an afternoon.
Install requirements
yarn install tsx fastify @fastify/autoload –dev
Create mockServer.ts:
/* eslint-disable no-console */
import fastify from 'fastify';
import autoLoad from '@fastify/autoload';
import path from 'node:path';
const app = fastify({ logger: true });
app.register(autoLoad, {
dir: path.join(__dirname, 'src/mocks'),
dirNameRoutePrefix: true,
});
app.listen({ port: 8080, host: '0.0.0.0' }, (err, address) => {
if (err) {
console.error(err);
process.exit(1);
}
console.log(`Server listening at ${address}`);
});
Now in src/mocks, create a folder for each API.
For example, if you have a “docs” API, create “src/mocks/docs” folder.
Now create an index.ts in src/mocks/docs:
import { FastifyPluginAsync } from 'fastify';
const docs: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/documents/1', async function () {
return {
content: 'This is the content',
};
});
};
export default docs;
Now we can boot up our mock server:
tsx watch mockServer.ts
Now when we get http://localhost:8080/docs/documents/1, we’ll get:
{
content: 'This is the content',
}
We just need our app to detect when we’re in a local environment and have it point “docs” API calls to http://localhost:8080/docs/
Making our mock server type-safe
At this point, we have a fully functional mock server. But we haven’t reaped the benefits of having it be type-safe. There are many ways this could be accomplished, and it all depends on how you have types working within your app. Most of the APIs I call have a OpenAPI Swagger definition, so I have implemented them with OpenAPI Fetch. Part of the process of using OpenAPI Fetch is converting the swagger definition to TypeScript types via OpenAPI TypeScript. However way you generate types for your API calls, all we have to do is use those within our Fastify routes and we now have a type-safe mock server.
To demonstrate this, here's an updated index.ts for our docs API mock:
import { FastifyPluginAsync } from 'fastify';
import { Document } from 'src/@types/docsAPI';
const docs: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/documents/1', async function () {
return {
content: 'This is the content',
} satisfies Document;
});
};
export default docs;
Need a fresh perspective on a tough project?
Let’s talk about how RDG can help.