The OpenAPI and Swagger API description format are becoming important standards to specify API contracts for RESTful web services and the Microservices trend pushes the need for such contracts even further.

Therefore arises the need for software architects, testers and developers to write tests to verify if an exposed API follows such a specified contract.

In the following tutorial I will demonstrate a setup with Java, Maven, JUnit and the designated contract-testing-library, assertj-swagger that verifies the validity of such a contract exposed by a Spring Boot application against a local stored definition.

Testing OpenAPI / Swagger Specifications
Figure 1. Testing OpenAPI / Swagger Specifications

About

To demonstrate a typical use-case for assertj-swagger I will re-use the Sprint Boot application from my tutorial “Integrating Swagger into a Spring Boot RESTful Webservice with Springfox” because it exposes a RESTful webservice as well as its service-contract as a Swagger schema.

So we will write an integration test runnable with Maven that verifies that the exposed service-contract matches a local specification read from a Swagger YAML file.

For more detailed information about the OpenAPI specifications, please feel free to visit the OpenAPI Initiative’s web-site or the OpenApi Specification GitHub repository.

Prerequisites

Using Maven, we need to add the following two dependencies to our project’s pom.xml:

  • junit as default test integration library

  • assertj-swagger for our OpenAPI / Swagger contract testing

pom.xml
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.12</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>io.github.robwin</groupId>
	<artifactId>assertj-swagger</artifactId>
	<version>0.8.1</version>
	<scope>test</scope>
</dependency>

API Test

We’re now ready to write our API tests – first of all we need an OpenAPI / Swagger scheme as local contract…

After all, our project’s directory structure should look like this (the classes in src/main/java are from the tutorial mentioned above):

.
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── com
    │           └── hascode
    │               └── tutorial
    │                   ├── Application.java
    │                   ├── CurrentDateController.java
    │                   └── FormattedDate.java
    └── test
        ├── java
        │   └── it
        │       └── RestApiSchemaIntegrationTest.java
        └── resources
            └── swagger-contract.yaml

OpenAPI / Swagger Schema

This is an excerpt from our local contract file in the YAML notation (JSON should be working, too).

swagger: '2.0'
[..]
basePath: /
tags:
  -
    name: current-date-controller
    description: 'Current Date Controller'
  -
    name: basic-error-controller
    description: 'Basic Error Controller'
paths:
  '/currentdate/{pattern}':
    get:
      tags:
        - current-date-controller
      summary: formatCurrentDate
      operationId: formatCurrentDateUsingGET
      consumes:
        - application/json
      produces:
        - '*/*'
      parameters:
        -
          name: pattern
          in: path
          description: pattern
          required: true
          type: string
      responses:
        '200':
          description: OK
          schema:
            $ref: '#/definitions/FormattedDate'
[..]

Integration Test

This is our integration test. It has some Spring Boot specials so that the full web context is started and a random port is assigned for the web application.

The relevant part happens inside the @Test annotated test method.

package it;

import com.hascode.tutorial.Application;
import io.github.robwin.swagger.test.SwaggerAssertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = Application.class)
public class RestApiSchemaIntegrationTest {

  @LocalServerPort
  int port;

  @Test
  public void validateThatImplementationMatchesDocumentationSpecification() {
    String apiContract = RestApiSchemaIntegrationTest.class.getResource("/swagger-contract.yaml").getPath();
    System.out.println(apiContract);
    String swaggerSchemaUrl = String.format("http://localhost:%d/v2/api-docs", port);
    SwaggerAssertions.assertThat(swaggerSchemaUrl)
        .isEqualTo(apiContract);
  }
}

Running the Test

We may now run our test in the console like this:

$ mvn integration-test
[INFO] Scanning for projects...
[INFO]
[INFO] ------------< com.hascode.tutorial:assertj-swagger-testing >------------
[INFO] Building dateconv-rest-service 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[..]
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running it.RestApiSchemaIntegrationTest
[..]
2018-08-31 18:46:03.918  INFO 14244 --- [           main] it.RestApiSchemaIntegrationTest          : Started RestApiSchemaIntegrationTest in 21.952 seconds (JVM running for 28.206)
/data/project/assertj-swagger-tutorial/target/test-classes/swagger-contract.yaml
2018-08-31 18:46:04.601  INFO 14244 --- [           main] io.swagger.parser.Swagger20Parser        : reading from http://localhost:37589/v2/api-docs
2018-08-31 18:46:04.948  INFO 14244 --- [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-08-31 18:46:04.949  INFO 14244 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-08-31 18:46:04.990  INFO 14244 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 41 ms
2018-08-31 18:46:06.700  INFO 14244 --- [           main] io.swagger.parser.Swagger20Parser        : reading from /data/project/assertj-swagger-tutorial/target/test-classes/swagger-contract.yaml
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 36.444 s - in it.RestApiSchemaIntegrationTest
2018-08-31 18:46:14.496  INFO 14244 --- [       Thread-2] ConfigServletWebServerApplicationContext : Closing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2bd2b28e: startup date [Fri Aug 31 18:45:44 CEST 2018]; root of context hierarchy
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ assertj-swagger-testing ---
[INFO] Building jar: /data/project/assertj-swagger-tutorial/target/assertj-swagger-testing-1.0.0.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.0.2.RELEASE:repackage (default) @ assertj-swagger-testing ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Troubleshooting

  • “java.lang.NoClassDefFoundError: io/swagger/models/RefResponse
    at it.RestApiSchemaIT.validateThatImplementationMatchesDocumentationSpecification(RestApiSchemaIT.java:25)
    Caused by: java.lang.ClassNotFoundException: io.swagger.models.RefResponse
    at it.RestApiSchemaIT.validateThatImplementationMatchesDocumentationSpecification(RestApiSchemaIT.java:25)”
    We need to add the following dependency to our project’s pom.xml:

    <dependency>
      <groupId>io.swagger</groupId>
      <artifactId>swagger-models</artifactId>
      <version>1.5.21</version>
    </dependency>

Tutorial Sources

Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:

git clone https://github.com/hascode/assertj-swagger-testing.git