/assertj-swagger

A Swagger assertj test library which compares a contract-first Swagger YAML/JSON file with a code-first Swagger JSON

Primary LanguageJavaApache License 2.0Apache-2.0

assertj-swagger

Build Status Coverage Status download Apache License 2 Twitter

Overview

assertj-swagger is a assertj library which compares a contract-first Swagger YAML/JSON file with a code-first Swagger JSON output (e.g. from springfox or JAX-RS Swagger). assertj-swagger allows to validate that the API implementation is in compliance with a contract specification for two test patterns: Documentation Driven Contracts and Consumer Driven Contracts.

The Documentation Driven Contracts test pattern, useful for public APIs, validates using #isEqualTo and will validate that the design first documentation contract matches the implementation in its entirety.

The Consumer Driven Contracts test pattern, useful for internal microservice APIs, validates using #satisfiesContract and will validate that the implementation provides, at minimum, the requirements of the design first consumer contract. This pattern allows for extension points in the API resources, resource methods, and models.

The library supports the Swagger v2.0 specification. assertj-swagger compares Swagger objects like Paths, Parameters and Definitions. It does not compare unimportant Swagger objects like info, descriptions or summaries.

Usage guide

Adding assertj-swagger to your project

The project is published in JCenter and Maven Central.

Maven

<repositories>
    <repository>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <id>central</id>
        <name>bintray</name>
        <url>http://jcenter.bintray.com</url>
    </repository>
</repositories>

<dependency>
    <groupId>io.github.robwin</groupId>
    <artifactId>assertj-swagger</artifactId>
    <version>0.9.0</version>
</dependency>

Gradle

repositories {
    jcenter()
}

compile "io.github.robwin:assertj-swagger:0.9.0"

Using assertj-swagger in an integration test

Using assertj-swagger is simple. For example, if you are using Spring Boot and springfox or JAX-RS Swagger, you can validate your Swagger JSON in an integration test.

The following code sample shows how to validate an API using the Documentation Driven Contract test pattern:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@IntegrationTest
@WebAppConfiguration
public class AssertjSwaggerDocumentationDrivenTest {
    @Test
    public void validateThatImplementationMatchesDocumentationSpecification(){
        String designFirstSwagger = SwaggerAssertTest.class.getResource("/swagger.yaml").getPath();
        SwaggerAssertions.assertThat("http://localhost:8080/v2/api-docs")
            .isEqualTo(designFirstSwagger);
    }
}

The following code sample shows how to validate an API using the Consumer Driven Contract test pattern:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@IntegrationTest
@WebAppConfiguration
public class AssertjSwaggerConsumerDrivenTest {
    @Test
    public void validateThatImplementationSatisfiesConsumerSpecification(){
        String designFirstSwagger = SwaggerAssertTest.class.getResource("/swagger-consumer1.yaml").getPath();
        SwaggerAssertions.assertThat("http://localhost:8080/v2/api-docs")
            .satisfiesContract(designFirstSwagger);
    }
}

Example output

For Documentation Driven Contract tests, Assertj-swagger fails a test if it finds differences between the implementation and the specification.

The following 4 assertions failed:
1) [Checking Paths]
Expecting:
  <["/api/pet", "/api/pet/findByStatus", "/api/pet/findByTags", "/api/pet/{petId}", "/api/store/order", "/api/store/order/{orderId}", "/api/user", "/api/user/createWithArray", "/api/user/createWithList", "/api/user/login", "/api/user/logout", "/api/user/{username}"]>
to contain only:
  <["/pets", "/pets/findByStatus", "/pets/findByTags", "/pets/{petId}", "/stores/order", "/stores/order/{orderId}", "/users", "/users/createWithArray", "/users/createWithList", "/users/login", "/users/logout", "/users/{username}"]>
elements not found:
  <["/pets/findByTags", "/users/logout", "/users", "/stores/order", "/users/createWithArray", "/pets", "/users/createWithList", "/pets/findByStatus", "/pets/{petId}", "/users/{username}", "/stores/order/{orderId}", "/users/login"]>
and elements not expected:
  <["/api/store/order", "/api/user", "/api/user/createWithList", "/api/pet", "/api/pet/findByTags", "/api/user/createWithArray", "/api/user/login", "/api/pet/{petId}", "/api/store/order/{orderId}", "/api/user/{username}", "/api/pet/findByStatus", "/api/user/logout"]>

2) [Checking properties of definition 'Order']
Expecting:
  <["complete", "id", "identifier", "petId", "quantity", "shipDate", "status"]>
to contain only:
  <["id", "petId", "quantity", "shipDate", "status", "complete"]>
elements not found:
  <[]>
and elements not expected:
  <["identifier"]>

3) [Checking properties of definition 'User']
Expecting:
  <["email", "firstName", "id", "identifier", "lastName", "password", "phone", "userStatus", "username"]>
to contain only:
  <["id", "username", "firstName", "lastName", "email", "password", "phone", "userStatus"]>
elements not found:
  <[]>
and elements not expected:
  <["identifier"]>

4) [Checking properties of definition 'Pet']
Expecting:
  <["category", "id", "identifier", "name", "photoUrls", "status", "tags"]>
to contain only:
  <["id", "category", "name", "photoUrls", "tags", "status"]>
elements not found:
  <[]>
and elements not expected:
  <["identifier"]>

For Consumer Driven Contract tests, Assertj-swagger fails a test if it finds missing resources, methods, models, or properties in the implementation which are required by the consumer specification.

The following 4 assertions failed:
1) [Checking Paths]
Expecting:
 <["/pets", "/pets/findByStatus", "/pets/findByTags", "/pets/{petId}", "/stores/order", "/stores/order/{orderId}", "/users", "/users/createWithArray", "/users/createWithList", "/users/login", "/users/logout", "/users/{username}"]>
to contain:
 <["/animals/{animalId}", "/pets", "/pets/findByStatus", "/pets/{petId}"]>
but could not find:
 <["/animals/{animalId}"]>

2) [Checking Definitions]
Expecting:
 <["User", "Category", "Pet", "Tag", "Order"]>
to contain:
 <["Category", "Pet", "Animal", "Tag"]>
but could not find:
 <["Animal"]>

3) [Checking properties of definition 'Pet']
Expecting:
 <["id", "category", "name", "photoUrls", "tags", "status"]>
to contain:
 <["photoUrls", "extraProperty", "name", "id", "category", "tags", "status"]>
but could not find:
 <["extraProperty"]>

4) [Checking property 'extraProperty' of definition 'Pet']
Expecting actual not to be null

Using assertj-swagger in a unit test

If you are using the spring-framework and springfox, Spring’s MVC Test framework can also be used to validate the Swagger JSON output against your contract-first Swagger specification.
That way you can make sure that the implementation is in compliance with the design specification.

The following code sample shows how to write a unit test using the Documentation Driven Contract test pattern:

@Test
public void validateThatImplementationFitsDesignSpecification() throws Exception {
    String designFirstDocumentationSwaggerLocation = Swagger2MarkupTest.class.getResource("/swagger.yaml").getPath();

    MvcResult mvcResult = this.mockMvc.perform(get("/v2/api-docs")
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andReturn();

    String springfoxSwaggerJson = mvcResult.getResponse().getContentAsString();
    SwaggerAssertions.assertThat(new SwaggerParser().parse(springfoxSwaggerJson)).isEqualTo(designFirstDocumentationSwaggerLocation);
}

The following code sample shows how to write a unit test using the Consumer Driven Contract test pattern:

@Test
public void validateThatImplementationFitsDesignSpecification() throws Exception {
    String designFirstConsumerSwaggerLocation = Swagger2MarkupTest.class.getResource("/swagger-consumer1.yaml").getPath();

    MvcResult mvcResult = this.mockMvc.perform(get("/v2/api-docs")
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andReturn();

    String springfoxSwaggerJson = mvcResult.getResponse().getContentAsString();
    SwaggerAssertions.assertThat(new SwaggerParser().parse(springfoxSwaggerJson)).satisfiesContract(designFirstConsumerSwaggerLocation);
}

Customizing assertj-swagger’s behaviour

For most use cases, the default behaviour will be sufficient. However, you can override the default behaviour in various ways by placing a Java property file, /assertj-swagger.properties, at the root of your classpath. It is also possible to override the configuration in your tests; construct an instance of the SwaggerAssert class with a custom configuration if this is required.

The following overrides are available:

Disable various types of checks which are enabled by default

  • assertj.swagger.validateDefinitions=false: disable all validation of definitions

    • assertj.swagger.validateProperties=false: disable validation of properties of definitions

      • assertj.swagger.validateRefProperties=false: disable validation of reference ($ref) properties of definitions

      • assertj.swagger.validateArrayProperties=false: disable validation of array properties of definitions

      • assertj.swagger.validateByteArrayProperties=false: disable validation of byte-array properties of definitions

      • assertj.swagger.validateStringProperties=false: disable validation of string properties of definitions

    • assertj.swagger.validateModels=false: disable validation of models

  • assertj.swagger.validatePaths=false: disable all validation of endpoint definitions

  • assertj.swagger.validateResponseWithStrictlyMatch=false: allow actual contract return extra return code

Enable various types of checks which are disabled by default

The following settings are disabled by default, as they will cause schema comparisions to be too brittle for many users. They can be enabled if required.

  • assertj.swagger.validateInfo=true: enable comparison of the info section

  • assertj.swagger.validateVersion=true: enable comparison of the schema version numbers

Disable checks for certain paths or definitions in 'actual' schema

This feature is useful in development situations, where you have written a contract-first schema by hand, and are validating a contract-last schema generated by a partially-implemented API.

To ignore unimplemented endpoints, try something like:

assertj.swagger.pathsToIgnoreInExpected=\
   /v1/friends/{id},\
   /v1/groups/{groupId}

To ignore unimplemented definitions, use something like:

assertj.swagger.definitionsToIgnoreInExpected=\
   Foo,\
   Bar

To ignore unimplemented properties, use something like:

assertj.swagger.propertiesToIgnoreInExpected=\
   Foo.prop1,\
   Bar.prop2

Comparing expected and actual paths in schemas

It is occasionally useful to be able to compare schemas, where due to limitations in tools and libraries, endpoint
paths don’t align. Specifying a basePath setting in your design-first schema here won’t work — it’s only used by
Swagger tooling to generate paths at runtime, and does not form part of the logical pathname of your endpoints.
For instance, in your design-first schema, you may specify a set of endpoints and a basePath, while your generated
schema (generated from, say, Springfox) has a common prefix prepended on the endpoint paths; e.g.:

/pets/findByStatus       ! design-first schema

and

/v2/pets/findByStatus    ! actual schema

To ensure that assertj-swagger is comparing like-with-like in this situation, you could use the following in your
configuration file:

assertj.swagger.pathsPrependExpected=/v2

License

Copyright 2015 Robert Winkler

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.