I have a TypeScript project that runs a backend RESTful API using Express. It is very object-heavy by design, so that lots of classes can be instantiated and injected into each other both at run time and when services classes are tested.
We have a good suite of tests across all service classes. However, we have an index.ts that brings this all together, and this presently escapes test automation. I am mulling a variety of approaches to testing this, so that endpoints and lightweight controllers are insulated against regressions. (Rather than list all my ideas that might result in an overly broad question, I shall focus on one specific idea for now).
Let me show an example of my front controller (src/index.ts):
/* Lots of imports here */
const app = express();
app.use(express.json());
app.use(cors());
app.options('*', cors());
/* Lots of settings from env vars here */
// Build some modules
const verificationsTableName = 'SV-Verifications';
const verificationsIndexName = 'SV-VerificationsByUserId';
const getVerificationService = new GetVerification(
docClient,
verificationsTableName,
verificationsIndexName,
timer,
EXPIRY_LENGTH,
);
const writeVerifiedStatusService = new WriteVerifiedStatus(
docClient,
verificationsTableName,
timer,
getVerificationService,
);
/* Some code omitted for brevity */
// Create some routes
GetVerificationController.createRoutes(getVerificationService, app);
FinishVerificationController.createRoutes(finishVerificationService, app);
addPostStartVerification(startVerification, app);
IsVerifiedController.createValidationRoutes(di2.createOverallFeatureFlagService(), getVerificationService, app);
app.listen(PORT, () => {
console.log(`⚡️[server]: Server is running at http://localhost:${PORT}`);
});
You get the idea - classes are assembled using dependency injection, we fetch some config from env vars, and we start the HTTP listener. The major point to notice is that this file doesn't contain or export any classes or functions.
I'd like to run this file in a Jest test suite, something like this:
describe('Test endpoint wiring', () => {
beforeEach(() => {
// Set up lots of env vars
// How to run `src/index.ts` here?
});
afterEach(() => {
// Tear down the server here
});
test('First endpoint test', () => {
// Run a test against an endpoint
});
});
I wonder if there is some sort of await exec('node command') I can do here? I would want it to run in the background so that tests run once the server has started. Ideally this would form part of the async thread in Jest, but if that is not possible, a straightforward process spawn would probably be fine.
It would be great if there was a reliable way to kill that at the end of each test (I suppose maintaining the PID and sending a stop signal is OK).
Modifying the index.ts is not out of the question (and indeed I am minded to stuff all this DI construction into a class, so that pieces can be replaced for testing purposes using simple method inheritance). But I would like to explore this no-changes option first.