Introduction
The aim of the tool presented in this article is to increase the productivity of webservices testing. There are plenty of tools available for testing web services, from open source -- like Windows Forms web service clients using ServiceDescriptionImporter
-- to such monsters as XMLSpy. Yet searching the web, I couldn't find a tool that would allow for quick testing of a webservice having a complicated XML hierarchy -- i.e. not primitive types -- as input. It would be great if such a tool would allow a tester unfamiliar with the details of XML namespaces and SOAP protocol to simply test the business logic of the web service itself, without thinking about the details of XML serialization.
Using the code
The core of this tool is its use of reflection to iterate through types, generated by ServiceDescriptionImporter
after it imports WSDL file. It populates the Windows Forms TreeView with a hierarchy of input from a selected web service operation, i.e. a proxy class method. Although this could seem like an easy task, I've faced a number of challenges when trying to build a working solution:
- It should be possible to edit "writable" nodes, like text, in the hierarchy. This would actually edit the property of some object, which in turn might be a property of some other parent object
- Although TreeView would list a full hierarchy of input objects, only some properties and sub-properties need to be "enabled" in different test scenarios, i.e. TreeView with checkboxes
- Some of the properties in the hierarchy can be arrays of objects, adding complexity to populating these properties with values. There is also a variable number of items that could populate the array
The are 2 main classes that this tool uses to achieve its goals: WebServTreeNode
and WSDLImporter
. WSDLImporter
contains static methods for importing of WSDL and parsing resulting objects. WebServTreeNode
is an extension of System.Windows.Forms.TreeNode
; it is used to store and populate the objects and their container objects. To make parsing of the object easier, the process was divided between 2 methods. parseWebParamType
initially parses the object, which could be an array. parseWebParamProps
parses the properties of non-array objects.
private static void parseWebParamType(object pObject,
WebServTreeNode pNode, PropertyInfo pPropInfo, Array pParentArray)
{
Type tObjectType =
(pParentArray != null) ?
pParentArray.GetType() : tObjectType = pObject.GetType();
if (tObjectType.IsArray)
{
tObjectType = tObjectType.GetElementType();
if (tObjectType.IsArray)
throw new NotSupportedException(
NotSupportedExceptionType.MultiDimArrays);
object oNewContainerObject = Helper.invokeConstructor(tObjectType);
if (pParentArray != null)
pParentArray.SetValue(oNewContainerObject, 0);
WebServTreeNode nNewNode =
new WebServTreeNode(String.Format("{0}[]",
tObjectType.Name), WebServTreeNodeType.ArrayElement, null, null);
nNewNode.ArrayObject = pParentArray;
nNewNode.ArrayIndex = 0;
pNode.Nodes.Add(nNewNode);
nNewNode.ValueType = tObjectType;
nNewNode.Value = oNewContainerObject;
parseWebParamProps(oNewContainerObject, nNewNode);
}
else
{
parseWebParamProps(pObject, pNode);
}
}
private static void parseWebParamProps(object pObject, WebServTreeNode pNode)
{
Type tObjectType = pObject.GetType();
foreach (PropertyInfo pi in tObjectType.GetProperties())
{
WebServTreeNodeType propNodeType = WebServTreeNodeType.Element;
if (!Helper.isTypeWritable(pi.PropertyType))
{
if (pi.PropertyType.IsArray)
propNodeType = WebServTreeNodeType.Array;
else
propNodeType = WebServTreeNodeType.Container;
}
WebServTreeNode nNewNode =
new WebServTreeNode(pi.Name, propNodeType, pObject, pi);
pNode.Nodes.Add(nNewNode);
if (propNodeType == WebServTreeNodeType.Container ||
propNodeType == WebServTreeNodeType.Array)
{
object oNewPropObject = null;
Array oNewPropArrayObject = null;
if (propNodeType == WebServTreeNodeType.Array)
{
if (pi.PropertyType.GetElementType().IsArray)
throw new NotSupportedException(
NotSupportedExceptionType.MultiDimArrays);
oNewPropArrayObject =
Array.CreateInstance(pi.PropertyType.GetElementType(), 1);
}
else
oNewPropObject = Helper.invokeConstructor(pi.PropertyType);
nNewNode.Value =
(oNewPropArrayObject != null) ?
oNewPropArrayObject : oNewPropObject;
parseWebParamType(oNewPropObject,
nNewNode, pi, oNewPropArrayObject);
}
else
{
nNewNode.ValueType = pi.PropertyType;
nNewNode.Value = Helper.invokeConstructor(pi.PropertyType);
}
}
}
As you can see, the main task of these methods is to create instances of WebServTreeNode
of appropriate type --i.e. WebServTreeNodeType
enumeration -- and populate it with references to the object it should populate. The logic of editing properties of the objects as well as "cloning" array element nodes is placed within methods and properties of WebServTreeNode
, which proved to be quite a reasonable design decision.
Points of interest
Although the functionality of the tool is all about web services, the "core" of its source code is all about reflection. Object parsing code is not the most effective. It still has some bugs and "scenarios" it does not support, but it was a great exercise in object-orientation, allowing a beginner to learn the intricacies of .NET object structure. Plus, it already proved to be a great helper in testing my current project's webservices. On the other hand, I hope that this tool will prove to be interesting -- at least as idea, if not its source code -- for webservices developers who are interested in .NET and are searching for a way to unit-test webservices. I think that the main part of the source code is clean enough to be easily extendable for new webservice testing scenarios.
History
- 17 June, 2007 -- Original version posted