When it comes to building and delivering modern web applications, the importance of continuous integration cannot be overemphasized. With the rapid pace of software development, ensuring that every change in your codebase is thoroughly tested and seamlessly integrated into your project is essential for maintaining a robust and dependable application.

In this guide, I will walk you through the process of setting up a Spring Boot API project, creating test cases with SpringBootTest, and automating the testing process with CircleCI. By the end of this tutorial, you’ll have a solid understanding of how to rigorously test your Spring Boot applications and integrate continuous integration into your development workflow for enhanced code quality and reliability. Let’s dive in!

Prerequisites

In addition to a basic understanding of Java and Spring Boot, you will need the following to get the most from this tutorial:

Cloning the demo project

Clone the sample project by running the following command.

git clone https://github.com/CIRCLECI-GWP/spring-exchange-api.git

The sample project is an exchange rate API with three endpoints:

S/N Endpoint Method Function
1. /currency GET Get all currencies supported by the API
2. /rates GET Get all the exchange rates for all the supported currencies
3. /rates/{currency} GET Get the exchange rates for a single currency

Next, go to the root of the new folder created by the previous command (spring-exchange-api). Set up and run the project by running the following commands.

cd spring-exchange-api

mvn -B -DskipTests clean package

mvn spring-boot:run

By default, your application will be served to port 8080. Navigate to http://127.0.0.1:8080/rates in order to view the JSON response.

2022-11-21-application-running-locally

Writing tests with SpringBootTest and MockMvc

To ensure that the application works properly, the demo project already contains some test cases using @SpringBootTest and @AutoConfigureMockMvc to test the behavior of your API endpoints.

Navigate to the src/test/java/com/example/exchangeRates folder, open the file named ExchangeRatesApplicationTests.java and ensure its content is the same as the following.

package com.example.exchangeRates;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ExchangeRatesApplicationTests {

    final String[] expectedCurrencies = {"EUR", "GBP", "NGN", "USD", "YEN"};
    @Autowired
    private MockMvc mvc;
    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void assert_correct_number_of_rates_are_returned() throws Exception {

        mvc.perform(get("/rates/NGN"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.NGN").isArray())
                .andExpect(jsonPath("$.NGN", hasSize(expectedCurrencies.length - 1)));
    }

    @Test
    public void assert_400_is_returned_for_unsupported_currency() throws Exception {
        mvc.perform(get("/rates/TES"))
                .andExpect(status().isBadRequest());
    }

    @Test
    public void assert_supported_currencies_are_returned_for_currency_index_route() throws Exception {
        String[] expectedCurrencies = {"EUR", "GBP", "NGN", "USD", "YEN"};

        MvcResult mvcResult = mvc.perform(get("/currency"))
                .andExpect(status().isOk()).andReturn();

        String actualResponseBody = mvcResult.getResponse().getContentAsString();

        assertThat(actualResponseBody).isEqualToIgnoringWhitespace(
                objectMapper.writeValueAsString(expectedCurrencies));
    }
}

The test suite uses @Autowired to inject dependencies into the test class, including Spring MockMVC and an ObjectMapper, which it uses to simulate requests to endpoints in the API.

The first test ensures that for a supported currency (NGN in this case), an HTTP_OK response is returned. Additionally, it ensures that an array for the provided currency is returned and that the size of the array matches the expected length.

The second test ensures that for an unsupported currency, an HTTP_BAD_REQUEST response is returned.

The final test mocks a request to get all the supported currencies. In addition to ensuring that an HTTP_OK response is returned, it grabs the response and ensures that it matches the expected list of supported currencies.

Running the tests locally

You can run your tests with the following command.

mvn test

With a test suite, you now have a higher level of confidence that your code works as expected — a key prerequisite for an efficiently automated pipeline. With this in place, you can add your CircleCI configuration to automate everything, from checkout to testing.

Adding CircleCI configuration

In your project root directory, open the config.yml file within the .circleci folder and ensure its content is the same as the code below.

# Use the latest 2.1 version of CircleCI pipeline process engine.
version: 2.1

jobs:
  build-and-test:
    docker:
      - image: cimg/openjdk:19.0.1
    steps:
      - checkout
      - run:
          name: Build
          command: mvn -B -DskipTests clean package
      - run:
          name: Test
          command: mvn test

workflows:
  build-and-test:
    jobs:
      - build-and-test

This file holds the configuration required to automate your Spring Boot tests on CircleCI.

The first thing we do is to specify the version of CircleCI pipeline process engine. Always specify the latest version (2.1 at the time this article was written).

After specifying the CircleCI version, we specify a job named build-and-test. This job has two key blocks. The docker block specifies the image(s) we need for our build process to run successfully. Here we specify the CircleCI’s OpenJDK Docker image.

In the steps block, we do three things:

  1. Checkout the latest code from our GitHub repository
  2. Build the application and download the specified dependencies
  3. Run the application tests

The build-and-test job is executed as specified in the workflows block.

Next, we need to set up a repository on GitHub and link the project to CircleCI. See this post for help pushing your project to GitHub. GitLab users can also follow along by pushing to GitLab and connecting their GitLab repo to CircleCI.

Adding the project to CircleCI

Log into your CircleCI account. If you signed up with your GitHub account, all your repositories will be displayed on your project’s dashboard.

Next to your spring-exchange-api project. Click Set Up Project.

2022-11-21-select-project

You will be prompted to input the name of the branch on GitHub where your configuration file is housed. Choose appropriately as shown here:

2022-11-21-select-config

Finally, click Set Up Project to start the build. CircleCI will use your configuration file to trigger the build. Your build will start running and complete successfully.

2022-11-21-build-successful

Click build-and-test. You will see the job steps and the status of each job as shown in the screenshot below.

2022-11-21-view-build-test-job

Now, on every change to your project, CircleCI will automatically execute the Spring Boot tests defined in your test file, allowing you to quickly add new features or fix bugs with increased confidence in the health and integrity of your application.

Conclusion

In this tutorial, I have shown you how to set up a continuous integration pipeline for automatically testing a Spring Boot application using CircleCI. Continuous integration builds on software development best practices in testing and version control to automate the process of adding new features to software. This removes the risk of human error causing downtime in the production environment. It also adds an additional level of quality control and assurance to the software being maintained. Give continuous integration a try and make code base bottlenecks a thing of the past for your team!

The entire codebase for this tutorial is available on GitHub.

Happy coding!


Oluyemi is a tech enthusiast with a background in Telecommunication Engineering. With a keen interest in solving day-to-day problems encountered by users, he ventured into programming and has since directed his problem solving skills at building software for both web and mobile. A full stack software engineer with a passion for sharing knowledge, Oluyemi has published a good number of technical articles and blog posts on several blogs around the world. Being tech savvy, his hobbies include trying out new programming languages and frameworks.

Read more posts by Olususi Oluyemi