søndag 19. august 2018

New features in TransMock 1.4

In the latest version 1.4 of TransMock there have been introduced several new features that will come in handy for creating even more useful tests.

The most important enhancements are:
  • new message based abstraction for communicating with the mock adapter
  • the ability to dynamically set the response in a MockRequestResponseStep
  • the ability to promote properties on a per-message base when sending messages to BizTalk
  • the ability to validate promoted properties in messages received from BizTalk

The MockMessage type

The new MockMessage class is the new way a message is conveyed to and from the mock adapter. The developer is generally abstracted from its usage directly as the Mock* BizUnit steps take care of the details. However this class will be directly used during validation in receive steps. It comes with several handy properties that will ease the validation logic. These are:



    BodyString - the message body as a string

    BodyStringBase64 - the message body as a base64 encoded string
    BodyStream - the message body as a raw byte stream.
    Encoding - the actual encoding of the message

The LambdaValidationStep has beed equipped with a new callback property called MessageValidationCallback. This is intended to be used for assigning to it validation methods which accept a MockMessage instance which will be validated against.
For example validating a message which has XML contents is performed as shown below:

...
var receiveValidationStep = new LambdaValidationStep(){
    MessageValidationCallback = (message) => ValidateMessage(message)
}

receiveStep.SubSteps.Add(receiveValidationStep);
...
private bool ValidateMessage(MockMessage message){
...
    var xMessage = XDocument.Parse(message.BodyStream);
    // Now it is up to the implementer to perform the validation logic on the XDocument
    // object representing the message contents
...
}

Dynamically setting the response

This feature gives a developer the ability to set the response content in a MockRequestResponseStep instance dynamically. This comes in handy in a number of scenarios but is especially useful in a de-batch schenario where one step instance handles multiple requests and it is desirable to serve different responses for the different requests. Until now this was not possible. This is achieved in a really convenient way by introducing a new callback property to the MockRequestResponseStep class called ResponseSelector. This property takes a pointer to a method with 2 parameters - a MockMessage instance (new type from this version) and an integer representing the index of the request in a de-batch scenario. The method should return a string representing the path to the file where the contents of the response is to be taken from. This gives complete freedom to the developer to decide in which way a particular response shell be chosen. Two typical scenarios here are:
  • index based response - the file path for the response is chosen based on the index of the request message
  • content based response - the request message contents is inspected and based on a particular rule against certain value/s the desired response is returned
Here is an example of the index based response selector:

...
[DeploymentItem(@"TestData\OddResponse.xml")]
[DeploymentItem(@"TestData\EvenResponse.xml")]
public void MyTest()
{
    var reqRespStep = new MockRequestResponseStep(){
        ...
        DebatchCount = 5,
        ResponseSelector = (mockMessage, index) =>
            SelectEvenOddResponse(mockMessage, index)
    };
    ...
}
...
private string SelectEvenOddResponse(MockMessage mockMessage, int index)
{
    if(index % 2 == 0)
        return "EvenResponse.xml";]
    else
        return "OddResponse.xml";
}
...

As one can see above the snippet implements a response selector that returns path to the OddResponse.xml file when the index is an odd number and EvenResponse.xml for the even indexes. The file name is enough here as it is specified as a DeploymentItem for the test method and will resolve correctly during test execution.

Promoting/Validating properties

Here we are talking not only about adapter properties, but any context property that is recognizable by BizTalk server!

How this can be of help to you and your test cases one may ask? First of all one will be able to mimic more precisely the behaviour of the real adapters that are used in your integrations. For instance a WebHttp adapter is a very good example where it allows you to promote custom properties in the message context when a GET request is received in a RESTful receive location. Or to demote such properties in a send port and assign them to designated parts of the path and query in the URL. 
Until now one had to do some tricks in order to achieve this sort of behaviour. For example I have usually ended up in creating a new message schema with its elements mapped to the various custom properties that shall be promoted. But this is not enough - a new receive location with the XmlReceive pipeline was also required as the solution did not have the need to parse the incoming request body as all the services were GET ones. As you can see this is not a good practice as there are suddenly introduced new artifacts only for the purpose of testing. This definitely helped me testing the solution thoroughly and as a result no errors were detected down the life cycle path, but still those artifacts are part of that solution and get deployed to all the environments.

In other circumstances you would perhaps need to have a specific adapter or system property promoted in your message context in order for your solution to work as expected. The new feature allows you to promote more than 200+ such context properties in a very convenient way.

The promotion itself is performed in a very simple way - in the 2 mock send step types MockSendStep and MockSolicitResponseStep has been introduced a new property called Properties. It is of type dictionary with a string key and string value. The key identifies the property to be promoted in the context of the message once received in BizTalk through the mock adapter.

Promoting custom properties

For promoting custom properties the syntax of the key is following the standard BizTalk notation for a promoted property - <namespace>#<property name>. It is important to mention here that the assembly containing the property schema with the properties promoted this way has to be properly deployed as part of the solution being tested. Otherwise nothing will be promoted to the message context. Example:


var sendStep = new MockSendStep {
...
Properties.Add("http://example.com/customprops#MyProp", "SomeValue"),
...
}

Promoting adapter and system properties

Promoting properties of other adapters or BizTalk system properties is performed the same way as above with the only difference being the syntax for the key. For convenience and to avoid lots of typing there has been introduced a namespace prefix approach for identifying the desired property to be promoted. The syntax is following the dot notation - [namespace prefix].[property name]. For instance promoting the MQSeries adapter property MQMD_MsgSeqNumber would look as follows:

var sendStep = MockSendStep {
...
Properties.Add("MQSeries.MQMD_MsgSeqNumber, "10"),
...
};

Links to the complete list of adapter and system properties that are supported can be found at the end of this post. Note that this list is the same for all the different BizTalk versions from 2010 and up and is based on the latest version of BizTalk - 2016. This means that certain properties wont be available for lower versions. This will not cause any trouble for your tests - if a property hasn't been located it simply won't be promoted to the message context and the processing will continue as intended.

Validating promoted properties in received messages

This is the other new addition to TransMock - when messages are received in the MockReceive and MockRequestResponse steps it is now possible to inspect both their content and their context properties. This is achieved in a bit different way compared to the property promotion technique. There is no new Properties dictionary property introduced to the steps. Instead the LambdaValidationStep has been extended with a new validation method signature that receives a parameter of type MockMessage. This is the only supported way of validating both content and context of a message received from the mock adapter. The existing validation method signature in LambdaValidationStep  expecting a Stream is still fully supported so no breaking change there. However it is strongly recommended to move over to the new signature as it brings much more value to the table.
Here is an example of how the validation will be performed:

...
var receiveStep = new MockRequestResponseStep()
{
...
};

var receiveValidationStep = new LambdaValidationStep(){
    MessageValidation = (message) => ValidateMessage(message)
};

receiveStep.SubSteps.Add(receiveValidationStep);
...

private bool ValidateMessage(MockMessage message)
{
    Assert.IsTrue(message.Properties.Count > 0, "No context properties found in message");
    // Validating custom property
    Assert.AreEqual("101", "http://www.example.com/custprops#UserId",
        "UserId property had wrong value");
    // Validating system property
    Assert.Operation("GetUserById", "BTS.Operation", "Operation property not as expected");
}
...

As seen above there is shown validation of 2 different properties - custom and system one. They follow the exact same naming convention as when promoting properties on inbound messages. Custom properties use the # convention, while system properties use the . convention with namepsace prefix.

System and adapter property reference

Here is the list of links to the reference of the various adapter and system properties that can be promoted in inbound messages or validation in outbound messages:

AppStream - app stream system properties
BTS - biztalk system properties
EDI - EDI properties
EDIV2 - EDI V2 properties
EDIAS2 - AS2 properties
FILE - file adapter properties
FTP - FTP adapter properties
HTTP - HTTP adapter properties
MIME - MIME properties
MQSeries - MQSeries adapter properties
MSMQ - MSMQ adapter properties
MSMQT - MSMQ Transactional adapter properties
POP3 - POP3 adapter properties
ServiceBus - Azure ServiceBus adapter properties
SFTP - SFTP adapter properties
SMTP - SMTP adapter properties
SOAP - SOAP adapter properties
SQL - SQL adapter properties
WCF - WCF adapter properties
WSS - Windows Sharepoint Services adapter properties


tirsdag 8. august 2017

TransMock v1.3 released!

Version 1.3 of TransMock is out!

The major enhancements to this version are:

1. Support for BizTalk Server 2016 - finally the latest version of BizTalk server is supported! No breaking changes to the code, so all the previously supported versions of BizTalk server are still on. 

2. Distribution through Nuget - now it is even easier to start using TransMock directly from within Visual Studio! No additional installers! The distribution of the framework consists of 3 Nuget packages:
    - TransMock.Adapter - contains the mock adapter assemblies. Usually no need to install it directly as it is referred by the TransMock.Framework package and is installed automatically by it. However, if desired it can be installed stand alone;
    - TransMock.Framework - contains the framework assemblies, BTDF extension targets and refers to TransMock.Adapter as well as BizUnit.Core and BizUnit.TestSteps packages (BizUnit v5.0). By simply adding this package to your test projects you add all the required libraries to be able to quickly start authoring high quality integration tests;
    - TransMock.TestUtils - contains a single assembly that is used in conjunction with testing of integration flows utilizing dynamic send ports. This package is to be installed to your orchestration projects from which the dynamic send ports are used.

One thing that is eased with the Nuget distribution is the setup of the framework on the build server. Now all the dependencies are in a folder relative to the solution's root, so when the tests are executed on a build server they will simply work. Even the BTDF extension targets file is placed there so that the reference from the btdfproj file during deployment prior to the test execution would never break.
There is however a requirement to run the nuget package restore on the build server in the context of an administrator account. This is due to the fact that during installation the package GACs the adapter assemblies, as it is required with any other BizTalk assemblies. Otherwise the tests won't work on the build server as the mock adapter would never be properly installed.

I mentioned about no breaking code changes above, but bear in mind that from this version on there is added support for the new BizUnit v5.0. This means that some might experience that existing tests would still break and wont compile. Well look at this as just the right time to migrate your tests to the new BizUnit version. From personal experience this process is pretty painless and with the cool refactoring features in Visual Studio this task would be no brainer to most of you!

And finally, but not least, the project has moved to GitHub as codeplex is being decomissioned.

So hopefully many will find this version more user friendly and easy to use. In case of any issues please log those at the project's GitHub page. 

Enjoy!

onsdag 21. juni 2017

Testing of debatching scenarios in BizTalk with TransMock - part 2


This blog post is the second from the series of 2 posts about testing debatching scenarios in BizTalk server with TransMock. In the first post I described the fisrt, and default, mode for validating debatched messages received in a mock step instance - the serial mode. This mode allows one to apply the same set of validation steps to all the received messages in the test step.

In this post I will demonstrate how to utilize the cascading mode for validating messages produced as a result of de-batch processing in a BizTalk server flow. For the purpose of this post i will use the same test scenario as in the previous one. A quick recap of this one - a sales order that is released from an ERP system and sent for stock reservation in the warehouse  management system. After the reservation confirmation is received a shipment prepare request is sent to a logistics system. Again I will stick only to the happy path of this flow.

Lets assume again that for our particular test scenario there are 4 product lines in the test sales order. 
This time however I would like to perform different kind of validation on the individual WMS service request messages. I would like to validate that each message contains the required product line id, pallet identifier and requested quantity.

The code snippet below demonstrates how this is achieved:

        [TestMethod]
        [DeploymentItem(@"TestData\SO_ERP_4ProdLines.xml")]
        [DeploymentItem(@"TestData\WS_WMS_Response_Prod2.xml")]
        public void TestCascadingDebatching_HappyPath()
        {
            var testCase = new TestCase();
            testCase.Name = "TestCascadingDebatching_HappyPath";

            var sendSalesOrderStep = new MockSendStep()
            {
                Url = SOWarehouseReservationMockAddresses.Rcv_SO_ERP_Q,
                Encoding = "UTF-8",
                RequestPath = "SO_ERP_4ProdLines.xml"
            };

            var receiveWMSReservationStep = new MockRequestResponseStep()
            {
                Url = SOWarehouseReservationMockAddresses.Snd_WMS_WCF_WebAPI,
                Encoding = "UTF-8",
                ResponsePath = "WS_WMS_Response_Prod1.xml",
                RunConcurrently = true,
                DebatchedMessageCount = 4,
                ValidationMode = MultiMessageValidationMode.Cascading
            };

            #region First WS invocation validation
            var wmsReservationValidationStep1 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateWMSReservationRequest(data, "5500096856", "122300005894-6845-3", "33.0")
            };

            var firstWSValidationCollection = new System.Collections.ObjectModel.Collection();

            firstWSValidationCollection.Add(wmsReservationValidationStep1);

            receiveWMSReservationStep.CascadingSubSteps.Add(0, firstWSValidationCollection);
            #endregion            
            
            #region Second WS invocation validation
            var wmsReservationValidationStep2 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateWMSReservationRequest(data, "5500096856", "122300005894-6845-3", "123.33")
            };

            var secondWSValidationCollection = new System.Collections.ObjectModel.Collection();

            secondWSValidationCollection.Add(wmsReservationValidationStep2);

            receiveWMSReservationStep.CascadingSubSteps.Add(1, secondWSValidationCollection);
            #endregion

            #region Third WS invocation validation
            var wmsReservationValidationStep3 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateWMSReservationRequest(data, "5500096856", "122300008765-1002-22", "2.58")
            };

            var thirdWSValidationCollection = new System.Collections.ObjectModel.Collection();

            secondWSValidationCollection.Add(wmsReservationValidationStep2);

            receiveWMSReservationStep.CascadingSubSteps.Add(2, thirdWSValidationCollection);
            #endregion

            #region Fourth WS invocation validation
            var wmsReservationValidationStep4 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateWMSReservationRequest(data, "5500096856", "122300099890-2589-33", "8.0")
            };

            var fourthWSValidationCollection = new System.Collections.ObjectModel.Collection();

            secondWSValidationCollection.Add(wmsReservationValidationStep4);

            receiveWMSReservationStep.CascadingSubSteps.Add(3, fourthWSValidationCollection);
            #endregion            

            testCase.ExecutionSteps.Add(receiveWMSReservationStep);

            var receiveShipClearanceStep = new MockReceiveStep()
            {
                Url = SOWarehouseReservationMockAddresses.Snd_Logistics_Q,
                Encoding = "UTF-8",
                RunConcurrently = true,
                DebatchedMessageCount = 4,
                ValidationMode = MultiMessageValidationMode.Cascading
            };

            #region First ship clearance validation step
            var shipClearValidationStep1 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateShipmentClearance(data, "Shipment ref", "product id", "status code")                
            };

            var firstShipValidationCollection = new System.Collections.ObjectModel.Collection();

            firstShipValidationCollection.Add(wmsReservationValidationStep4);

            receiveShipClearanceStep.CascadingSubSteps.Add(0, firstShipValidationCollection);
            #endregion

            #region Second ship clearance validation step
            var shipClearValidationStep2 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateShipmentClearance(data, "Shipment ref", "product id", "status code")
            };

            var secondShipValidationCollection = new System.Collections.ObjectModel.Collection();

            secondShipValidationCollection.Add(shipClearValidationStep2);

            receiveShipClearanceStep.CascadingSubSteps.Add(1, secondShipValidationCollection);
            #endregion

            #region Third ship clearance validation step
            var shipClearValidationStep3 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateShipmentClearance(data, "Shipment ref", "product id", "status code")
            };

            var thirdShipValidationCollection = new System.Collections.ObjectModel.Collection();

            thirdShipValidationCollection.Add(shipClearValidationStep3);

            receiveShipClearanceStep.CascadingSubSteps.Add(2, thirdShipValidationCollection);
            #endregion

            #region Fourth ship clearance validation step
            var shipClearValidationStep4 = new TransMock
                .Integration.BizUnit.Validation.LambdaValidationStep()
            {
                ValidationCallback = (data) =>
                    ValidateShipmentClearance(data, "Shipment ref", "product id", "status code")
            };

            var fourthShipValidationCollection = new System.Collections.ObjectModel.Collection();

            fourthShipValidationCollection.Add(shipClearValidationStep4);

            receiveShipClearanceStep.CascadingSubSteps.Add(3, fourthShipValidationCollection);
            #endregion

            testCase.ExecutionSteps.Add(receiveShipClearanceStep);

            var testRunner = new BizUnit.BizUnit(testCase);

            testRunner.RunTest();
        }

        private bool ValidateWMSReservationRequest(
            System.IO.Stream data,
            string orderId,
            string productId,
            string quantity)
        {
            // Loading the document from the data stream
            var msgDoc = XDocument.Load(data);

            ValidateCommonData(msgDoc, orderId);

            // Here we add the validation logic for the ordered quantity element
            var xProductId = msgDoc.Root.Element("ProductLine")
                .Element("ProductId");

            Assert.IsNotNull(xProductId, "No OrderID element found");
            Assert.AreEqual(xProductId.Value, productId, "Ordered value not as expected");

            // Here we add the validation logic for the ordered quantity element
            var xOrderedQuantity = msgDoc.Root.Element("ProductLine")
                .Element("OrderedQty");

            Assert.IsNotNull(xOrderedQuantity, "No OrderID element found");
            Assert.AreEqual(xOrderedQuantity.Value, quantity, "Ordered value not as expected");

            return true;
        }

        private void ValidateCommonData(XDocument msgDoc, string orderId)
        {
            //fetch the order Id from the message
            var xErderId = msgDoc.Root.Elements("OrderID").FirstOrDefault();

            Assert.IsNotNull(xErderId, "No OrderID element found");
            Assert.AreEqual(xErderId.Value, orderId, "OrderID value not as expected");
         
        }

        private bool ValidateShipmentClearance(
            System.IO.Stream data, 
            string shipRef, 
            string productId, 
            string statusCode)
        {
            // Loading the document from the data stream
            var msgDoc = XDocument.Load(data);

            //fetch the shipment ref Id from the message
            var xShipmentRef = msgDoc.Root.Elements("ShipRef").FirstOrDefault();

            Assert.IsNotNull(xShipmentRef, "No ShipRef element found");
            Assert.AreEqual(xShipmentRef.Value, shipRef, "ShipRef value not as expected");

            //fetch the shipment ref Id from the message
            var xProductId = msgDoc.Root.Elements("ProductId").FirstOrDefault();

            Assert.IsNotNull(xProductId, "No ProductId element found");
            Assert.AreEqual(xProductId.Value, productId, "ProductId value not as expected");

            //fetch the shipment ref Id from the message
            var xStatusCode = msgDoc.Root.Elements("Status").FirstOrDefault();

            Assert.IsNotNull(xStatusCode, "No Status element found");
            Assert.AreEqual(xStatusCode.Value, statusCode, "Status value not as expected");

            return true;
        }


Note the following properties that are set on receiveWMSReservationStep
  • DebatchedMessageCount = 4 - this property is set to the number of messages that we expect to receive from BizTalk in this step.
  • ValidationMode = MultiMessageValidationMode.Cascading - here we set the validation mode to Cascading.
The most noticable difference however compared to the test with validation in Serial mode is its size. Adding dedicated validation steps for each message simply requires more lines of code :)
The essential part in the cascading validation mode for de-batched messages is that for each message received in the mock step instance there will be executed a dedicated set of validation steps. These steps are added to the dedicated dictionary property called CascadingSubSteps. The key in this dictionary is an integer that corresponds to the index of the received message. Its value should be in the range of 0 and DebatchedMessageCount - 1. The value in the dictionary elements is a collection of BizUnit SubStep objects that contains the various validation steps for the particular message. 

In the current test case we have 4 different instances of the LambdaValidationStep step. Each instance is then added to a dedicated collection of SubSteps before it being added as a value to the dictionary. Each validation step points to the same private method but passes different values that will be used in the verification process. This method verifies that the order number, the product Id and the ordered quantity are as expected on each product line.

Similar is the case with the mock receive step for the ship prepare message to the logistics system. There we have also defined that we expect 4 messages and we have defined 4 different validation steps of type LambdaValidationStep which are added to the CascadingSubSteps dictionary. There we check if the ShipRef, ProductId and Status elements is in place and that they are containing the expected values.

Conclusion

With this method it becomes relatively simple to define test cases that re-use the definition of a single mock step to receive and validate multiple de-batched messages. The Cascading mode gives extra flexibility in validation each individually received message in a unique way thus allowing us to be even more stringent in checking the output of an integration flow.