Mule is an enterprise service bus (ESB) and integration framework. In Mule, we define flows and sub-flows in order to integrate applications and orchestrate services.These flows contain endpoints to integrate different applications or services. These endpoints can be HTTP, VM, JMS, etc. More details about development in mule can be found here. Below is a sample flow.
In order to write unit test cases for mule flows, mule provides an abstract JUnit test case called org.mule.tck.junit4.FunctionalTestCase that runs Mule inside a test case and manages the lifecycle of the server.More details about can be found here.
Now, while writing these test cases, we face an issue when one application makes a call to another application using HTTP endpoint. For our unit test case to run independently, we need to mock this HTTP endpoint. There are few solutions available in the market like Confluex, or wireMock but I did not want to increase the technology footprint of my application and thought of a simpler in-house solution.
To solve this problem, I used the embedded Jetty Server in my Functional Test Case.
public abstract class BaseTest extends FunctionalTestCase { static Server mockServer; protected abstract int getPortNumber(); @Before public void createServer() throws Exception { mockServer = new Server(getPortNumber()); mockServer.setHandler(new MockHttpRequestHandler()); mockServer.start(); } @After public void stopServer() throws Exception { mockServer.stop(); } protected String get(String endpointURL, String contentType) throws URISyntaxException, ClientProtocolException, IOException { HttpClient hc = new DefaultHttpClient(); HttpGet get = new HttpGet(); get.setURI(new URI(endpointURL)); get.setHeader("Content-Type", contentType); HttpResponse resp = hc.execute(get); return EntityUtils.toString(resp.getEntity()); } protected String post(String payload, String endpointURL, String contentType) throws URISyntaxException, ClientProtocolException, IOException { HttpClient hc = new DefaultHttpClient(); HttpPost post = new HttpPost(); post.setURI(new URI(endpointURL)); post.setHeader("Content-Type", contentType); post.setEntity(new StringEntity(payload)); HttpResponse resp = hc.execute(post); return EntityUtils.toString(resp.getEntity()); } }
Let’s understand this step by step :
public abstract class BaseTest extends FunctionalTestCase {
To write a functional test case in mule, we need to extend FunctionalTestCase, this will start a mule instance inside the test case using the xml file denied in the class extending this baseTest class, as we have not overridden the method “getConfigFiles()” in this class.
static org.eclipse.jetty.server.Server mockServer;
We make a static reference to org.eclipse.jetty.server.Server, which would act as the mock server.
protected abstract int getPortNumber();
this method needs to be implemented by the class extending this baseTest class, this is done to externalise the port number, so that multiple tests can use this base class.
@Before public void createServer() throws Exception { mockServer = new Server(getPortNumber()); mockServer.setHandler(new MockHttpRequestHandler()); mockServer.start(); }
We use this method to create a new instance of the server and define the port number on which it will run.
The most important part of the implementation here is declaring handler for the server.
Lets discuss about handlers first.
Jetty Handlers : Handlers in jetty can be used to filter requests and manipulate response and content. Using this api, we can write custom handlers and set them to the server instance.
Here is a wonderful tutorial which talks about creating custom handlers.
In our implementation we have made a class MockHttpRequestHandler extending abstractHandler.
public class MockHttpRequestHandler extends AbstractHandler { @Override public void handle(String targetUri, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String contentType = request.getHeader("Content-Type"); if(targetUri.contains("/mockTest")){ handleMockTest(response, contentType); baseRequest.setHandled(true); } } private void handleMockTest(HttpServletResponse response,String contentType)throws IOException { InputStream in = this.getClass().getClassLoader() .getResourceAsStream("/responseMockTest/file/path"); response.setContentType(contentType); String responseString = IOUtils.toString(in); response.setStatus(HttpServletResponse.SC_OK); response.getWriter().print(responseString); } }
As we can see above, AbstractHandler declares a the method handle(), which has request, response and uri. These can be used to filter requests and check the uri, mock content, based on request or uri, or any other manipulation.
In the above we test the path and manipulate response accordingly in method handleMockTest.
We can see here, we are reading the response from a file in the class path. We are also setting the status and content Type of the response.
So, we can mock any response we want for a particular request here.
Also, in the end, one thing to note here is :
baseRequest.setHandled(true);
Setting it true means, we are telling the server that we have mapped a response for the base Request, setting it to false will return in 404.
We can play around this, generalise this and make a framework for better and easier usage.
Finally, lets see a basic test case, which will use these 2 classes to mock the HTTP endpoint in a mule flow.
public class TestMockHttpEndPoint extends BaseTest { private static final String JSON_PAYLOAD = "{\"Authentication\":{\"client\":\"10000003\",\"password\":\"qbgNqpzQBwB3DfAP\"}}"; private static final String JSON_RESPONSE = "{\"status":\"success\"}"; @Override protected String[] getConfigFiles() { String[] arr = {"mule-unit-test-mock-framework.xml"}; return arr; } @Override protected int getPortNumber() { return 10002; } @Test public void shouldTestPOSTMockFramework() throws ClientProtocolException, URISyntaxException, IOException { Assert.assertEquals( JSON_RESPONSE, post(JSON_PAYLOAD, "http://localhost:10001/mockTest", "application/json")); } @Test public void shouldTestGETMock() throws ClientProtocolException, URISyntaxException, IOException { get("http://localhost:10001/mockTest", "application/json"); } }
Here, the flow “mule-unit-test-mock-framework.xml” is being tested, this mule app has inbound HTTP endpoint running on port 10001 and path /mockTest.
We are running our mock Jetty server on path 10002, as seen in the overridden method “getPortNumber()” .
This is what happens in the test:
1.) We send a JSON request to the flow being tested running at port 10001 and path /mockTest, and expect a JSON response in this flow.
2.)This flow in between makes call to outbound HTTP endpoint running at port 10002 (which we have mocked), this mock (MockHttpRequestHandler) checks the uri, and prepares the response and sends it in the flow.
So, we are able to test the flow which has dependency on other application via HTTP endpoint, without worrying about that the second application (which we have mocked) is up and running.