Beliebte Suchanfragen
//

How to validate your Spring Boot implementation when choosing an API first approach?

7.6.2024 | 6 minutes of reading time

When choosing to follow the API First approach, ensuring that the actual implementation follows the defined specification can present a significant challenge. Achieving alignment between the specification and implementation is crucial, as it greatly influences the developer experience and the success of the final product.

This article will guide you through a working example to achieve the aforementioned alignment using Spring Boot, REST Assured, swagger-request-validator and Open API Spec.

The API First approach is a development practice that focuses on the design of the API before implementing the rest of the system. The goal is to introduce a reliable API that can be consumed by multiple parties. It is common practice to use an API description language like the Open API specification.

To verify that the implemented system follows the API spec, accepts requests as specified, and returns the correct responses, a test framework like REST Assured in combination with a filter for the API spec validation can be one approach that offers a reliable system.

Following this article, we will create a Spring Boot application that should implement a predefined API spec and verify that the endpoints behave correctly.

Definition of the API spec

The latest version of the Open API Spec is version 3.1.0. The OPENAPI initiative offers detailed documentation for every element that can be part of the specification. For our specific example, we will be using the following specification:

openapi: "3.1.0"
info:
  version: "1.0.0"
  title: "User API"
  description: "API for retrieving user information"
paths:
  /users/{id}:
    get:
      summary: "Retrieves user information based on the user ID"
      parameters:
        - name: id
          in: path
          description: "ID of the user"
          required: true
          schema:
            type: integer
            format: int64
      responses:
        200:
          description: "Successful operation"
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: integer
                    format: int64
                  name:
                    type: string
                  email:
                    type: string
        404:
          description: "User not found"

This rudimentary User API spec has one endpoint to retrieve user information and will return it in case a user with the given ID is present. Otherwise, it will return an HTTP 404 response.

This spec can be imported as a test resource into our Spring Boot application for later use.

Implementation

At this point, we can assume that a Spring Boot setup has been initialized and that the API spec is included as a test resource.

First of all, we need to add the dependencies for REST Assured and swagger-request-validator to our pom.xml.

[...]
<name>spring-boot-api-spec-validation-rest-assured</name>  
<description>Spring Boot Open API Spec Validation with REST Assured and swagger-request-validator</description>  
<properties>  
    <java.version>21</java.version>  
    <swagger-request-validator.version>2.40.0</swagger-request-validator.version>  
</properties>  
<dependencies>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter</artifactId>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-test</artifactId>  
        <scope>test</scope>  
    </dependency>  
  
    <dependency>  
        <groupId>io.rest-assured</groupId>  
        <artifactId>rest-assured</artifactId>  
        <version>5.4.0</version>  
        <scope>test</scope>  
    </dependency>  
  
    <dependency>  
        <groupId>com.atlassian.oai</groupId>  
        <artifactId>swagger-request-validator-restassured</artifactId>  
        <version>${swagger-request-validator.version}</version>  
    </dependency>  
  
    <dependency>  
        <groupId>com.atlassian.oai</groupId>  
        <artifactId>swagger-request-validator-core</artifactId>  
        <version>${swagger-request-validator.version}</version>  
    </dependency>  
</dependencies>
[...]

The users-endpoint

We are starting off by creating a controller package to contain the REST controller that should implement the /users/{id} path of the specification.

Throughout this guide, we will make intentional mistakes to show the advantages of the verification mechanism when implementing the API.

The plain controller can look something like this:

@RestController  
public class UsersController {  
  
    @GetMapping("/user")  
    public User getUser() {  
        return new User();  
    }  
}

The dummy user class used for the test can be implemented like this:

public class User {  
}

First test

Adding a first test class offers a chance to show what the overall structure to enable the API spec validation looks like in combination with REST assured.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,  
        classes = {SpringBootApiSpecValidationRestAssuredApplication.class})  
class UsersControllerIntegrationTest {  
  
    private OpenApiValidationFilter openApiValidationFilter;  
  
    @BeforeEach  
    void setUp() {  
        var validator = OpenApiInteractionValidator.createFor("/api-specs/user-api.yaml")  
                .build();  
        openApiValidationFilter = new OpenApiValidationFilter(validator);  
        RestAssured.baseURI = "http://localhost/";  
        RestAssured.port = 8080;  
    }  
  
    @Test  
    void getUser() {  
        RestAssured.given()  
            .filter(openApiValidationFilter)  
            .when()  
                .get("/user")  
            .then()  
                .statusCode(200);  
    }  
}

Running the test yields the following error message:

com.atlassian.oai.validator.restassured.OpenApiValidationFilter$OpenApiValidationException: {
  "messages" : [ {
    "key" : "validation.request.path.missing",
    "level" : "ERROR",
    "message" : "No API path found that matches request '/user'.",
    "context" : {
      "requestPath" : "/user",
      "requestMethod" : "GET"
    }
  } ]
}

This tells us that the specification does not contain a path that is annotated with /user. Changing the request path to /users/1 as specified in the OAS file causes a new problem:

com.atlassian.oai.validator.restassured.OpenApiValidationFilter$OpenApiValidationException: {
  "messages" : [ {
    "key" : "validation.response.body.unexpected",
    "level" : "ERROR",
    "message" : "No response body is expected but one was found.",
    "context" : {
      "requestPath" : "/users/1",
      "responseStatus" : 404,
      "location" : "RESPONSE",
      "requestMethod" : "GET"
    }
  } ]
}

Since the controller is not fully implemented and returns the default body for a HTTP 404 response, the validator throws the corresponding error message above.

Implementing the spec (almost)

Offering the correct paths and returning the right response for a missing user will prevent the validator from throwing an exception.

To show another useful feature of the validator, an almost complete and spec-compliant implementation will be implemented. However, one attribute will differ from the spec, so that the validator will throw another error. For this, email is changed to emailAddress in the implementation, which returns the following error:

com.atlassian.oai.validator.restassured.OpenApiValidationFilter$OpenApiValidationException: {
  "messages" : [ {
    "key" : "validation.response.body.schema.additionalProperties",
    "level" : "ERROR",
    "message" : "Object instance has properties which are not allowed by the schema: [\"emailAddress\"]",
    "context" : {
      "requestPath" : "/users/1",
      "responseStatus" : 200,
      "location" : "RESPONSE",
      "pointers" : {
        "instance" : "/"
      },
      "requestMethod" : "GET"
    }
  } ]
}

Validation of request objects is also taking place: Adding a request object and sending a payload that does not match this specification will throw an exception during the test.

For brevity, the implementation of request validation will not be discussed in full in this article.

A PUT endpoint will be implemented to offer an option to update an existing user’s information. The specification demands a payload containing an email and a name that should replace the existing values. Sending a body containing the field username instead will throw the following error:

Object instance has properties which are not allowed by the schema: [\"username\"]

Thereby, the validator offers a powerful tool to assert that the published API is actually covered by the system implementation.

Spec Validation

Besides validating the implementation against the specification, the validator can also find errors in the API specification that are nonconforming with the OAS standard.

If the user-api.yaml were to specify the {id} parameter in the path without describing it in the following specification, the validator would throw an error.

com.atlassian.oai.validator.OpenApiInteractionValidator$ApiLoadException: Unable to load API spec from provided URL or payload:
    - paths.'/users/{id}'. Declared path parameter id needs to be defined as a path parameter in path or operation level

However, the standard conformity of the implementation should be verified beforehand using a tool like Redocly or vacuum.

The built-in validation mechanism is useful to detect errors in the provided specification even if it was imported from an external source and contains formatting issues.

Summary

As seen above, the combination of REST Assured and the swagger-request-validator can be a useful combination of tools to verify the implementation of an Open API Spec in Spring Boot using an API First approach.

The complete source code is available on GitHub.

share post

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.