[Azure] Mocking with Azure API Management
Azure API Management
Azure API Management (APIM) is one of the many tools offered by Azure.
It offers an intermediate API layer between external accesses and the internal infrastructure, allowing the activation/deactivation of policies and features with little impact on the client or backend services, sometimes not requiring any changes at all.
As an intermediate layer, it also allows changing the backend infrastructure without impacting the client, for example when the backend URI changes.
Some resources available on APIM are monitoring and metrification, security and rate limiting, caching, data transformation, different forms of authentication and authorization, and much more. This article focuses mainly on relevant features for mocking.
Mocking
Mocking is the practice of simulating a behaviour without executing the real operation. It is a common practice for unit testing, but the concept can be applied for API Management as well.
For example, APIM allows the creation of an endpoint that returns a fixed JSON on its response. Then a frontend developer can make requests to the endpoint and receive a JSON as if it was the real deal, without requiring a functional backend.
Then, when the backend is ready, the same endpoint can be updated forward requests to the new backend, and return the backend response instead of the fixed JSON. This converts a mock into a real feature without changing the frontend.
Reinforcing APIM concepts
For this article, it is recommended to have basic knowledge about APIM policies. The main concepts are:
Each APIM endpoint has a general configuration block called frontend, with data such as the endpoints path, which content type and payloads are acceptable, etc.
Then, all requests might go through 4 sections. Each section contains policy definitions registered with XML tags:
- Inbound: Policies, processes and transformations that happens before the request is sent to the backend.
- Backend: Policies and configurations to send the requests to the backend service.
- Outbound: Policies, processes and transformations for the backend response.
- On-Error: Optional section for handling errors that might happen during the request
Mocking a response
There are different strategies to mock in APIM. One of the simplest is to use the <return-response> policy inside the inbound section.
This policy returns a result before the backend and outbound section are called.
<policies>
<inbound>
<return-response>
<set-status code="200" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>{ "data": "ok" }</set-body>
</return-response>
</inbound>
<backend><!-- your policies --></backend>
<outbound><!-- your policies --></outbound>
<on-error><!-- your policies --></on-error>
</policies>
Putting a pure JSON inside the <set-body> tag might trigger a warning about potential syntax problems, but this kind of syntax does not interfere with the feature.
The content body and type should be changed as needed. Microsoft has an article giving more details about the <set-body>.
Validating the payload content
APIM offers native schema validators for JSON, XML and SOAP formats using the <validate-content> policy.
The JSON schema is defined by the JSON Schema standard. Some platforms can generate the JSON Schema based on a JSON content, such as jsonschema.net.
For the validation to take effect it is necessary to register a Representation on the endpoint, indicating what content-type and schema is expected in the request.
Representations can be configured on the Frontend block -> Request tab -> Representation section. The schema should be put into the Definitions attribute.
Configuring the Representation is mandatory, otherwise APIM will return a BadRequest with the body below. The content type displayed changes based on the payload.
{
"statusCode": 400,
"message": "Unspecified content type application/json is not allowed."
}
After registering the Representation and Definition, add the policy as follows:
<policies>
<inbound>
<validate-content
unspecified-content-type-action="prevent"
max-size="128"
size-exceeded-action="ignore">
<content
type="application/json"
validate-as="json"
action="prevent" />
</validate-content>
</inbound>
<backend><!-- your policies --></backend>
<outbound><!-- your policies --></outbound>
<on-error><!-- your policies --></on-error>
</policies>
Using global schema
Another option to include a schema is using the APIM schema registration. When using this mechanism, an attribute schema-id has to be added to the <content> tag, with the registered schema identifier.
Please note that schema-id is not required when the schema is registered as a Definition of a Representation.
<policies>
<inbound>
<validate-content
unspecified-content-type-action="prevent"
max-size="128"
size-exceeded-action="ignore">
<content
type="application/json"
validate-as="json"
action="prevent"
schema-id="your-schema-id" />
</validate-content>
</inbound>
<backend><!-- your policies --></backend>
<outbound><!-- your policies --></outbound>
<on-error><!-- your policies --></on-error>
</policies>
Different responses and StatusCodes
It is possible to establish conditional flows inside APIM by using the <choose> policy, and access context data (such as data sent in the request) through the context variable.
By combining the two features, APIM can activate different flows and return predictable responses without the need to create additional endpoints, for example by checking how a flag was set in a request.
The code below shows that by checking a "mock" flag, sent as a query of the URI.
<policies>
<inbound>
<choose>
<when condition="@(context.Request.OriginalUrl.Query.GetValueOrDefault("mock") == "error")">
<return-response>
<set-status code="500" />
<set-header name="Content-Type" exists-action="override">
<value>plain/text</value>
</set-header>
<set-body>Internal server error</set-body>
</return-response>
</when>
<when condition="@(context.Request.OriginalUrl.Query.GetValueOrDefault("mock") == "no_access")">
<return-response>
<set-status code="403" />
</return-response>
</when>
<otherwise>
<return-response>
<set-status code="200" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>{ "result": "ok" }</set-body>
</return-response>
</otherwise>
</choose>
</inbound>
<backend><!-- your policies --></backend>
<outbound><!-- your policies --></outbound>
<on-error><!-- your policies --></on-error>
</policies>
Calling https://{apim_domain}/{api_path}/{endpoint_path}?mock={flag} and changing the {flag} value, the result is:
- mock=error: StatusCode 500 and an error text message
- mock=no_access: StatusCode 403 (Forbidden)
- No flag or other values: StatusCode 200 and JSON result
In conclusion
APIM is a cloud tool with rich features that can be combined with great versatility, abstracting the internal infrastructure and possibly absorbing responsabilities that would be resolved on the backend.
Do you know better solutions to create mocks with or without APIM? Feel free to message me on LinkedIn!