As stated in the previous article, this one will be more of an overview about testing the microservices. As it turns out, this is equally if not more complicated than managing them and took me quite a lot of time to go through various articles and YT videos, but in the end, it boils down to few simple steps.
First things first, we can just ignore unit testing for now since this is done on a single class/method, as well as System/UI testing since this is usually done on the whole system and there is no debate here - you start the whole system and test it end to end.
The type of testing that is a bit tricky in microservices is intergration testing which should test interactions between the components of the system, in our case - between the services, and that's where the trouble begins.
The problem is with setting up the testing environment for integration tests. Since we should test only specific interactions there is no need to run everything, especially locally on your own machine. We should just start some of the services - but which of them? How much of the environment do you need to successfully test some features? And in the case of The One system - how much do you need to run in order to test if API Gateway works properly?
The step-by-step approach is crucial here - yeah, I know, obvious, but I've seen people doing everything at once more than a few times. So, without further ado, here's what my approach is right now.
- Which feature do we want to test?
- Which services are needed to run?
- Do we need a database to run?
- Which services do we need running and which can be mocked?
- Which database do we need running and which can be mocked?
These are the first questions, you should ask yourself to determine the next steps.
As an example: in The One project, we want to test, whether the proxying requests work properly. The answers to the above questions will look like this:
- Proxying request to correct service,
- API Gateway, Service Discovery, one of the business logic services, let's say, User Service,
- Two-part answer:
- Need Running: API Gateway, Service Discovery
- Can Be Mocked: User Service
- Not applicable
So, we need 3 services running, 1 of which can be Mocked or Stubbed. Since at the time of writing this article, the User Service doesn't have any actual logic in it, we could just start it, but, to humor ourselves, let's pretend it already has database connection and complex business logic and we need to somehow mock that.
The additional questions to ask yourself would be:
- Mock or stub the service?
- Mock or stub connection to DB or the whole DB? (ask only if we need DB)
As for the difference between the Mock and Stub, I find this StackOverflow answer very helpful. As I understand it:
- Mock - the response from mock is configured and can vary between runs (like in Mockito)
- Stub - the response is hardcoded and always the same
So, as I see it, if you decide to use Stub, you essentially need to create a second service with the same API and hardcode the responses to always return the same data. In a monolithic application, you would just create a second implementation of an interface, in the case of microservices, I would suggest creating a second microservice or adding a profile to the existing one. Each approach will have its own benefits.
If you create a second service:
- it will be lightweight, with no if statements to determine what to do,
- the original service will be unaffected, no redundant code in production,
- but you will have to support two implementations in case of refactoring.
If you just add a profile:
- no need to support two implementations,
- you can have stub implementation right next to the actual one,
- but testing it will be a bit slower since you will have more code to start.
Mocking will be primarily used in the test file itself, you will basically say:
When I call method X with these parameters, I want that response
For Java, the most popular library is Mockito which is easy to use.
In the case of our example, if we wanted to test only the proxy, we would mock the call to Discovery Service and check if proxied request points to correct ip, but I think it's pointless to test it that way. I want to know if User Service registers itself with Discovery and will be available through Gateway.
As for the database, you can also Mock it, Stub it, use Test Environment Database or provide an instance for the duration of the test only. I would suggest avoiding Test Env DB if you can do that because it is usually used by the QA team and may be unstable.
The rule of thumb for DB will be:
- if you need only to get values from db - Mock or Stub the methods called on DB,
- if you need to check how different services transform the data in DB - provide special testing DB if you can, if not, use Testing Env DB
The last step is to implement what you decided on. Create testing docker-compose.yml, mock or stub services, provide database if necessary and run the tests. I will do that in the near future, but one idea is to create another Java project which will only contain integration tests.
Testing is huge topic which adds another layer to already complex topic of running and managing microservices. I hope this overview will be of some help in your projects.