Saturday, 6 June 2009

Unit Testing With XLANGMessage

As a rule I generally try to pass messages from BizTalk orchestrations into .NET components using the Microsoft.XLANGs.BaseTypes.XLANGMessage object. This is an abstract base class used by the orchestration engine to represent a message and provides a performant way of marshalling message content without having to resort to using an XMLDocument. For more detail on the benefits of XLANGMessage see Jon Flanders' post on the subject.

Anyhow, whilst using XLANGMessage is a real boon for BizTalk based applications, it does raise problems when trying to write unit tests. How do I write a test for a class that uses XLANGMessage in its interface without resorting to creating an orchestration, deploying a BizTalk app, dealing with File adapters etc?

Well the solution I have adopted is to create mock XLANGMessage and XLANGPart classes that use generics to allow a specified object type to be returned when calling the RetrieveAs method. I typically use this in conjunction with a class generated from the underlying message schema using the XSD tool. I can then write simple unit test code such as:

void Test()
{
// Create and populate a new message class instance.
// Typically this would be done by deserializing an XML string.
MyMessageClass msg = new MyMessageClass();

// Create a new mock XLANGMessage to wrap the message
MockXLANGMessage<MyMessageClass> xlang
= new MockXLANGMessage<MyMessageClass>(msg);

// Call the method on the class to be tested
// passing in the mock XLANGMessage
MyClassToBeTested.DoSomething(xlang);
}


where the class to be tested looks like:



class MyClassToBeTested
{
static void DoSomething(XLANGMessage msg)
{
MyMessageClass reader =
(MyMessageClass)msg[0].RetrieveAs(typeof(MyMessageClass));
}
}


As you can see this is pretty simple code but works very effectively. But the real meat of this is the mock classes. So what do they look like? Here are simple versions which you can enhance to flesh them out if required. The key bit is the use of the generic class which provides the flexibility to wrap any class. This is utilised in the RetrieveAs implementation.



public class MockXLANGPart<T> : XLANGPart
{
T m_obj;

public MockXLANGPart(T obj)
{
m_obj = obj;
}

public override void Dispose()
{
}

public override object GetPartProperty(Type propType)
{
throw new NotImplementedException();
}

public override Type GetPartType()
{
throw new NotImplementedException();
}

public override string GetXPathValue(string xpath)
{
throw new NotImplementedException();
}

public override void LoadFrom(object source)
{
throw new NotImplementedException();
}

public override string Name
{
get { return "MockXLANGPart"; }
}

public override void PrefetchXPathValue(string xpath)
{
throw new NotImplementedException();
}

public override object RetrieveAs(Type t)
{
if (t == typeof(T))
{
return m_obj;
}

return null;
}

public override void SetPartProperty(Type propType, object value)
{
throw new NotImplementedException();
}

public override System.Xml.Schema.XmlSchema XmlSchema
{
get { throw new NotImplementedException(); }
}

public override System.Xml.Schema.XmlSchemaCollection XmlSchemaCollection
{
get { throw new NotImplementedException(); }
}
}

public class MockXLANGMessage<T> : XLANGMessage
{
List<MockXLANGPart<T>> m_parts = new List<MockXLANGPart<T>>();

public MockXLANGMessage(T obj)
{
m_parts.Add(new MockXLANGPart<T>(obj));
}

public override void AddPart(object part, string partName)
{
throw new NotImplementedException();
}

public override void AddPart(XLANGPart part, string partName)
{
throw new NotImplementedException();
}

public override void AddPart(XLANGPart part)
{
throw new NotImplementedException();
}

public override int Count
{
get { return m_parts.Count; }
}

public override void Dispose()
{
}

public override System.Collections.IEnumerator GetEnumerator()
{
return m_parts.GetEnumerator();
}

public override object GetPropertyValue(Type propType)
{
throw new NotImplementedException();
}

public override string Name
{
get { return "MockXLANGMessage"; }
}

public override void SetPropertyValue(Type propType, object value)
{
throw new NotImplementedException();
}

public override XLANGPart this[int partIndex]
{
get { return m_parts[partIndex]; }
}

public override XLANGPart this[string partName]
{
get { return m_parts[0]; }
}
}



My thanks to Anil Prasad for putting me onto this technique.

Tuesday, 28 April 2009

Where did I leave my HAT?

Following an upgrade to BizTalk 2009 you may be left wondering where the Health and Activity Tracking (HAT) Tool has disappeared to. The Start Menu group for BizTalk 2009 no longer contains a link to HAT.

The answer lies in the BizTalk Administration Console MMC Snap-in. If you navigate to the Group Hub and select New Query you will see some new entries in the Value column such as Tracked Service Instances. This will retrieve data in the same way that the HAT queries did.

hat Right clicking on one of the resultset entries will offer the same options as HAT used to e.g. Message Flow, Orchestration Debugger. Navigate into one of these options presents the same interface as HAT always used to.

In fact HAT is still there under the covers, it's just been integrated into the Admin Console rather than running as a separate application. Try looking in the Task Manager list of running processes and you'll see BTSHatApp.exe.

HAT is dead...long live HAT.

Thursday, 23 April 2009

How to find the BizTalk Edition installed

I recently had to find the edition of an existing BizTalk installation on a test server. Initially I thought this would be a case of looking at a few Help > About dialogs in the admin tools. However, it turns out that this is stored in the registry under

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\BizTalk Server\3.0\ProductEdition

Sunday, 8 March 2009

MSBTS_SendAdapter doesn't work!

Just a quick post as a reminder to me (and a pointer for anyone else who happens upon this blog) to save lots of time scratching my head, looking at code that should work but doesn't.

I wrote some code as part of a BizTalk deployment framework which was intended to create an adapter handler if one does not already exist for the specified adapter/host. The Receive handler version worked fine but the Send version gave some strange results.

First I queried the existence of the send handler via WMI using MSBTS_SendHandler. This always returned an empty set indicating that it doesn't (even when the handler does in fact exist). When trying to create a send handler I got an error message (will update with exact text when I get a chance to look).

Anyway, to cut a long story short it turns out that MSBTS_SendHandler2 works fine so the guidance is to use this instead. My question is though - why does MSBTS_SendHandler still exist at all? It can't be for backwards compatibility because IT DOESN'T WORK! Why not just fix MSBTS_SendHandler instead of adding MSBTS_SendHandler2? Answers on a postcard to...

Wednesday, 24 December 2008

Identifying a BizTalk process

When using multiple BizTalk Hosts the process relating to each host can be identified using the following command line instruction:

tasklist /SVC /FI "IMAGENAME eq btsntsvc.exe"

This will display a list of BizTalk processes with the name of the Host and the PID. Note the PID for the host you are interested in. Match the correct PID with the process listed in Task Manager or the Visual Studio  Attach To Process dialog.

Tuesday, 23 December 2008

Resolving Schema Type Name Clashes

A common schema related error that may occur when building BizTalk projects that include schemas is:

BEC2017: Node "<Schema>" - This schema file has a TypeName that collides with the RootNode TypeName of one of its root nodes. Make sure that they are different.

The reason for this is quite simple - when the project is compiled, a .NET class will be generated for each top level element in your schema. If you have two schemas which share the same top level element name a clash will occur.

This is quite a common scenario and it is not always practical (or even possible) to change the element names. The solution is to change the generated type name for the schema. To do so, select the schema in the Solution Explorer and press F4 to display the Properties window. Then change the Type Name property so that it is not the same as any of the top level elements.

Monday, 22 December 2008

Default content in Maps

On a couple of occasions recently I have hit upon the need to use default content in some elements of a message generated via a map. More specifically I had an orchestration which did the following:

  • Defined an input and output schema that contained a standard header section
  • Makes calls to external .NET components, other orchestrations and send ports
  • If all works OK, a suitable response message is returned
  • In the event of a failure, an exception handler is invoked which constructs a new message to send to a common error logging orchestration before creating and returning a response message to the caller

The area of interest is the message sent to the error logging orchestration. This schema contains the standard header along with error information to be logged (for which each element defined as a distinguished field) i.e.

<xsd:element name="exception_details">
  <xsd:complexType>
    <xsd:sequence>
      <xsd:element name="type" type="xsd:string" />
      <xsd:element name="description" type="xsd:string" />
      <xsd:element name="error_source" type="xsd:string" />
      <xsd:element name="details" type="xsd:string" />
    </xsd:sequence>
  </xsd:complexType>
</xsd:element>

To create an instance of this message I transform the original request (to create the standard header) and insert the error details via code in a message assignment shape.

image

msgException.exception_details.description = ex.Message;
msgException.exception_details.details = ex.ToString();
msgException.exception_details.error_source = "orch name";
msgException.exception_details.type = "details here";

However, in order to programmatically insert the content into these elements, the elements had to exist in the new message! If it does not I get an XPath error when trying to set the distinguished property values.

To ensure that the elements exist I used the useful but often overlooked property of the element - Value. To find this open the Map Designer, select the target element and go to the Properties window. There you will see a Value property which can be used to set a default value for the element or <empty>. In this case <empty> was most appropriate giving message content of:

<message>
    <header>
        ...header content omitted
    </header>
    <payload>
        <exception_details>
            <description/>
            <details/>
            <error_source/>
            <type/>
        </exception_details>
    </payload>
</message>

This allowed me to set the error detail programmatically as required. Voila!