Microservices!
In the current tech ecosystem, we could see a huge trend, for better or worse, toward microservices. Most tech companies are migrating to or implementing their enterprise software to a microservices-based architecture.
When we consider microservices architectures, a major hurdle we face is testing. How do we properly and accurately test our microservice? In a monolith-based architecture, since all the needed calls, and modules are in the same code base, it’s rather easy to mock, spy objects and write unit and integration tests.
But in microservices architectures, since a single microservice depends on a lot of outside microservices and a lot of outside services like databases, message queues, and cloud services, it's not straightforward.
One way to achieve testing in a microservice is mocking and using alternative in-memory services in-place for the real thing. For example, if one is using an Oracle database in the microservice, one would replace that with an in-memory H2 database just for writing integration tests for that service.
Even though this sounds like a solid method for testing, this does not always achieve the same output as the real production environment. There may be some advanced features that the alternative H2 database does not have but is used in the production code or the response times may not be equal to that of a real production database.
Like that, there may be small inconsistencies in the testing procedure which would maybe result in a snowball effect and lead to huge gaps between the real production environment and the integration testing.
Cometh the hour, cometh the testing framework
Testcontainers is a Test Framework which has been made as a solution to the above-mentioned problem and addresses many key areas which cannot be achieved using mocks/alternatives.
Testcontainers framework provides a set of lightweight APIs which can be utilized to spin up temporary containers of the real services which are being used in the microservice. So when considering the above example, instead of using the H2 database as an alternative, we could simply spin up an Oracle temporary container from within the integration test itself programmatically and use that to do our testing.
In our test class, we could enter the below test snippet to spin up a test-oracle container.
var orcl = new OracleContainer(DockerImageName.parse("gvenzl/oracle-xe:21-slim-faststart"));
This would create a new container using the mentioned image name and it is accessible via the orcl variable of the type OracleContainer.
We could use the orcl.start(), orcl.stop() commands to start and stop the container as we wish. All the details which are required to connect to the created test-container are available in the orcl (OracleContainer) object itself. That info could be used to connect to the container using a JDBC connection.
orcl.start();
String username = orcl.getUsername();
String password = orcl.getPassword();
int port = orcl.getOraclePort();
Connection conn = DriverManager.getConnection(url,username,password);
From a testing perspective, the above configurations could be done in the @BeforeAll or @BeforeEach init states of the testing and then we could freely add, and remove data to the created connection/test-container. After the testing is finished all we have to do is call orcl.stop( ) in the @AfterAll stage of the testing and the Testcontainers framework would do its magic and clean up the temporary containers.
A simple Java test written with Testcontainers would look like the following:
class CustomerServiceTest {
var orcl = new PostgreSQLContainer<>("gvenzl/oracle-xe:21-slim-faststart");
CustomerService customerService;
@BeforeAll
static void beforeAll() {
orcl.start();
}
@AfterAll
static void afterAll() {
orcl.stop();
}
@BeforeEach
void setUp() {
DBConnectionProvider connectionProvider = new DBConnectionProvider(
orcl.getJdbcUrl(),
orcl.getUsername(),
orcl.getPassword()
);
customerService = new CustomerService(connectionProvider);
}
@Test
void shouldGetCustomers() {
customerService.createCustomer(new Customer(1L, "George"));
List customers = customerService.getAllCustomers();
assertEquals(1, customers.size());
}
}
As seen in the above example code snippet, without making any mocks or compromises, the developer is able to test the business logic using the real thing without much too much hassle. This is the flexibility and ease of use Testcontainers provides.
Not only Databases!
As I said, in the beginning, Testcontainer framework does not only offer database solutions. Various other SQL/NoSQL databases (cosmos db, neo4j etc), messaging solutions (Kafka, RabbitMQ etc.), Cloud Services (Google Cloud, Azure) and much more are offered on the testcontainers.com/modules page. You could utilize them to accurately build your microservices’ testing needs without any compromises.
This has been a quick introduction to the world of Testcontainers, a novel idea in the microservices ecosystem. I hope you gained something from the article, any feedback is mostly appreciated. Follow for more Java/Microservices/Cloud-related articles. Cheers!
Want to Connect ?
- Medium
- LinkedIn
- Twitter
- Threads
Find me everywhere @rashm1n.
Resources
- https://testcontainers.com/
- https://github.com/testcontainers/testcontainers-java/tree/main/modules