Definition-Based API Mocking, Simulation, and Testing with Microcks - Demo Application
This codecentric demo application accompanies a blog series about Microcks, and illustrates the tool's usage for API mocking and testing on the basis of API definitions expressed with OpenAPI. We rely on Testcontainers to run the mock operations and execute tests against it leveraging REST-assured. This approach allows for consistent and automated integration testing both local and in CI/CD pipelines.
In addition, this demo comprises a module that implements an example API to the extent that it can serve for Microcks-based contract testing.
By means of the example API, we are able to showcase several of Microcks' core features:
- Microcks Testcontainers module
- Multi-artifacts
- Dispatchers: Inferred URI PARTS dispatcher, JSON BODY dispatcher, and SCRIPT dispatcher with context expressions
- Dynamic mock content with function expressions
- Programmatic usage of Microcks' own API for mock preparation beyond characteristics from API definitions
- Interactive testing of contract conformance
The example requires Maven and Java 21 or greater. Its implementation resides in the
integration-tests
module and comprises a
test suite with a total of 14
REST-assured API tests which are executed against a Microcks testcontainer, whose preparation routine is implemented in
the
InfrastructureSetup
class.
To spin up the testcontainer and execute the tests, run mvn clean test
either from the directory in which the root
pom.xml
file resides or, alternatively, from the module's sub-directory with the module's own
pom.xml
.
In order to leverage Microcks and the prepared example API for interactive contract testing, first start the
example API's demo implementation by issuing the terminal command mvn spring-boot:run
from within
the directory of the api-implementation-app
module.
Next, start the Microcks testcontainer from either the demo's root directory or the integration-tests
sub-directory
with the terminal command mvn clean test -Dtest="InfrastructureSetup"
. This command causes the application to pause
after container start and also prints the URI at which the UI of the Microcks server running inside the container is
reachable from a local browser.
With both the example API's demo implementation and Microcks server running, it becomes possible to leverage Microcks for the API's contract testing following these steps in the Microcks UI:
- Create a new conformance test.
- Specify
http://host.testcontainers.internal:${PORT}
as Test Endpoint with${PORT}
being replaced by the port of the example API's demo implementation that was previously started (8081
by default).http://host.testcontainers.internal
is Testcontainers's base URI to enable access from a container (here: the Microcks server) to services running on the host (here: the example API's demo implementation). - Choose
OPEN API SCHEMA
as test Runner. - Open the advanced options and Add Headers to the mocked API operations as follows (for details, see the
example API's description):
GET /customer_kinds
:- Name:
api_key
- Value:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJBUElNb2NrIiwiaWF0IjoxNzIxMzY5MjQyLCJleHAiOjE3MjEzNjk1NDJ9.UqlkO_qW71spEEBZdJb6Oxe0j71U6_7Kdv6UotTbJUE
(see theapi_key_token
property in the rootpom.xml
).
- Name:
GET /customer_kinds/{id}
:- Name:
api_key
- Value:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJBUElNb2NrIiwiaWF0IjoxNzIxMzY5MjQyLCJleHAiOjE3MjEzNjk1NDJ9.UqlkO_qW71spEEBZdJb6Oxe0j71U6_7Kdv6UotTbJUE
(see theapi_key_token
property in the rootpom.xml
).
- Name:
POST /login
:- Name:
api_key
- Value:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJBUElNb2NrIiwiaWF0IjoxNzIxMzY5MjQyLCJleHAiOjE3MjEzNjk1NDJ9.UqlkO_qW71spEEBZdJb6Oxe0j71U6_7Kdv6UotTbJUE
(see theapi_key_token
property in the rootpom.xml
).
- Name:
GET /customer
:- Name:
customer_token
- Value:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJBUElNb2NrIiwiZW1haWwiOiJzb21lX2N1c3RvbWVyQGV4YW1wbGUub3JnIn0.7TECmPgKMLftw2kK_CncM_AK0r7HAY7AmkV0y7qSA5Y
(see thesome_customer_token
property in the rootpom.xml
).
- Name:
- Launch the test and observe that the API fulfills its expected contract with a score of ca. 66%. That is because the
ìnvalid_credentials
tests for thePOST /login
andGET /customer
operations fail. The rationales of these test failures are (i)POST /login
currently returning HTTP400 BAD REQUEST
instead of401 UNAUTHORIZED
for empty request bodies; and (ii)GET /customer
returning HTTP200 OK
instead of401 UNAUTHORIZED
as Microcks currently only allows for specifying headers for tested operations as a whole and not operation-specific test cases (see the example API's description for details about header semantics ofGET /customer
).
The demo application comprises an OpenAPI definition for an example API that is loaded into started Microcks testcontainers and accompanied by examples from a dedicated Postman collection.
Disclaimer: We discovered this API in the wild, and found it quite coherent, understandable, and therefore suitable to illustrate some of Microcks' core features. However, it also bears some potential for optimization and does not reflect a style of API design that we would advise.
The example API is rooted in the domain of Customer Relationship Management and provides the following operations:
GET /customer_kinds
Retrieve information about the supported kinds of customers. For its execution, this operation expects a fixed API key in the form of a JSON Web Token (JWT) to be provided upon calling the operation as value for the HTTP- header field
api_key
. An example response to a valid operation call looks as follows:
{
"1": "Private person",
"2": "Commercial enterprise",
"3": "Public institution"
}
GET /customer_kinds/{id}
Retrieve information about a certain supported kind of customer. For this purpose, the operation expects the same API key token asGET /customer_kinds
in the HTTP header fieldapi_key
. Invoking the operation for the customer kind with ID3
could yield the following response:
{
"id": "3",
"kind": "Public institution",
"createdOn": "12.07.2024 12:32:59",
"updatedOn": ""
}
POST /login
Log in a customer or other user. This operation requires the API key token in the same fashion asGET /customer_kinds
andGET /customer_kinds/{id}
and returns a user-specific JWT for use with operations that implement user-specific behavior, e.g.,GET /customer
(see below). The following listing shows a valid example request to the operation:
{
"email": "some_customer@example.org",
"password": "123456"
}
This request results in a response like
{
"customer_token": "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJBUElNb2NrIiwiZW1haWwiOiJzb21lX2N1c3RvbWVyQGV4YW1wbGUub3JnIn0.7TECmPgKMLftw2kK_CncM_AK0r7HAY7AmkV0y7qSA5Y"
}
whereas invalid credentials would result in an HTTP response with status code 401 UNAUTHORIZED
and an empty body.
GET /customer
This operation allows for the retrieval of a customer's details including first name, last name, and address. The customer is identified by submitting their specific login token (see the description of thePOST /login
operation) to the operation as a value for the HTTP header fieldcustomer_token
. Assuming that a valid login token was submitted, the operation results in an HTTP response like
{
"email": "some_customer@example.org",
"first_name": "Casey",
"last_name": "Rice",
"company_name": "",
"address": "6377 Fallon Pine, North Emoryburgh, Anguilla"
}
whereas an invalid login token would lead to an HTTP response with status code 401 UNAUTHORIZED
and an empty body.