A tutorial walking us through using the Component Test Framework to add a component test to the Simple Service, a REST based stripped back microservice
The component test framework is an open source framework which orchestrates the running of component tests, by spinning up the application under test and its external dependencies in Docker containers
All code can for this tutorial can be found on GitHub: CTF Getting Started
We use component testing to exercise some use cases that allow us to verify the component under test is ready to be integrated with the wider system. In our example we exercise a service call that results in the query of a downstream system. The component test validates that the service stands up correctly and is able to engage with external components (be these in mock or real form)
The service we are working with has been stripped back as far as possible so that we can focus on the component test.
Our simple service contains a REST controller with a single endpoint, the correct invocation of this endpoint will result in Simple Service making a REST call to the third party service. Our goal with the component test is to verify that this call is successful, reassuring us that the service is built correctly so that it can start in isolation, whilst also confirming that the service can engage with the external components. To achieve this we must start the Simple Service and components the service requires. In this example the Third Party Service must also be started. As external services can be unreliable and may have costs attached we use a mock representation. Meaning that our Component test setup will look as follows:
There are 3 steps to enabling testing of the service with the component test framework. These are:
This is our service under test, it’s stripped back as much as possible to make it easy to see how the test is working.
The service has a single REST endpoint which allows us to update the mood of a specific user. Under some conditions the service will call an external component also via REST.
@PutMapping("/users/{id}/mood/{mood}")
public Mood updateUserMood(@PathVariable Mood mood, @PathVariable Long id) {
userMoodService.processUserMood(mood);
return mood;
}
There are only 3 Moods defined, HAPPY, INDIFFERENT, and GRUMPY.
The UserMoodService has the following code to make the call to the third party service:
public void processUserMood(Mood mood) {
if (mood == Mood.GRUMPY || mood == Mood.HAPPY ) {
callThirdparty(mood.toString());
}
}
To exercise the endpoint, start the app:
mvn spring-boot:run
And then poke the endpoint with a request:
curl -i -X PUT http://localhost:8080/users/1/mood/INDIFFERENT
There are 2 things of note:
Now we understand the service let’s go back to those steps.
We make use of the spring boot maven plugin to generate the docker image.
mvn spring-boot:build-image
Because our simple service under test interacts with a third party service via REST, and it’s not practical and or reliable to connect with the real service, we must define a mock of the third party service. We will use Wiremock to create the mock.
Wiremock allows us to define the endpoints we are interested in, along with corresponding responses depending on the request and/or payload should we wish, using json configuration files.
For the third party mock to utilise wiremock with the component test framework, we only need to provide a couple of endpoint configurations for the service which we define in the test/resources/wiremock folder. In this simple example, there are 2 endpoints we must define.
The /health endpoint is required by all wiremock services we create as it is used by the framework to confirm that the mock has started successfully.
{
"request": {
"method": "GET",
"url": "/health"
},
"response": {
"status": 204
}
}
The other endpoint we define is the third party endpoint called by the service. We set this to simply return a success, in practice this response will likely be more complex, but this simple response is sufficient to illustrate the purpose.
{
"request": {
"method": "GET",
"urlPattern": "/api/thirdparty/.*"
},
"response": {
"status": 200,
"headers": {
"Content-Type": "text/plain"
},
"body": "Success"
}
}
First we pull in the framework in the pom:
<dependency>
<groupId>dev.lydtech</groupId>
<artifactId>component-test-framework</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>
Then we use the maven-surefire-plugin to configure the component test:
<profile>
<id>component</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>*CT.*</include>
</includes>
<environmentVariables>
<TESTCONTAINERS_REUSE_ENABLE>${containers.stayup}</TESTCONTAINERS_REUSE_ENABLE>
</environmentVariables>
<systemPropertyVariables>
<service.name>${project.name}</service.name>
<wiremock.enabled>true</wiremock.enabled>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
Let’s take a look at some of the key properties.
The service.name property aligns with the name of the docker container, and is used by the framework
<service.name>${project.name}</service.name>
We want to simulate a third party REST service, which we do via wiremock, the next setting enables wiremock.
<wiremock.enabled>true</wiremock.enabled>
The component tests have the suffix ‘CT’ which is specified in the pom as part of the surefire plugin configuration.
Defining the test class we see the CT suffix, but we also see the ExtendWith annotation which pulls in the framework, starts our containers and handles any Docker port mappings required.
@ExtendWith(ComponentTestExtension.class)
public class CallThirdPartyApiCT {
Initialise the wiremock and get the url of our service. This will include the port mappings which have been handled by the framework, so we need not do any complex configuration.
@BeforeEach
public void setup() {
WiremockClient.getInstance().deleteAllRequestsMappings();
serviceBaseUrl = ServiceClient.getInstance().getBaseUrl();
RestAssured.baseURI = serviceBaseUrl;
}
And the test, we use RestAssured to make the request to simple service, but we use the component test framework to query the wiremock instance, which confirms that the endpoint has been invoked.
@Test
public void testThirdPartyEndpointIsCalled() {
Response response = put("/users/1/mood/HAPPY");
log.info("Response: {}", response.getStatusCode());
RequestCriteria request = RequestCriteria.builder()
.method("GET")
.url("/api/thirdparty/HAPPY")
.build();
WiremockClient.getInstance().countMatchingRequests(request, 1);
}
Now let’s run the test referencing the profile we created in the pom, and see what happens.
mvn test -Pcomponent
The framework identifies the service under test and the components required to start up, in our case the wiremock for third party service. Both of these components are started as docker containers using test containers. The testing will not commence until the framework is happy that the services have been started, this check is done by the framework testing for the health endpoint and awaiting for a successful response. Once the framework is running, then the tests will be executed very much like standard spring boot tests would be.
Component tests are comparatively time expensive, therefore we want to use them as little as possible and validate what we can in the unit and integration phases which are closer to the base of the testing pyramid and so give faster feedback.
In the Simple service we’ve put some conditional logic in the UserMoodService, You’ll see that there is a condition to only call the third party service if the mood is either HAPPY or GRUMPY.
if (mood == Mood.GRUMPY || mood == Mood.HAPPY ) {
callThirdparty(mood.toString());
}
To validate the service is built and configured correctly we need only execute the code path that engages the third party. The business logic can be tested at the unit test level, so we get fast feedback on the logic correctness of the logic. There is no need to stand up any containers for us to validate the business logic. Therefore In our example, we would only have a single component test, and that test would update the user mood to HAPPY (or GRUMPY, but there is no need for both).
Apply these learnings to your current project and see how easy it is to get up and running with Component Testing.
This was a simple example, but the framework supports other components including:
Check out the framework directly or the Lydtech pages to find more detail on integrations.
The Component Test Framework project is available on GitHub: