In my first article, I introduced you to the advantages and disadvantages of test-driven development and highlighted the possibilities that this type of test scaling offers for software development. Today I would like to take a closer look at integration tests of MircoServices in Java.
Software tests are central components of continuous quality assurance. They can be divided into unit, integration and E2E tests. While isolated components - i.e. methods or classes - are tested in unit tests, the interaction of these components is tested in integration tests. Martin Fowler describes this as follows: "Integration tests determine if independently developed units of software work correctly when they are connected to each other".
For example, a MicroService that reads a database with the latest news could be tested in an integrated manner. The MicroService technically consists of three parts:
- The resource that receives and answers the requests
- The service that contains the processing logic
- The client who handles the procurement of the data
In terms of message evaluation, there is the client, which is responsible for reading and writing to the database, the service, which implements how the messages are to be evaluated, and the resource, which decides which response a received message request receives. These components are tested in their interaction during the integration test. The tests can also cross system boundaries, whereby a test double is often used.
Extending the monolith with middleware
MicroServices are used to split up monoliths or to extend them; if they are used to extend an existing backend, they form a middleware between the backend and the frontend. On the one hand, this has the advantage that the historically grown monolith can remain in place. On the other hand, a modern web-based frontend can be created using technologies such as Angular or React. The frontend then requests the data to be displayed from the middleware. The user interface is supplied with the content via web interfaces. It does not matter whether the content is stored in a database, uploaded via an UpLoad, provided as a PDF or in another representation. The middleware is based on web services as the central technology. The number of interfaces provided by the middleware can grow rapidly. This means that manual tests of the web services can quickly become time-consuming. Automated integration tests are therefore more suitable for MircoServices.
TestContainers is an open source library that simplifies integration testing of Docker applications. The project led by Richard North is an important contribution to the testing of current web applications.
Testing the functional components with integration tests
Integration tests are created in smaller numbers than unit tests, as they take many times longer to execute. For example, integration tests can only be created for the so-called happy path and error cases and edge cases can be omitted.
Integration tests are also generally not carried out as frequently as unit tests. Successful integration tests should not be a prerequisite for a successful build. They must therefore be carried out separately.
Unit tests are used to check the behavior of the technical program units. Automated integration tests are used to validate the functional components of the software. In this way, system components that can be separated from each other are tested, each of which performs a function for the software application. However, integration tests are also used to indirectly test system or module-wide configurations. Database access can only work if, for example, the server or other configurations of the runtime environment are correct.
E2E tests ensure user acceptance. They ensure that users achieve their goals. If web services of a middleware are tested, the application server must first be started. This is also the main reason for the longer execution time. Starting the application in particular was a key challenge during the integration test. However, applications containerized in Docker can be deployed more easily in a test environment.
Integration tests of MicroServices with the TestContainers library
In the following, I would like to outline how integration tests of MicroServices can be implemented. I use the integration test library TestContainers for this purpose. Although the website itself does not talk about MicroService tests, these can also be implemented perfectly for this case. The Docker Compose module of the library is used to start Docker services. The docker-compose.yml can be simplified to look like in code example 1:
Dateiname: docker-compose.yml version: '2' services: app: image: open-liberty volumes: - ./webServices.war:/config/dropins/ db: image: postgres volumes: - db-data:/var/lib/db volumes: db-data: {}
Two services are created in the DockerCompose configuration. The app service contains an application webServices.war, the db service is a relational database. The application that is deployed in the app container on the application server contains WebServices.
Code example 2 is a simple WebService class, as you can easily implement in Java with JAX-RS:
Dateiname: WebService.java @ApplicationScoped @Path("/services") public class WebService { @GET @Path("hello") public Response sayHello(){ return Response.ok("Hello WebService!").build(); } }
The WebService can now be called from the command line using Postman or cUrl, for example. However, it is better to implement a JUnit test class that tests the service automatically using the TestContainers API. For this purpose, a build configuration for executing the test suite can be created with a tool such as Maven or Gradle.
Code example 3 contains an implementation to test the success of the web service. It checks whether the client receives a response with status code 200:
Dateiname: WebServiceIntegrationTest.java public class WebServiceIntegrationTest { private String middlewareUrl; @ClassRule public static DockerComposeContainer environment = new DockerComposeContainer(new File("./docker-compose.yml")) .withExposedService("app_1", SERVICE_PORT, Wait.forListeningPort()); @Before public void setUp(){ middlewareUrl = environment.getServiceHost("app_1", SERVICE_PORT) + ":" + environment.getServicePort("app_1", SERVICE_PORT); } @Test public void shouldSayHello(){ WebTarget target = middlewareUrl; Response response = target.path("services/hello") .request(MediaType.APPLICATION_JSON) .get(); assertThat(response.getStatus() isEqualTo(200)); } }
Conclusion
Middleware and modern front ends go well together. Web services are used as the central technology of the middleware. This makes integration tests more difficult in the first step.
In my article, I showed you how this difficulty can be easily overcome with the TestContainers library and how you can test the individual WebServices integrated as functional components. Even if integration tests are less common than unit tests and are executed less frequently, they are still an effective means of making the development process more efficient.
Article by Mischa Siebert, Consultant at objective partner