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.
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
<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
Resources
Other Testing Tutorials of Mine
Please feel free to have a look at other testing tutorial of mine (an excerpt):
And more…