Today, I built a small test bed for unit-testing Flow Services in webMethods’ Integration Server. If you develop Java Services for IS and follow the best practice of “no business logic in the service layer”, you can unit-test your logic in isolation in a plain old Java environment and deploy the library to IS, which then only acts as the service bus and simply forwards service calls to the library. However, if you create Flow Services directly in IS, which can get complicated pretty fast, I found no built-in tool for testing their logic. Here’s an example of a small Flow Service:
So I went ahead and created a local Java project, in which I could unit-test the remote services. I know network access in unit-tests is considered a “boundary” which should not be crossed, because tests run slower and may fail due to network problems. However, I would rather have a suite of (relatively) slow running tests for my important services than have no tests at all and see programs fail due to regression. And debugging services in IS is no fun at all! Calling the IS services directly from Java is surprisingly easy. Software AG Designer generates all the needed source code for you. Right-click on the service and choose Generate Code
. Then follow the steps in the wizard:
The generated code is executable directly, as long as you add references to IS’ client.jar
and enttoolkit.jar
to your Java project.
The service call itself is implemented in this simple method:
public static IData invoke(Context context, IData inputDocument)
throws IOException, ServiceException
{
IData out = context.invoke("PACKAGE", "NAME", inputDocument);
IData outputDocument = out;
return outputDocument;
}
However, as you can see above, the service accepts and returns IData
objects, whose construction is not that easy and should not be scattered throughout your test code. Even providing a simple value requires at least the following code. And the code for getting the value out of IData
to be able to call an assertion on it looks similar.
IData out = IDataFactory.create();
IDataCursor idc = out.getCursor();
idc.insertAfter("errorFlag", "true");
idc.destroy();
So, to make life easier for me, I spent some time and developed a small environment, in which I can work with plain old Java objects for service input and output in my tests and assert against the public fields of these objects, like this example of a simple date conversion service demonstrates:
protected ConvertDateToN8 sut;
protected ConvertDateToN8.Input input;
@Test
public void shouldConvertValidDate() throws Exception
{
input.Date = "2008-10-15";
ConvertDateToN8.Output output = sut.call(input);
assertThat(output.DateN8, is("20081015"));
}
The service class looks like this:
public class ConvertDateToN8 extends ServiceCall<ConvertDateToN8.Input, ConvertDateToN8.Output>
{
public class Input extends ServiceInput
{
public String Date;
}
public class Output extends ServiceOutput
{
public String DateN8;
}
public ConvertDateToN8(String host)
{
super(host, "Common", "ConvertDateToN8");
}
public Input createInput()
{
return new Input();
}
@Override
protected Output createOutput()
{
return new Output();
}
}
The only thing that needs to be added to the project to test another service is a class like the one above. I can simply define the input and output of the service in nested classes and everything else is magically built with generics and reflection. Here is an example of converting the generic Input
object to IData
in class ServiceCall
:
protected IData createPipelineFromInput(ServiceInput input)
{
IData pipeline = IDataFactory.create();
IDataCursor idc = pipeline.getCursor();
Class<?> c = input.getClass();
for (Field publicField : c.getFields())
{
try
{
String fieldName = FieldNameConverter.convertToPipelineName(publicField.getName());
Object fieldValue = FieldConverter.convertToPipeline(publicField.getType().getName(), publicField.get(input));
IDataUtil.put(idc, fieldName, fieldValue);
}
catch (Exception e)
{
e.printStackTrace();
}
}
idc.destroy();
return pipeline;
}
Even more complex data structures like objects or arrays can be constructed by extending the classes FieldConverter
and PipelineConverter
which encapsulate the access to IData
:
public class Input extends ServiceInput
{
public String errorFlag;
public String errorCode;
public String errorMessage;
public NaturalErrorInfo ERROR_INFO;
}
You can take a look at the implementation for converting the above input to and from IData
in the source code, which can be downloaded below. Feel free to take a look at it and contact me for questions or additions!
Hi Stefan,
Can you please help in getting is-client.jar , I was able to find the entoolkit in C:\SoftwareAG\common\lib\ext.
Thank you,
Dharmendra D
Hi Dharmendra, I use Everything to search for files on my disk. Maybe that helps.