Introduction
When it comes to the development of a new API, a good approach is always to define the API contract first. This can be done using OpenAPI (formerly known as Swagger). It defines the structure of the API and how to interact with it. OpenAPI uses JSON Schema to define the structure of the request and response bodies.
What's already out there?
When developing an API, it is important to validate the responses of the API. Now that we have the contract defined in an OpenAPI schema file, we can use this schema to validate the responses of the API – but how?
There are some tools out there which can validate a JSON response against a JSON schema defined in an OpenAPI schema file. One of the most popular tools for testing APIs in my social bubble is Postman. Postman is a great tool for testing APIs, but it is not OSS, it is not integrated into the IDE and some of the features I really like are behind a paywall.
So, why not use the tools we already have? I like the HTTP Client in IntelliJ IDEA a lot, because it integrates well in my favorite IDE, it uses simple text files for the definition which allows a good integration into version control systems, and compared to other tools, you can see the whole configuration and scripts in a single file.
IntelliJ HTTP Client still misses a lot of features to be a full replacement for postman, but the most important feature I was missing was the ability to validate the response against a JSON schema – until now 🥳!
HTTP Client schema check
HTTP Client schema check is a code generator to generate code for IntelliJ HTTP Client to validate responses against JSON schemas defined in OpenAPI schema files.
Its usage is simple:
- Define your API contract in an OpenAPI schema file.
- Run the code generator to generate the necessary code for IntelliJ HTTP Client.
- Use the generated code in your HTTP Client response handler script to validate the responses against the defined JSON schema.
- Profit 🎉!
Example
Let's say you have an API like the Swagger Petstore and you want to write some test for its API-Implementation.
The OpenAPI schema file petstore.yaml
is your local workspace in the directory ./schema
and you have podman or docker installed.
To have a consistent naming of the generated functions, the operationId and the response code are used to generate the function name. This implies, that the operationId is properly set in the OpenAPI schema file.
openapi: 3.0.2
info:
title: Swagger Petstore - OpenAPI 3.0
version: 1.0.19
paths:
/pet/{petId}:
get:
operationId: getPetById
parameters:
- name: petId
in: path
required: true
responses:
"200":
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
components:
schemas:
Category:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
Tag:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
Pet:
required:
- name
- photoUrls
type: object
properties:
id:
type: integer
format: int64
example: 10
name:
type: string
example: doggie
category:
$ref: '#/components/schemas/Category'
photoUrls:
type: array
xml:
wrapped: true
items:
type: string
xml:
name: photoUrl
tags:
type: array
xml:
wrapped: true
items:
$ref: '#/components/schemas/Tag'
status:
type: string
description: pet status in the store
enum:
- available
- pending
- sold
To generate the validation code, you only need to run the generator with the path to the directory containing your OpenAPI schema file:
podman run --rm -it --security-opt="label=disable" -v $(pwd)/schema:/app/openapi registry.gitlab.com/http-client-schema-check/http-client-schema-check:latest
This will end up with a file named validate-petstore.js
in the directory ./schema
which contains the generated code to validate the response of the API. All you need to do now is to import the correct function in your IntellJ HTTP Client response handler script and use it to validate the response.
GET https://petstore3.swagger.io/api/v3/pet/10
accept: application/json
> {%
import { getPetById_200 as validate } from '../schema/validate-petstore.js'
client.test("Request status is 200", function() {
client.assert(response.status === 200, "Response status is not 200 but " + response.status);
})
client.test("Response body is valid", function() {
client.assert(validate(response.body), "Response body is invalid: " + JSON.stringify(validate.errors))
})
%}
CI/CD or IntelliJ community
Thanks to the HTTP Client cli tool, you can run the HTTP Client scripts in your CI/CD pipeline or on a local container engine without purchasing a license for IntelliJ IDEA Ultimate.
If you want to run the tests on a machine which is not a workstation like in a CI/CD pipeline, you might consider building a dedicated container image for the test runs:
# tests.Containerfile
FROM registry.gitlab.com/http-client-schema-check/http-client-schema-check:latest AS generator
COPY schema/petstore.yaml /app/openapi/petstore.yaml
RUN node create-validators.js
FROM docker.io/jetbrains/intellij-http-client AS runner
COPY --from=generator /app/openapi/validate-petstore.js /workdir/schema/validate-petstore.js
COPY tests/petstore.http /workdir/tests/petstore.http
CMD ["tests/petstore.http"]
GitLab CI
Running these images in GitLab CI require some adjustments. e.g. if you want to run them scheduled:
testimage-build:
image:
name: quay.io/buildah/stable:latest
stage: test
before_script:
- 'echo "$CI_REGISTRY_PASSWORD" | buildah login --username "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY'
script:
# no expensive builds are done, skipping layer-cache
- buildah bud
-f "tests.Containerfile"
-t "$CI_REGISTRY_IMAGE/tests:$CI_COMMIT_SHORT_SHA"
.
- buildah push "$CI_REGISTRY_IMAGE/tests:$CI_COMMIT_SHORT_SHA"
testimage-run:
image:
name: "$CI_REGISTRY_IMAGE/tests:$CI_COMMIT_SHORT_SHA"
entrypoint: [""]
script:
- java $IJHTTP_JAVA_OPTS -cp "/intellij-http-client/*" "com.intellij.httpClient.cli.HttpClientMain" /workdir/tests/petstore.http --report
artifacts:
reports:
junit: reports/report.xml
needs:
- job: testimage-build
So long, and thanks for all the fish
I hope you enjoyed this post and that you will sleep even better in the future because you know, that your APIs are not only correctly implemented, but you also know that they are still up and running.
If you liked this post so much that you want to buy me a coffee, you can do so via buymeacoffee.com/blaimi
Top comments (1)
This should be a lot easier now since IntelliJ switched to GraalJS for interpreting the scripts here. Will see, what I can simplify here since the scripts code is IMHO very fragile.