If you have tried creating your own custom apiman policy, you may have had a bit of difficulty creating useful JUnit tests for it. Many policies require various apiman runtime components to be available. It can be super annoying trying to use something like mockito to create mock versions of everything your policy needs. Even for simple policies you really just want a quick and effective way to test the implementation within a reasonably "real world" harness.
Well you’ve probably guessed by now that I’m about to show you how it’s done! (OK fine, how it will be done in the next release of apiman - 1.1.3.Final)
In this post I’ll explain (and show!) you how to write a unit test for your apiman policy, using the new Policy Tester junit framework we’ve created.
First you need a custom policy!
If you haven’t created a custom apiman policy yet, have a look at the Developer Guide to learn how.
So for example, let’s say you’ve created your own simple custom policy class, and it looks like this:
public class MySimplePolicy implements IPolicy {
// ...
@Override
public Object parseConfiguration(String jsonConfiguration) throws ConfigurationParseException {
return jsonConfiguration;
}
@Override
public void apply(ApiRequest request, IPolicyContext context, Object config,
IPolicyChain<ApiRequest> chain) {
request.getHeaders().put("X-MTP-Header", "Hello World");
if (request.getHeaders().get("X-Fail-Test") != null) {
IPolicyFailureFactoryComponent failureFactory = context.getComponent(IPolicyFailureFactoryComponent.class);
chain.doFailure(failureFactory.createFailure(PolicyFailureType.Other, 42, "Failure"));
} else {
chain.doApply(request);
}
}
@Override
public void apply(ApiResponse response, IPolicyContext context, Object config,
IPolicyChain<ApiResponse> chain) {
response.getHeaders().put("X-MTP-Response-Header", "Goodbye World");
chain.doApply(response);
}
// ...
}
OK I’ve got a policy, how do I test it?
Now that you’ve got your policy, you need a quick and effective way to test it. I also think it’s important for your test to run quickly and for you to be able to easily set breakpoints to debug the code. We explored using Arquillian to configure and publish an API with the custom policy to a running WildFly server. It actually works remarkably well, but the overhead of firing up a WildFly server just to test a single policy seemed excessive. That work will likely lead into a separate testing effort focused on testing larger integration scenarios.
OK enough - let’s get to the test! The first thing you need is to pull the Policy Tester dependency into your project’s pom.xml:
@TestingPolicy(MySimplePolicy.class)
public class MyTestPolicyTest1 extends ApimanPolicyTest {
@Test
@Configuration("{}")
public void testGet() throws Throwable {
// Send a test HTTP request to the API (resulting in executing the policy).
PolicyTestResponse response = send(PolicyTestRequest.build(PolicyTestRequestType.GET, "/some/resource")
.header("X-Test-Name", "testGet"));
// Now do some assertions on the result!
Assert.assertEquals(200, response.code());
EchoResponse entity = response.entity(EchoResponse.class);
Assert.assertEquals("GET", entity.getMethod());
Assert.assertEquals("/some/resource", entity.getResource());
Assert.assertEquals("testGet", entity.getHeaders().get("X-Test-Name"));
// Assert the request header that was added by the policy
Assert.assertEquals("Hello World", entity.getHeaders().get("X-MTP-Header"));
// Assert the response header was added by the policy
Assert.assertEquals("Goodbye World", response.header("X-MTP-Response-Header"));
}
}
Great - now just create a new junit test and make sure it extends the ApimanPolicyTest
base test class. In that junit test you’ll need to sprinkle in a few annotations
provided by the apiman Policy Testing framework. At a minimum you will need to
include the @TestingPolicy
and @Configuration
annotations. Both of these annotations
can be specified at either the Class or Method level. The former tells the testing
framework which policy is being tested. The latter describes the policy configuration
that should be used for the test.
Just look at the example, already:
@TestingPolicy(MySimplePolicy.class)
public class MyTestPolicyTest1 extends ApimanPolicyTest {
// ...
@Test
@Configuration("{}")
public void testGet() throws Throwable {
// Send a test HTTP request to the API (resulting in executing the policy).
PolicyTestResponse response = send(PolicyTestRequest.build(PolicyTestRequestType.GET, "/some/resource")
.header("X-Test-Name", "testGet"));
// Now do some assertions on the result!
Assert.assertEquals(200, response.code());
EchoResponse entity = response.entity(EchoResponse.class);
Assert.assertEquals("GET", entity.getMethod());
Assert.assertEquals("/some/resource", entity.getResource());
Assert.assertEquals("testGet", entity.getHeaders().get("X-Test-Name"));
// Assert the request header that was added by the policy
Assert.assertEquals("Hello World", entity.getHeaders().get("X-MTP-Header"));
// Assert the response header was added by the policy
Assert.assertEquals("Goodbye World", response.header("X-MTP-Response-Header"));
}
// ...
}
So what’s going on here?
Let me tell you! For each test method in your junit test, we’ll actually spin up a fully
functional apiman API Gateway. We’ll also publish a test API that’s configured with
your custom policy (and using the policy configuration you specified in the @Configuration
annotation). After that, it’s a simple matter of sending one or more simulated HTTP
requests to the gateway. You do that by sending a PolicyTestRequest
to the send
method.
Easy peasy!
Note that it’s pretty easy to create a PolicyTestRequest
- there’s a nice little fluent
builder you can use to create it. The builder allows you to set the HTTP verb, the resource
path, and any HTTP headers.
What about the back-end API?
Yeah, that’s a good point. Assuming your policy doesn’t produce a failure, the API Gateway we’re using for the test needs to "invoke" a back-end API and return the result. We simulate this rather than actually going out and making a REST request. By default, we create a simple Echo back-end API which bundles up all the information in the REST request (including anything your policy may have added to the request) and builds a JSON response that includes all that information. This is handy because it allows you to verify that, for example, any HTTP headers your policy added to the request actually made it through to the back-end API.
Now are you ready for an advanced topic? If not I understand, you can just hit the Back button on your browser!
Still here? Great! Another thing you can do is actually provide your own simulated
back-end API. This is necessary sometimes when your policy does something
specific with, for example, the API response payload. You may actually need your
test to respond in a certain way. To accomplish this all you need to do is use the
@BackEndApi
annotation, providing a Class that implements the
IPolicyTestBackEndApi
interface. You do that, and we’ll use your simulated
back end API for the test instead of the echo API! :)
What would that look like? Something like this:
@TestingPolicy(MySimplePolicy.class)
public class MyTestPolicyTest1 extends ApimanPolicyTest {
// ...
@Test
@Configuration("{}")
@BackEndApi(MyCustomBackEndApiImpl.class)
public void testGet() throws Throwable {
// Send a test HTTP request to the API (resulting in executing the policy).
PolicyTestResponse response = send(
PolicyTestRequest
.build(PolicyTestRequestType.GET, "/some/resource")
.header("X-Test-Name", "testGet")
);
// Now do some assertions on the result!
MyCustomBackEndApiResponseBean entity = response.entity(MyCustomBackEndApiResponseBean.class);
// Do some more assertions here using the entity from above!
}
// ...
}
And then perhaps your custom back end implementation class might look like this:
public class MyCustomBackEndApiImpl implements IPolicyTestBackEndApi {
@Override
public PolicyTestBackEndApiResponse invoke(ApiRequest request, byte[] requestBody) {
// Create a valid API response for this request, and then configure it.
ApiResponse apiResponse = new ApiResponse();
apiResponse.setCode(200);
apiResponse.setMessage("OK");
apiResponse.getHeaders().put("Date", new Date().toString());
apiResponse.getHeaders().put("Server", "apiman.policy-test");
apiResponse.getHeaders().put("Content-Type", "text/plain");
// Let's respond with a classic "Hello World" for the response body
String body = "Hello World";
PolicyTestBackEndApiResponse response = new PolicyTestBackEndApiResponse(apiResponse, body);
return response;
}
}
Alright - if you made it this far thanks for reading!!
/post