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.

onsdag 14. juni 2017

Testing of debatching scenarios in BizTalk with TransMock - part 1


This blog post is a series of 2 posts about testing debatching scenarios in BizTalk server with TransMock. Why exactly 2? Simply because TransMock has 2 different modes for verifying messages which are received as part of a debatching operation - serial and cascading.
  • Serial mode, which is the default one, is where each and every debatched message is validated in the same way by the same validation step. 
  • Cascading mode is when each debatched message is validated through a dedicated validation step.
In this post I will demonstrate how to utilize the serial mode for testing and verifying batching scenarios. But first let's describe the test scenario. The flow is about a sales order that is released from the ERP and sent for verification of warehouse availability by the warehouse management system.
BizTalk receives an XML message from the ERP, which is debatched in the receive pipeline. Each individual message is then processed by a subscribed orchestration. In the orchestration the request message is mapped to a web service request and then the corresponding web service exposed by the WMS system is invoked. The response from the service is then transformed to a final message type which is put onto a queue to the target logistics system.
The input message contains the sales order with separate product lines, which are debatched. Lets assume that for a particular test scenario there are 4 product lines in the test sales order.
The web service is invoked to verify the availability of the product quontity of each product line.

By now it should be obvious that there is a particular challange in testing this flow with TransMock. The web service endpoint mock should be invoked multiple number of times - in our test case that is 4. How shall the test case be defined in this particular scenario? The answer to this is pretty simple as TransMock supports debatching scenarios out of the box since version 1.2. The code snippet below demonstrates how is this achieved:

    
    [TestMethod]
    [DeploymentItem(@"TestData\SO_ERP_4ProdLines.xml")]
    [DeploymentItem(@"TestData\WS_WMS_Response_Prod1.xml")]
    public void TestSerialDebatching_HappyPath()
    {
        var testCase = new TestCase();
        testCase.Name = "TestSerialDebatching_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.Serial              
        };          

        var wmsReservationValidationStep = new TransMock
            .Integration.BizUnit.Validation.LambdaValidationStep()
        {
            ValidationCallback = (data) => {
                // Loading the document from the data stream
                var msgDoc = XDocument.Load(data);

                //fetch the order Id from the message
                var orderId = msgDoc.Root.Elements("OrderID").FirstOrDefault();
                 
                Assert.IsNotNull(orderId, "No OrderID element found");
                Assert.AreEqual(orderId.Value, "5500096856", "OrderID value not as expected");

                return true;
            }
        };

        receiveWMSReservationStep.SubSteps.Add(wmsReservationValidationStep);
        testCase.ExecutionSteps.Add(receiveWMSReservationStep);

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

        var shipClearValidationStep = new TransMock
            .Integration.BizUnit.Validation.LambdaValidationStep()
        {
            ValidationCallback = (data) =>
            {
                // Loading the document from the data stream
                var msgDoc = XDocument.Load(data);

                //fetch the order Id from the message
                var shipmentRef = msgDoc.Root.Elements("ShipRef").FirstOrDefault();

                Assert.IsNotNull(shipmentRef, "No ShipRef element found");
                Assert.AreEqual(shipmentRef.Value, "1223/5500096856", "ShipRef value not as expected");

                return true;
            }
        };
 
        receiveShipClearanceStep.SubSteps.Add(shipClearValidationStep);
        testCase.ExecutionSteps.Add(receiveShipClearanceStep);

        var testRunner = new BizUnit.BizUnit(testCase);

        testRunner.RunTest();
    }

Note the following properties that are set on the inventoryWebServiceMockStep:
- DebatchedMessageCount = 4 - this property is set to the number of messages that we expect to receive from BizTalk in this step.
- ValidationMode = MultiMessageValidationMode.Serial - this property is about setting the validation mode for debatched messages. This is also the default value of this property. Here it is set just for the purpose of demosntration, otherwise it is not required.

The essential part in the serial validation mode for debatched messages is that for each debatched message received in the mock step instance there will be executed each and every validation step defined in the SubSteps collection.

In our case we have only one LambdaValidationStep instance. This validation step verifies that the order number is as expected on each product line. If the method of using the LambdaValidationStep is new to you, please read the dedicated blog post for it here. LambdaValidationStep was introduced in version 1.2 as an attempt to promote a more flexible, simplified and unified validation programming model for validation logic in BizUnit.

The same 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 one step of type LambdaValidationStep. There we check if the ShipRef element is in place and contains the expected value.

Conclusion

With this method it is relatively simple to define test cases that re-use the same definition of a single mock step to receive and validate multiple debatched messages. In fact this technique is applicable not only in classical debatching scenarios as described in the fictive test case above, but in cases where there are for example looping conditions in the tested flow, resulting in multiple invocations of a single mock step instance.

Jump to the Testing of debatching scenarios in BizTalk with TransMock - part 2 blog post to learn more about the Cascading validation mode

onsdag 1. mars 2017

Testing of custom pipeline components with Moq

Testing of custom pipeline components is not as hard as one might think. If one follows the standard way of testing such component as part of the pipeline where it is used, by running the provided with the BizTalk SDK tool Pipeline.exe, then it is quite possible that unit testing that component will be the last thing one would consider.
Well, that should definitely not be the case - make unit and assembly testing an integral part of your BizTalk development cycle!
In this article I will demonstrate you how easy it is to test a custom pipeline component without any dependency on external tools or even deployment to BizTalk server in a very simple yet very accessible and adoptable manner.

For this purpose I am utilizing one of the most widely used frameworks for mocking, called Moq. I am able to apply it in the case of pipeline components simply because the BizTalk pipeline framework is (thanks God) designed, and implemented, following solid SOA principles for componentization and separation of concern. Basically everything related to pipelines and pipeline components in BizTalk is defined by following the interface pattern. This automatically allows us to apply the principles of mocking by utilizing Moq. This framework gives the possibility to create mocked instances of interfaces and abstract classes with only few lines of code! And the best part of it - it is completely free! Developers all over the world totally love it!

So how mocking will help us in this case? Well, here is the definition of the main entry point method for a pipeline component defined in the interface IComponent:

IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)

The method accepts 2 parameters, both of types interface and returns an object of type interface too. Hence we can create mocks of the parameter objects and set those up in such a way that they behave as if they were passed by the BizTalk runtime. Lets go ahead and demonstrate this with a simple example.

One of the most often examples found on the Net for custom pipeline components is the one about diverting large files from entering the MessageBox database, by saving it to a dedicated location on a disc share and constructing a simple XML mesasage that contains the path to this file. For the demonstration purpose i have enhanced the component to also promote the original name of the file, as if it is used in CBR scenario. The component will return the newly created XML message as a result with the required property promoted.

Here is the corresponding code for this set up:
  
public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)
{
    if (_largeFileLocation == null || _largeFileLocation.Length == 0)
        _largeFileLocation = Path.GetTempPath();
 
    if (_thresholdSize == null || _thresholdSize == 0)
        _thresholdSize = 4096;

    // Treat as large file only if the size of the file is greater than the thresholdsize
    if(pInMsg.BodyPart.GetOriginalDataStream().Length > _thresholdSize)
    {
        string largeFilePath = _largeFileLocation + pInMsg.MessageID.ToString() + ".msg";

        using(FileStream fs = new FileStream(largeFilePath, FileMode.Create))
        {

            // Write the message content to disk
         
            byte[] buffer = new byte[4096]; 
            int bytesRead  = 0;

            while((bytesRead = originalStream.Read(buffer, 0, buffer.Length)) > 0)
            {  
               fs.Write(buffer, 0, buffer.Length);
               fs.Flush();             
            }

            fs.Flush();
        }
      
        string originalFileName = (string)pInMsg.Context.Read("ReceivedFileName","http://www.microsoft.com/biztalk/schemas/file-properties");

        if(string.IsNullOrEmpty(originalFileName))
        {
            originalFileName = "NotAvailable";
        }
        else
        {
            originalFileName = System.IO.Path.GetFileName(originalFileName);
        }

        pInMsg.Context.Promote("OriginalLargeFileName", "www.example.com/largefile/properties/1.0", originalFileName);

        // Create the meta message
        string msgMeta = @"<LargeFile xmlns="http://example.com"><path>" + 
            largeFilePath + "</path></Largefile>";
      
        byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(msgMeta);
        MemoryStream ms = new MemoryStream(byteArray);
        pInMsg.BodyPart.Data = ms;
     }

     return pInMsg; 
}

And this is how I am approaching the unit testing of the component.

// 1. Define the test method
[TestMethod]
[DeploymentItem(@"TestData\largefile.txt")]
public void TestLargeFileDecoder_HappyPath()
{

    // 2. Define the context mock<

    var contextMock = new Mock<IPipelineContext>();
    // The only thing that we really need to do with the context mock is to declare it as it is not used in the Execute() method at all.

    // 3. Define and setup the message context mock
    var messageContextMock = new Mock<IBaseMessageContext>();

    messageContextMock.Setup(c => c.Read("ReceivedFileName", this.FileAdapterNamespace))
        .Returns(@"\\somesrv\somedir\testfile123.txt");

    string originalFileName = null;

    messageContextMock.Setup(c => c.Promote(
        "OriginalFileName", 
         this.CustomPropertyNamespace, 
         It.Is<string>(
             s => s != null)))
            .Callback<string, string, object>( 
                (propName, namespace, value) => { originalFileName = value;});

    // 4. Define and setup the message part mock
    using(var fs = File.OpenRead("largefile.txt"))
    {
        var messagePartMock = new Mock<IBaseMassegaPart>();

        messagePartMock.Setup( mp => mp.GetOriginalDataStream()).Returns(fs);
        messagePartMock.SetupProperty(mp => mp.Data);

        var messageId = Guid.New();
        var messageMock = new Mock<IBaseMessage>();
        messsageMock.SetupProperty(m => m.MessageID, messageId);

       // 5. Define the message mock
       var messageId = Guid.New();
       var messageMock = new Mock<IBaseMessage>();

       messsageMock.SetupProperty(m => m.MessageID, messageId);
       messsageMock.SetupGet(m => m.Context).Returns(messageContextMock.Object);
       messsageMock.SetupGet(m => m.BodyPart).Returns(messagePartMock.Object);
 
       // 6. Invoke the Execute method
       var component = new LargeFileDecoder();
       component.LargeFileLocation = @"D:\Test\";
       
       var resultMessage = component.Execute(contextMock.Object, messageMock.Object);

       // 7. Verify the outcome:
       messageContextMock.Verify( c => c.Read("ReceivedFileName", this.FileAdapterNamespace), Times.Once()); 
 
       messageContextMock.Verify( c => c.Promote("OriginalFileName", this.CustomPropertyNamespace, 
           It.Is<string>(s => s == "testfile123.txt")), Times.Once());

       // Here we verify that the message produced is the meta message
           
       var resultMessageDoc = XDocument.Load(resultMessage.BodyPart.Data);
    
       // Verifying the root node name 
       Assert.AreEqual(XName.Get("LargeFile", "www.example.com/integration/demo/moq"), resultMessageDoc.Document.Name);

       // Extracting the path to the large file for verification
       string largeFilePath = resultMessageDoc.Document.Element("Path").Value;

       Assert.AreEqual(string.Format(@"D:\Test\{0}.msg", messageId), largeFilePath);
       
   }
}

That's really what it takes to create a pure unit test for your pipeline components.
Enjoy!