Testing With Persistence in NestJS
At Spinwheel, we use Nest for platform development. Nest (NestJS) is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with and fully supports TypeScript (yet still enables developers to code in pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
Nest provides a very good end to end testing mechanism with Jest. It is facilitated for the same via the @nestjs/testing module.
Spinwheel focuses on end to end testing as part of its development flow.
When originally setting up end-to-end testing on the platform, we faced four core problems. I’ll walk you through how we solved each of these problems.
Problem: Connecting to external persistent systems like database and queues while testing
Solution: Starting the application in a test environment with external dependencies
- For any API, it needs to talk to a database to read or write persistent data. As part of end-to-end test setup, we had to make sure we were able to connect to the database when we bootstrapped the application for testing.
- The solution we came up with is instrumenting mongodb-memory-server as a module. One of the problems with any persistence layer is it needs to be running or available before executing tests. Since we use the NestJS framework, we wrapped the in-memory server into a module which helped us tap into the applications start and shutdown behavior. This ensures that we have persistence available before executing tests after starting an application. We used the modules lifecycle events to start accurately before execution of tests to be available for tests. It exposed a service which could share the connection URI which can be used in the “application” we are testing.
- We use queues in the platform to get asynchronous tasks done. To stop the application from failing due to connection issues in the testing environment and also facilitate real messages for the purpose of testing functionality, we mocked the queue connector module. We then created a fixture to support the NextJS transport layer required to support queue communication.
- This allowed us to post and receive messages in memory for the edge to edge testing of our controllers, decorators including authentication and filtering and interceptors.
Problem: Creating necessary objects in DB to test with real scenarios
Solution: Providing a context to test case
- To create the relevant objects in db for simulation of scenarios we used factory. Wrote factories for DTOs to generate the JSON objects and saved them in memory DB before each test scenario mentioned above.
Problem: Calling endpoints for bootstrapped application
Solution: Testing APIs
- We have an HTTP server to expose an API on the platform. To test that we used supertest to call endpoints.
- We also used the fixture we created to send the messages via in-memory queue to invoke the controllers for microsservice listeners.
Problem: Mocking external systems like API calls and loggers
Solution: Supporting integrations
- We used nock for mocking the HTTP requests going out of the system to provide pre-saved objects.
This article was written by Bharat Mhaskar. Ronan Fegan and Alejandro Mouras did the hard work of increasing our coverage from nothing to majorly covered code, implementing all the solutions mentioned above.