API TESTING WITH JAVA AND SPRING BOOT TEST – PART 1: THE BASIC SETUP

API TESTING WITH JAVA AND SPRING BOOT TEST – PART 1: THE BASIC SETUP

Luiz Martins · November 30, 2022

Here at Mercedes-benz.io (MB.io), we collaborate as multiple multi-disciplinary teams (nothing new to a Scrum-based organization).

I'm part of one of those teams, responsible for a Java-based microservice. Since this microservice sends data to a back-office application, we need to test the APIs provided by it.

With that said, we had the challenge to build a new API test framework from scratch.

In this series of articles we'll show:

  • How we choose the tools
  • The process of creating and improving the test framework
  • Pipeline configuration
  • Test report

Choosing the language and framework

The main reason why we went for a Java-based framework is that the background of our team is Java and the microservice itself is written in this language. Our team is composed of Java developers, so they can contribute to building the right solution for our team and also maintain the code base of the test repository in case it's needed.

The test framework we've chosen to be the base of our solution was Rest Assured.io. The reason behind it is that rest assured is already used in several projects within our tribe at MB.io and is also widely used and maintained in the community.

We also added Spring Boot to organize, structure, and be the foundation of the project.

Setting up the project

Step 1: Create the project

We choose Maven as our dependencies manager. Now, the first thing to do is to add the dependencies we need in our project.

Tip: You can use the spring boot initializer to get the basic pom.xml file with the spring initial setup.

After the initial setup, we need to add the dependencies for the rest-assured test framework and other things we'll use to make our lives easier.

The pom.xml file should be something like that:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>api.test.java</groupId>
    <artifactId>apitest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>api-test-java</name>
    <description>Api Tests</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-text</artifactId>
            <version>1.9</version>
        </dependency>

        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>5.1.1</version>
            <exclusions><!-- https://www.baeldung.com/maven-version-collision -->
                <exclusion>
                    <groupId>org.apache.groovy</groupId>
                    <artifactId>groovy</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.groovy</groupId>
                    <artifactId>groovy-xml</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>json-schema-validator</artifactId>
            <version>5.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Show code in Github Gist

With this, we should be able to start organizing our project.

Step 2: Changing the Main class

The Main class should be changed to a SpringBootApplication. And the main method must be configured to run as a SpringApplication.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {

    public static void main(String[] args) {

        SpringApplication.run(Main.class, args);
    }
}

Show code in Github Gist

Step 3: Create a Service to manage your API

To abstract access and configure the requests in one single place, we can create a new Service and take advantage of it.

Here is the place to set the proper configuration of the requests.

Let's create a new method here to abstract the use of a post request. In this post request, we'll provide the URL and the JSON body as parameters, so the file will be something like this:

package org.example.services;

import com.fasterxml.jackson.databind.JsonNode;
import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

@Slf4j
@Service
public class YourApiService {

    private RequestSpecification spec;

    @PostConstruct
    protected void init() {

        // On init you can set some global properties of RestAssured
        RestAssured.useRelaxedHTTPSValidation();

        spec = new RequestSpecBuilder().setBaseUri("https://reqres.in").setBasePath("/api").build();
    }

    public Response postRequest(String endpoint, JsonNode requestBody) {

        return RestAssured.given(spec)
            .contentType(ContentType.JSON)
            .body(requestBody)
        .when()
            .post(endpoint);
    }
}

Show code in Github Gist

Note: We'll return the full response to be able to validate what we want within the test itself.

As you can see in the file above, we also take advantage of the built-in RequestSpecification that Rest-assured has to set the baseURI and basePath for this service. This is a smart way to configure your service because if you have more than one service in our test framework, each of them can have its setup and host.

Step 4: Add a test case

First things first, let's add the proper annotations to be a spring boot JUnit 5 test class.

@ExtendWith(SpringExtension.class)
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)

After that, let's add a constructor method and assign the Service to be used in our test as a class variable.

private final YourApiService yourApiService;

public ApiTest(YourApiService yourApiService) {

    this.yourApiService = yourApiService;
}

Now we are good to start adding the test cases here. Let's do that.

The postRequest method expects two parameters:

  • the endpoint we want to send the data as a String;
  • the request body as a JsonNode.

The first thing we want to do is create an object to send in the request body of our request. We'll take advantage of the jackson-databind library to help us with the object mapping.

@Test
public void testCreateUser() throws JsonProcessingException {

    ObjectMapper mapper = new ObjectMapper();

    String body = "{\"name\": \"Luiz Eduardo\", \"job\": \"Senior QA Engineer\"}";
    JsonNode requestBody = mapper.readTree(body);
}

Now, we need to make the request and validate what we want. Let's add that to our test case. The final results should be something like that:

package api.test.java.tests;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.restassured.response.Response;
import org.example.services.YourApiService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class ApiTest {

    private final YourApiService yourApiService;

    public ApiTest(YourApiService yourApiService) {

        this.yourApiService = yourApiService;
    }

    @Test
    public void testCreateUser() throws JsonProcessingException {

        ObjectMapper mapper = new ObjectMapper();

        String body = "{\"name\": \"Luiz Eduardo\", \"job\": \"Senior QA Engineer\"}";
        JsonNode requestBody = mapper.readTree(body);

        Response res = yourApiService.postRequest("/users", requestBody);
        assertThat(res.statusCode(), is(equalTo(201)));
    }
}

Show code in Github Gist

Note: Bear in mind that this is just the first iteration, we'll improve the code to keep the responsibilities within the respective classes.

What we've seen so far:

  • The project's basic setup
  • How to structure the project
  • How to abstract the requests in the service layer
  • The first functional test case

This is the end of part 1 of this series of articles. The next part will cover:

  • A short refactor on the object mapping
  • Improve the response validation
  • Property files

See you soon!

Photo by Nubelson Fernandes on Unsplash


Luiz Martins