This content originally appeared on DEV Community and was authored by Sam Adams
At Super Payment we adopt an “integration-first” testing methodology, with unit/e2e style tests being applied more sparingly.
If you want to read more on why we prefer integration tests to unit/e2e tests then Kent Dodds has done a much better job than I will on explaining the rationale.
To understand how our integration tests work at Super, we need to know a bit more about our architecture:
Architecture
At Super, our services are written in TypeScript and each service encompasses a given domain. Each service has it’s own DB, SQS queue and EventBridge Bus (or whichever subset it needs).
The main way for our services to talk to each other internally is via EventBridge/SQS messages, where EB receives messages as the output of the producing system, and the consuming system binds the EB messages it’s interested in to it’s own, dedicated SQS queue for consumption.
The main way the outside world (customer, third-parties etc) interact with Super is via HTTP (or a partner EventBridge).
Integration Tests
When we say ‘integration test’ we mean hitting a single service with some real-world input (an event or HTTP request) to see what outputs/side-effects occur.
Since most of our services follow the same set of inputs (HTTP/SQS) and outputs (HTTP/EventBridge), our tests try to only act and assert on those inputs and outputs (ignoring ‘how’ the service works internally).
A typical integration test at Super looks like this:
- start supporting containers (postgres and localstack)
- start the app (we use NestJS with fastify)
- spin up a new clean DB for that test (we use Knex migrations)
- use nock to intercept all network requests
- make HTTP requests to the app and/or send SQS events
- assert on what happened
We can assert on:
- the HTTP requests we made to third-party APIs (nock)
- the events we sent (using nock to monitor the EventBridge requests)
- that the SQS queue has been processed/is empty (by directly calling localstack with the AWS-SQS SDK)
- the database state (if we have to)
- the response from our starting HTTP request
Since our tests are fully stateful (but fully isolated from other tests) we can see how changes to inputs affect outputs.
In this series we will dig into the finer details on the above, specifically on how we isolate our tests (while keeping them fast).
This content originally appeared on DEV Community and was authored by Sam Adams