DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Deployment
  4. Microservices Integration Tests With Hoverfly and Testcontainers

Microservices Integration Tests With Hoverfly and Testcontainers

Learn how to test your microservices and integration efforts using these handy frameworks.

Piotr Mińkowski user avatar by
Piotr Mińkowski
CORE ·
May. 01, 19 · Tutorial
Like (3)
Save
Tweet
Share
12.63K Views

Join the DZone community and get the full member experience.

Join For Free

Building good integration tests of a system consisting of several microservices may be quite a challenge. Today I'm going to show you how to use such tools like Hoverfly and Testcontainers to implement such tests. I have already written about Hoverfly in my previous articles, as well as about Testcontainers. If you are interested in an intro to these framework you may take a look on the following articles:

  • Testing REST APIs with Hoverfly
  • Testing Spring Boot Integration with Vault and Postgres using the Testcontainers Framework

Today we will consider the system consisting of three microservices, where each microservice is developed by a different team. One of these microservices trip-management is integrating with two others: driver-management and passenger-management. The question is how to organize integration tests under these assumptions. In that case we can use one of the interesting features provided by Hoverfly — the ability to run it as a remote proxy. What does it mean in practice? It is illustrated in the picture below. The same external instance of a Hoverfly proxy is shared between all microservices during JUnit tests. Thedriver-management and passenger-management microservices are testing their own methods exposed for use by trip-management, but all the requests are sent through a Hoverfly remote instance which acts as a proxy. Hoverfly will capture all the requests and responses sent during the tests. On the other hand, trip-management is also testing its methods, but the communication with other microservices is simulated by a remote Hoverfly instance based on previously captured HTTP traffic.

Hoverfly Proxy

We will use Docker for running a remote instance of the Hoverfly proxy. We will also use Docker images of microservices during the tests. That's why we need the Testcontainers framework, which is responsible for running an application container before starting integration tests. So, the first step is to build a Docker image of the driver-management and passenger-management microservices.

1. Building a Docker Image

Assuming you have successfully installed Docker on your machine, and you have set environment variables DOCKER_HOST and DOCKER_CERT_PATH, you may use io.fabric:docker-maven-plugin for it. It is important to execute the build goal of that plugin just after the package Maven phase, but before the integration-test phase. Here's the appropriate configuration inside Maven pom.xml.

<plugin>
    <groupId>io.fabric8</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <configuration>
        <images>
            <image>
                <name>piomin/driver-management</name>
                <alias>dockerfile</alias>
                <build>
                    <dockerFileDir>${project.basedir}</dockerFileDir>
                </build>
            </image>
        </images>
    </configuration>
    <executions>
        <execution>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>build</goal>
            </goals>
        </execution>
    </executions>
</plugin>

2. Application Integration Tests

Our integration tests should be run during the integration-test phase, so they must not be executed during test, before building an application fat jar and Docker image. Here's the appropriate configuration with maven-surefire-plugin.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <excludes>
            <exclude>pl.piomin.services.driver.contract.DriverControllerIntegrationTests</exclude>
        </excludes>
    </configuration>
    <executions>
        <execution>
            <id>integration-test</id>
            <goals>
                <goal>test</goal>
            </goals>
            <phase>integration-test</phase>
            <configuration>
                <excludes>
                    <exclude>none</exclude>
                </excludes>
                <includes>
                    <include>pl.piomin.services.driver.contract.DriverControllerIntegrationTests</include>
                </includes>
            </configuration>
        </execution>
    </executions>
</plugin>

3. Running Hoverfly

Before running any tests we need to start an instance of Hoverfly in proxy mode. To achieve it we use a Hoverfly Docker image. Because Hoverfly has to forward requests to the downstream microservices by host name, we create a Docker network and then run Hoverfly in this network.

$ docker network create tests
$ docker run -d --name hoverfly -p 8500:8500 -p 8888:8888 --network tests spectolabs/hoverfly

A Hoverfly proxy is now available for me (I'm using Docker Toolbox) under the address 192.168.99.100:8500. We can also take a look admin web console available under the address http://192.168.99.100:8888. Under that address you can also access the HTTP API, what is described in the next section.

4. Including Test Dependencies

To enable Hoverfly and Testcontainers for our test we first need to include some dependencies to the Maven pom.xml. Our sample application is built on top of Spring Boot, so we also include a Spring Test project.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.10.6</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.specto</groupId>
    <artifactId>hoverfly-java</artifactId>
    <version>0.11.1</version>
    <scope>test</scope>
</dependency>

5. Building Integration Tests on the Provider Site

Now, we can finally proceed to the JUnit test implementation. Here's the full source code of test for driver-management microservice, but some things needs to explained. Before running our test methods we first starta Docker container of the application using Testcontainers. We use GenericContainer annotated with @ClassRule for that. Testcontainers provides an API for interaction with containers, so we can easily set a target a Docker network and container hostname. We will also wait until the application container is ready for use by calling the method waitingFor on GenericContainer.
The next step is to enable a Hoverfly rule for our test. We will run it in capture mode. By default, Hoverfly is trying to start a local proxy instance, that's why we provide a remote address of the existing instance already started using a Docker container.

The tests are pretty simple. We will call endpoints using Spring's TestRestTemplate. Because the request must finally be proxied to the application container we use its hostname as the target address. All traffic is then captured by Hoverfly.

public class DriverControllerIntegrationTests {
 
    private TestRestTemplate template = new TestRestTemplate();
 
    @ClassRule
    public static GenericContainer appContainer = new GenericContainer<>("piomin/driver-management")
            .withCreateContainerCmdModifier(cmd -> cmd.withName("driver-management").withHostName("driver-management"))
            .withNetworkMode("tests")
            .withNetworkAliases("driver-management")
            .withExposedPorts(8080)
            .waitingFor(Wait.forHttp("/drivers"));
 
    @ClassRule
    public static HoverflyRule hoverflyRule = HoverflyRule
            .inCaptureMode("driver.json", HoverflyConfig.remoteConfigs().host("192.168.99.100"))
            .printSimulationData();
 
    @Test
    public void testFindNearestDriver() {
        Driver driver = template.getForObject("http://driver-management:8080/drivers/{locationX}/{locationY}", Driver.class, 40, 20);
        Assert.assertNotNull(driver);
        driver = template.getForObject("http://driver-management:8080/drivers/{locationX}/{locationY}", Driver.class, 10, 20);
        Assert.assertNotNull(driver);
    }
 
    @Test
    public void testUpdateDriver() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        DriverInput input = new DriverInput();
        input.setId(2L);
        input.setStatus(DriverStatus.UNAVAILABLE);
        HttpEntity<DriverInput> entity = new HttpEntity<>(input, headers);
        template.put("http://driver-management:8080/drivers", entity);
        input.setId(1L);
        input.setStatus(DriverStatus.AVAILABLE);
        entity = new HttpEntity<>(input, headers);
        template.put("http://driver-management:8080/drivers", entity);
    }
 
}

Now, you can execute the tests while building the application using the mvn clean verify command. The sample application source code is available on GitHub in the sample-testing-microservices repository under the remote branch.

6. Building Integration Tests on the Consumer Site

In the previous section, we discussed the integration tests implemented on the consumer site. There are two microservices driver-management and passenger-management, that expose endpoints invoked by the third microservice trip-management. The traffic generated during the tests has already been captured by Hoverfly. It is a very important thing in that sample, because each time you build the newest version of a microservice Hoverfly is refreshing the structure of the previously recorded requests. Now, if we run the tests for the consumer application ( trip-management) it fully bases the newest version of the requests generated during these tests on the microservices on the provider site. You can check out the list of all the requests captured by Hoverfly by calling endpoint http://192.168.99.100:8888/api/v2/simulation.
Here are the integration tests implemented inside trip-management. They are also using  remote Hoverfly proxy instance. The only difference is in the running mode, which is a simulation. It tries to simulate requests sent to driver-management and passenger-management based on the traffic captured by Hoverfly.

@SpringBootTest
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TripIntegrationTests {
 
    ObjectMapper mapper = new ObjectMapper();
 
    @ClassRule
    public static HoverflyRule hoverflyRule = HoverflyRule
            .inSimulationMode(HoverflyConfig.remoteConfigs().host("192.168.99.100"))
            .printSimulationData();
 
    @Autowired
    MockMvc mockMvc;
 
    @Test
    public void test1CreateNewTrip() throws Exception {
        TripInput ti = new TripInput("test", 10, 20, "walker");
        mockMvc.perform(MockMvcRequestBuilders.post("/trips")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(mapper.writeValueAsString(ti)))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.id", Matchers.any(Integer.class)))
                .andExpect(MockMvcResultMatchers.jsonPath("$.status", Matchers.is("NEW")))
                .andExpect(MockMvcResultMatchers.jsonPath("$.driverId", Matchers.any(Integer.class)));
    }
 
    @Test
    public void test2CancelTrip() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.put("/trips/cancel/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(mapper.writeValueAsString(new Trip())))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.id", Matchers.any(Integer.class)))
                .andExpect(MockMvcResultMatchers.jsonPath("$.status", Matchers.is("IN_PROGRESS")))
                .andExpect(MockMvcResultMatchers.jsonPath("$.driverId", Matchers.any(Integer.class)));
    }
 
    @Test
    public void test3PayTrip() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.put("/trips/payment/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .content(mapper.writeValueAsString(new Trip())))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.id", Matchers.any(Integer.class)))
                .andExpect(MockMvcResultMatchers.jsonPath("$.status", Matchers.is("PAYED")));
    }
 
}

Now, you can run the command mvn clean verify on the root module. It runs the tests in the following order: driver-management, passenger-management and trip-management.

Testing microservice Integration Docker (software) application Spring Framework Requests

Published at DZone with permission of Piotr Mińkowski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Distributed Tracing: A Full Guide
  • Assessment of Scalability Constraints (and Solutions)
  • Implementing PEG in Java
  • 5 Steps for Getting Started in Deep Learning

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: