DEV Community

Discussion on: #Discuss - API Level Functional / Integration Tests in a Microservices Architecture

orkon profile image
Alex Rudenko

This is an excellent topic, and I would like to share my thoughts based on my experience.

First, I think it makes sense to classify dependencies of a microservice into three categories: infrastructural (database, Redis instance, etc.), internal (other services developed in your company) and external (services out of your control).

Second, it makes sense to classify (really basic classification) the layers of a typical service architecture:
1) gateway layer (provides access to 3rd party services via remote calls), 2) data layer (encapsulates the data sources of your microservice, here, I mean databases/caches, etc.)
3) service layer (business logic)
4) API layer (connects your APIs with your business layer).

Third, you need to classify your tests. I use the following classification:
1) unit tests. Simple tests that can test the code at any level without mocks/stubs/etc. And without access to the network or databases.
2) integration tests (here I mean the integration with other services and data sources, not how your code pieces integrate with each other). These tests should perform testing of the gateway and data layer using the real instance of the underlying system. It's ok to use mocks only in the cases when no test system is available (for example, you use 3rd party API, and they provide just a production system, and it does something important like payments).
3) component tests. These tests should cover your APIs and whatever the essential top-level "components" of your system are. These tests should use mocks for the gateway and data layers.

So with this in mind, I think the following is a reliable (and practical) approach:
1) cover every code of any importance with unit tests. Remember, if you are forced to mock something for a unit test, it's most likely not a good unit test. Unit tests should run without any dependencies on anything except for the code itself. It makes them fast, and you should probably run them first. It's good to force unit tests to fail if they try to reach outside or to the database.
2) write integration tests for everything in the gateway and data layer. It should run against real (test) instances of a related service or database. For internal services, I suggest having a separate environment where all these services are always running. We call it a development environment. For external services, use the test system provides by a 3rd party. For databases and instances, I suggest running a local version using docker compose. Alternatively, you can have a test instance of your data sources running somewhere constantly. Depending on the number of your dependencies, you may choose if you want to run the integration tests every time or only when there were some changes to the data or gateway layers.
3) write component tests for every API resource and every action you have in your service. Also, test error cases. I would say you can skip mocking the data layer because it's too much work and just use the real data layer code with a fresh copy of the data source started via docker (just like in integration tests). Also, I find it better to populate the data store with test data using the data layer code instead of loading SQL dumps or another form of snapshots. You can choose if you want to invest in mocks for the gateway level depending on the type of the service being integrated (external vs. internal) and the nature of your integration.
4) Additionally, have an end-to-end test which runs immediately after your service is deployed to the development environment. The e2e test should act as a real consumer of your service, and it should cover basic use cases.

Let me know what you think or if I can clarify some points.

brpaz profile image
Bruno Paz Author

Great explanation! Thank you!
Your points 1 and 4, are already covered that way.

Regarding 2 and 3, that is more or less what I was thinking.
My main doubt was about what would be the best to start: Integration or Component testing. I would love to have both but with our current resources, we need to focus on what can give more value and safety on releases ,since we are still finding our way to a more Continuous Delivery approach.

So, I think we will start with Integration tests running like you describe in point 2.
In our case, we have one service that rely on an External provider that although they have a staging environment its not very good and there are many test cases that wont be possible to be tested without mocking that service. So we will use mocks for that service, but everything else will be a real instance of the service.

We have 1 or 2 services that are shared between different projects / teams. In that case we will try to do some component testing also.