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.

Ingen kommentarer:

Legg inn en kommentar