In the previous tutorial, I explained the basics of Testcontainers, make sure to read that first.
In this tutorial, I'll explain how to use it to test your Microservice, which in the real world, usually requires more than one container.
Also, make sure to visit the Testcontainers official page.
Codes
In this example, I'm going to use the User-Management-API Microservice that I created.
To run it, it must be connected to a Postgres DB, hence I'm going to define them both in Testcontainers.
The concept is the same as on "docker-compose". You define a subnet, usually a bridge network, and you add your containers. They all encapsulated inside that subnet, hence, to communicate with them you need to expose their ports to the outside world.
For example:
user-mngmnt-api internal port is 8080, exposed outside on port 3373
postgres internal port is 5432, exposed outside on port 3215
In this case, both containers will communicate over internal IP and internal ports. When the Test communicates with them it will use an external IP and external port.
E.g: http://localhost:<extPort>/path
The system we are going to test
The system contains 2 containers:
User-Management-API
Postgres DB
How it works:
The user-mngmt-api is a user management system. You can Add Users, Get Users and Delete Users. It's a REST API. So for example we can go to http://localhost:8080/api/vi/getAllUsers
and we'll get a JSON response with all the Users that are currently in the system.
We can also add Users to the system by sending a POST request to the /api/addUser URL, with a JSON body:
{
"firstName": "jon",
"lastName": "dao"
}
Users are being stored in a User's Table in Postgres DB.
when we Add/Get Users from the system, it communicates with the DB over HTTP, hence we must make sure both the API and the DB can perform that communication.
Test Flow
Configure and spin up the containers
Add a new User to the system
Verify we can fetch that User we just added
Close the containers
Code Explanation
First I start by defining the Testcontainers Network instance "network".
All containers that I want to communicate with under the same subnet will get this instance as their network.
Then at the @Bforer method, I define the containers and start them -
Postgres Container
10-sec sleep. I make sure my Microservice is starting only after Postgres is already up and ready for connections. There are better ways to do so, but I rather keep this tutorial simple.
withNetwork("network") - pass the container a network instance. To communicate, all containers in this environment will have to get this same network instance.
withNetworkAliases("postgres") - no matter which IP it will get when it starts up, any container in the network will be able to communicate with it under the address "postgres"
withExposedPorts(5432) - expose port 5432 to the outside network. same as running via the command line with -p 5432:5432.
withEnv() - here we can pass the container any environment variables. Same as running Docker container from the CLI with the -e variable. In this case, I make sure to run the container with the same password that my Microservice is using.
start() to start the container. At this point, we can print "docker ps" in the terminal to see the container up and running.
user-mngmnt-api Container
I provided it with a name
I defined alias name for easy access its IP
Exposed port 8080
withEnv("postgres_ip", "postgres") - when the Microservice is starting, it tries to connect to Postgres. For that, it must get the IP where Postgres is listening.
At this point, the whole environment is up and running.
Type "docker ps" in the terminal and see all the information.
Notice: port 8080 is exposed to 32840 and port 5432 is exposed to 32839.
Now I'm going to add a User to the service. For that, I'm sending a POST request to "http//localhost:32840/api/vi/addUser" with User in the body.
Notice the URL! the user-mngmnt-api's IP is "usersApi", not localhost. But I'm communicating with it on localhost.
Note: there are 2 ways of communicating with any of the containers.
Inside the subnet - 1 container is communicating with the other container.
From outside the subnet - the Test is communicating with the containers.
When my Test is communicating with address "usersApi" he's coming from outside, hence I use the localhost:32840.
Regarding the external port - I get it from the getMappedPort(8080) method.
At this point, there is a new User in the DB -
{
"firstName": "jon",
"lastName": "dao"
}
Now it's time to send a GET request to "http//localhost:32840/api/v1/getAllUsers", extract the response to JsonObject (actually JsonArray in this case) with Gson, and validate the field with the Junit Assertion class.
Summary
In this tutorial, we learned how to test multiple containers that depend on each other - a typical Microservice system.
We used the Testcontainers library to configure and run them, and we managed to interact with them from our Test.
Comentários