Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Integration testing Spring Boot based Microservices

I have read many of the guides about working with Spring Boot and RESTful services, and many of them contain information about running unit tests, most notably "Building an Application with Spring Boot". However, I haven't seen anything that gives an example on how to unit test a Spring Boot application that consumes/depends on other Spring Boot applications, as is common in cloud micro-services architecture. So, for example, we have the following Spring Boot services:

ServiceMediator, Adapter1, Adapter2

ServiceMediator calls Adapter1 or Adapter2, depending on the input.

Is there a way to start up the Spring Boot services Adapter1 and Adapter2 before starting and testing the ServiceMediator in a Spring JUnit test?

like image 257
LouRoy Avatar asked Apr 17 '15 16:04

LouRoy


2 Answers

The process-exec-maven-plugin could be helpful as allows to start multiple java processes at pre-integration-test phase (as standard Spring Boot apps), and it automatically takes care of shutting them down in the post-integration-test phase.

NOTE: The integration test should be run at the integration-test phase for that the maven-failsafe-plugin should be configured with spring-boot-maven-plugin see. Then to run our integration tests verify or higher maven Lifecycle should be targeted, because the integration-test phase is in fact located between package and verify Lifecycles see Default Lifecycles.

The following maven (pom.xml) configuration worked for me:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>1.3.5.RELEASE</version>
            <executions>                    
                <execution>
                    <id>pre-integration-test</id>
                    <goals>
                        <goal>start</goal>
                    </goals>
                    <configuration>
                        <skip>${integration-tests.skip}</skip>
                    </configuration>
                </execution>
                <execution>
                    <id>post-integration-test</id>
                    <goals>
                        <goal>stop</goal>
                    </goals>
                    <configuration>
                        <skip>${integration-tests.skip}</skip>
                    </configuration>
                </execution>
            </executions>
        </plugin>           

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.19.1</version>
            <configuration>
                <skip>${integration-tests.skip}</skip>                  
                <includes>
                    <include>**/*IT.java</include>
                </includes>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>                        
                </execution>                    
            </executions>
        </plugin>

        <plugin>
            <groupId>com.bazaarvoice.maven.plugins</groupId>
            <artifactId>process-exec-maven-plugin</artifactId>
            <version>0.7</version>
            <executions>                    
                <execution>
                    <id>switchboard-process</id>
                    <phase>pre-integration-test</phase>
                    <goals>
                        <goal>start</goal>
                    </goals>
                    <configuration>
                        <name>accounts-service</name>
                        <workingDir>/../../micro-service</workingDir>
                        <waitForInterrupt>false</waitForInterrupt>                          
                        <arguments>
                            <argument>java</argument>
                            <argument>-jar</argument>
                            <argument>${basedir}/../micro-service/target/micro-service-${project.version}-exec.jar</argument>
                        </arguments>
                    </configuration>
                </execution>
                <!--Stop all processes in reverse order-->
                <execution>
                    <id>stop-all</id>
                    <phase>post-integration-test</phase>
                    <goals>
                        <goal>stop-all</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Having an Integration Test class (WebServerIT) in test.java folder:

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = WebServerApp.class)
@WebIntegrationTest("server.port:0")
public class WebServerIT {

    @Autowired
    private WebApplicationContext webServerAppContext;

    private MockMvc webServerMockMvc;

    @Before
    public void setUp() {
        System.out.println("the test is set up");
        webServerMockMvc = MockMvcBuilders.webAppContextSetup(webServerAppContext).build();
    }

    /**
     * This test calls the WebServer's endpoint (/accounts/123456789) which in turn calls the micro-service rest api 
     * which is started using the process-exec-maven-plugin, otherwise the test would fail.
     */
    @Test
    public void testWebServerInteractionWithMicroService() throws Exception {
        this.webServerMockMvc.perform(get("/accounts/123456789"))
                .andExpect(status().isOk());
    }
}
like image 93
kmarabet Avatar answered Oct 06 '22 10:10

kmarabet


package controller;


import static org.junit.Assert.assertThat;

import java.io.File;
import java.net.URL;

import mediator.CLPApplication;

import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = CLPApplication.class)
@WebAppConfiguration
@IntegrationTest()
public class ControllerTest {

    @Value("${adapter.dependency.jar.location}")
    private String adapterDependencyJarLocation;

    @Value("${adapter.dependency.jar.name}")
    private String adapterDependencyJarName;

    @Value("${adapter.url}")
    private String adapterURL;

    @Value("${mediator.url}")
    private String mediatorURL;

    private URL mediator;
    private URL adapter;
    private RestTemplate template;
    Process process = null;

    @Before
    public void setUp() throws Exception {

        adapter = new URL(adapterURL);
        template = new TestRestTemplate();

        //
        // Start the Atomic adapter
        // 
        System.out.println(adapterDependencyJarLocation);
        System.out.println("Starting Adapter");

        try {
            process = new ProcessBuilder("java", "-jar", adapterDependencyJarName)
                .directory(new File(adapterDependencyJarLocation)).start();

            // Try connecting 5 times with a 5 second pause between each
            // to see if it started. 
            Thread.sleep(5000);
            for(int i = 0; i <= 5; i++) {
                try{
                    System.out.println("Testing to see if Adapter is up");
                    template.getForEntity(adapter.toString(), String.class);
                    System.out.println("Adapter Started");
                    break;
                }
                catch(RestClientException rce){
                    System.out.println("It's not up yet");
                }
                Thread.sleep(5000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testMediator() throws Exception {
        mediator = new URL(mediatorURL);
        System.out.println("Calling Mediator");
        ResponseEntity<String> response = template.getForEntity(mediator.toString(), String.class);
        System.out.println(response.getBody());
        // Getting back JSON, so check to see if it starts with an open bracket
        assertThat(response.getBody(), Matchers.startsWith("{"));
    }

    @After
    public void tearDown() {
        if(process != null) {
            process.destroy();
        }
    }
}
like image 41
LouRoy Avatar answered Oct 06 '22 11:10

LouRoy