Testing OpenAPI / Swagger Schema Compliance with Java, JUnit and assertj-swagger
August 31st, 2018 by Micha KopsThe 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.
Contents
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 Bitbucket repository, fork it there or clone it using Git:
git clone https://bitbucket.org/hascode/assertj-swagger-testing.git
Resources
- assertj-swagger on GitHub
- OpenAPI Initiative Website
- OpenAPI Specification
- OpenAPI Specification Repository on GitHub
Other Testing Tutorials of Mine
Please feel free to have a look at other testing tutorial of mine (an excerpt):
- Testing Java Applications for Resilience by Simulating Network Problems with Toxiproxy, JUnit and the Docker Maven Plugin
- Generating JUnit Tests with Java, EvoSuite and Maven
- Layout Testing with Galen, JUnit and Maven
- Mocking HTTP Interaction with Java, JUnit and MockServer
- Testing Asynchronous Applications with Java and Awaitility
- Mutation Testing with Pitest and Maven
- BDD Testing with Cucumber, Java and JUnit
- Running categorized Tests using JUnit, Maven and Annotated-Test Suites
- Mocking, Stubbing and Test Spying using the Mockito Framework and PowerMock
And more…
Tags: Api, contract, contract-driven, failsafe, json, junit, maven, openapi, rest, schema, spring-boot, springfox, surefire, swagger, testing, yaml