Contact Us 1-800-596-4880

Example: Testing APIkit

Overview

This document contains a basic example on testing APIkit-based applications. There are two basic ways to test an application based on APIkit: Going through the APIkit router, or not going through the APIkit router.

The difference resides in what you are trying to test. Clearly, if you go through the APIkit router you have a more complete test than if you do not; however, not going through the router shields your test from API-related information, allowing you to test only the business logic of the API.

In this example, we go through the APIkit router.

Aside of what is contained in the XML configuration, the APIkit router handles some tasks under the hood:

  • Routing by resource/method/content-type

  • Validation of input payloads, headers, query-params, uri-params

  • Auto-transformation (serialization) of the response payload

To perform these tasks, the APIkit Router constantly reads the api.raml file. Thus, going through the APIkit Router helps you maintain consistency between the XML configuration that implements the API and the RAML definition file.

Example Test

Create a new Mule Studio project and create a RAML definition file in the api folder (for details on how to do this, see the APIkit Tutorial).

This test only works on MUnit version 3.5-M3-SNAPSHOT and above.

The RAML Definition

#%RAML 0.8
---
title: Sample API

/munit:
  displayName: Login
  get:
    description: Login through signed request
    responses:
      200:
        body:
          application/json:
  post:
    description: Login through signed request
    responses:
      201:
        body:
          application/json:
  put:
    description: Login through signed request
    responses:
      201:
        body:
          application/json:
  delete:
    description: Login through signed request
    responses:
      200:
        body:
          application/json:

The API Implementation

Below is the result after you generate the flows for the RAML definition:

 <apikit:config name="api-config" raml="api.raml" consoleEnabled="true" consolePath="console" doc:name="Router" />
    <apikit:mapping-exception-strategy name="api-apiKitGlobalExceptionMapping" doc:name="Mapping Exception Strategy">
        <apikit:mapping statusCode="404">
            <apikit:exception value="org.mule.module.apikit.exception.NotFoundException" />
            <set-property propertyName="Content-Type" value="application/json" doc:name="Property" />
            <set-payload value="{ &quot;message&quot;: &quot;Resource not found&quot; }" doc:name="Set Payload" />
        </apikit:mapping>
        <apikit:mapping statusCode="405">
            <apikit:exception value="org.mule.module.apikit.exception.MethodNotAllowedException" />
            <set-property propertyName="Content-Type" value="application/json" doc:name="Property" />
            <set-payload value="{ &quot;message&quot;: &quot;Method not allowed&quot; }" doc:name="Set Payload" />
        </apikit:mapping>
        <apikit:mapping statusCode="415">
            <apikit:exception value="org.mule.module.apikit.exception.UnsupportedMediaTypeException" />
            <set-property propertyName="Content-Type" value="application/json" doc:name="Property" />
            <set-payload value="{ &quot;message&quot;: &quot;Unsupported media type&quot; }" doc:name="Set Payload" />
        </apikit:mapping>
        <apikit:mapping statusCode="406">
            <apikit:exception value="org.mule.module.apikit.exception.NotAcceptableException" />
            <set-property propertyName="Content-Type" value="application/json" doc:name="Property" />
            <set-payload value="{ &quot;message&quot;: &quot;Not acceptable&quot; }" doc:name="Set Payload" />
        </apikit:mapping>
        <apikit:mapping statusCode="400">
            <apikit:exception value="org.mule.module.apikit.exception.BadRequestException" />
            <set-property propertyName="Content-Type" value="application/json" doc:name="Property" />
            <set-payload value="{ &quot;message&quot;: &quot;Bad request&quot; }" doc:name="Set Payload" />
        </apikit:mapping>
    </apikit:mapping-exception-strategy>
    <flow name="api-main" doc:name="api-main">
        <http:inbound-endpoint doc:name="HTTP" exchange-pattern="request-response" host="localhost" path="api" port="9090" responseTimeout="999999" />
        <apikit:router config-ref="api-config" doc:name="APIkit Router" />
        <exception-strategy ref="api-apiKitGlobalExceptionMapping" doc:name="Reference Exception Strategy" />
    </flow>
    <flow name="get:/munit:api-config" doc:name="get:/munit:api-config">
        <set-payload value="#['GET RESPONSE']" doc:name="Set Payload" />
    </flow>
    <flow name="put:/munit:api-config" doc:name="put:/munit:api-config">
        <set-payload value="#['PUT RESPONSE']" doc:name="Set Payload"/>
    </flow>
    <flow name="delete:/munit:api-config" doc:name="delete:/munit:api-config">
        <set-payload value="#['DELETE RESPONSE']" doc:name="Set Payload"/>
    </flow>
    <flow name="post:/munit:api-config" doc:name="post:/munit:api-config">
        <set-payload value="#['POST RESPONSE']" doc:name="Set Payload"/>
    </flow>
In the above code, the default inbound port has been changed to 9090.

The MUnit Test

These sections contain a breakdown of the important parts of the test. You can find a complete version of the XML at the bottom of the page.

The MUnit configuration:

<munit:config doc:name="Munit configuration" mock-connectors="false" mock-inbounds="false"/>
<spring:beans>
  <spring:import resource="classpath:api.xml"/>
</spring:beans>
In the MUnit configuration, it is essential that you set mock-connectors and mock-inbounds to false. By default, MUnit sets these values to true (since usually you don’t want to enable inbound endpoints), so you must manually set these values to false; otherwise the test does not work.

An actual test:

<munit:test name="api-test-get" description="Test">
        <munit:set payload="#['']" doc:name="Set Message"/>
        <http:outbound-endpoint exchange-pattern="request-response" host="localhost" port="9090" path="api/munit" method="GET" doc:name="HTTP"/>
        <object-to-string-transformer doc:name="Object to String"/>
        <munit:assert-true message="The HTTP Status code is not correct!" condition="#[messageInboundProperty('http.status').is(eq('200'))]" doc:name="Assert True"/>
        <munit:assert-on-equals message="The response payload is not correct!" expectedValue="#['\&quot;GET RESPONSE\&quot;']" actualValue="#[payload]" doc:name="Assert Equals"/>
</munit:test>

As you can see, we are using an http:outbound-endpoint to trigger the test. Make sure that exchange-pattern is set to request-response. This enables you to use the HTTP outbound endpoint to define everything you need in order to hit a resource of your API (HTTP verbs, headers, paths, MIME types, etc.). In this example, we cover only the verb.

The two assertions in the test:

<munit:assert-true message="The HTTP Status code is not correct!" condition="#[messageInboundProperty('http.status').is(eq('200'))]" doc:name="Assert True"/>

<munit:assert-on-equals message="The response payload is not correct!" expectedValue="#['\&quot;GET RESPONSE\&quot;']" actualValue="#[payload]" doc:name="Assert Equals"/>

This example illustrates one of the most basic assertions needed in a test like this:

  • Validate the HTTP status code

  • Validate the returned payload

Full test config XML:

 <munit:config doc:name="Munit configuration" mock-connectors="false" mock-inbounds="false"/>
    <spring:beans>
        <spring:import resource="classpath:api.xml"/>
    </spring:beans>

    <munit:test name="api-test-get" description="Test">
        <munit:set payload="#['']" doc:name="Set Message"/>
        <http:outbound-endpoint exchange-pattern="request-response" host="localhost" port="9090" path="api/munit" method="GET" doc:name="HTTP"/>
        <object-to-string-transformer doc:name="Object to String"/>
        <munit:assert-true message="The HTTP Status code is not correct!" condition="#[messageInboundProperty('http.status').is(eq('200'))]" doc:name="Assert True"/>
        <munit:assert-on-equals message="The response payload is not correct!" expectedValue="#['\&quot;GET RESPONSE\&quot;']" actualValue="#[payload]" doc:name="Assert Equals"/>
    </munit:test>

    <munit:test name="api-test-post" description="Test">
        <munit:set payload="#['']" doc:name="Set Message"/>
        <http:outbound-endpoint exchange-pattern="request-response" host="localhost" port="9090" path="api/munit" method="POST" doc:name="HTTP"/>
        <object-to-string-transformer doc:name="Object to String"/>
        <munit:assert-true message="The HTTP Status code is not correct!" condition="#[messageInboundProperty('http.status').is(eq('201'))]" doc:name="Assert True"/>
        <munit:assert-on-equals message="The response payload is not correct!" expectedValue="#['\&quot;POST RESPONSE\&quot;']" actualValue="#[payload]" doc:name="Assert Equals"/>
    </munit:test>

    <munit:test name="api-test-put" description="Test">
        <munit:set payload="#['']" doc:name="Set Message"/>
        <http:outbound-endpoint exchange-pattern="request-response" host="localhost" port="9090" path="api/munit" method="PUT" doc:name="HTTP"/>
        <object-to-string-transformer doc:name="Object to String"/>
        <munit:assert-true message="The HTTP Status code is not correct!" condition="#[messageInboundProperty('http.status').is(eq('201'))]" doc:name="Assert True"/>
        <munit:assert-on-equals message="The response payload is not correct!" expectedValue="#['\&quot;PUT RESPONSE\&quot;']" actualValue="#[payload]" doc:name="Assert Equals"/>
    </munit:test>

    <munit:test name="api-test-delete" description="Test">
        <munit:set payload="#['']" doc:name="Set Message"/>
        <http:outbound-endpoint exchange-pattern="request-response" host="localhost" port="9090" path="api/munit" method="DELETE" doc:name="HTTP"/>
        <object-to-string-transformer doc:name="Object to String"/>
        <munit:assert-true message="The HTTP Status code is not correct!" condition="#[messageInboundProperty('http.status').is(eq('200'))]" doc:name="Assert True"/>
        <munit:assert-on-equals message="The response payload is not correct!" expectedValue="#['\&quot;DELETE RESPONSE\&quot;']" actualValue="#[payload]" doc:name="Assert Equals"/>
    </munit:test>

Conclusion

This example shows how to trigger hits to the endpoint exposed by APIkit, and why it is important to test the endpoint in this manner. As always, you can make your test as sophisticated as you deem necessary by using the tools that MUnit offers: Mock, Spy, Verification, Assertion, etc.

Last Minute Comment

You can use APIkit in Mule runtime with or without API Gateway. API Gateway Runtime 1.3 - 2.x has a mule domain named api-gateway.

MUnit does not yet support Mule domains; thus, a test created for an APIkit-based application running on API Gateway may fail. If this happens, it’s probably because the global configurations defined in the Domain were not visible to MUnit during the run of the test. To make your tests work, duplicate those global configurations inside your MUnit Test Suite file.