Tuesday, October 24, 2017

Adding Mixture of Unit Testing Frameworks to Dynamics 365/CRM Plug-in Project

Introduction:

Fake Xrm Easy is a very useful, and considerably easy, open source framework to unit test your Dynamics 365/CRM plug-ins. You might already know about the other authentic, and not so "easy", unit testing framework Fakes introduced by Microsoft.

There are occasions when it is necessary, or sometimes more convenient, to use both frameworks in one unit testing project. In this post I will demo such a mixture in a simple plug-in project. I will also walk through steps of adding unit test project to an existing Visual Studio plug-in solution as a beginner guide to jump start a unit test initiative for your Dynamics 365/CRM plug-in.

The Plug-In:

My demo plug-in is a pre-operation, registered on the creation of a Note. When a new Note is created, creation date and user full name will append to the end of the Note's title.

For example, entering title "Demo Unit Test" will save new Note title as "Demo Unit Test: Note created on 12/31/2020 03:09:15 PM by Tuan Nguyen". You might recall this is the default behavior in Dynamics CRM 2011 without the need of a custom plug-in. If no title is entered, new Note will have title "Title: Note created on 12/31/2020 03:15:15 PM by Tuan Nguyen".

Here's the simplified version of the plug-in code in a VS project called TXN.Plugin.Annotation :

using System;
using System.Linq;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace TXN.Plugin.Annotation

{
    public class PreCreate: IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationService service = ((IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory))).CreateOrganizationService(new Guid?(context.UserId));

            if (context.InputParameters.Contains("Target") && (context.InputParameters["Target"] is Entity))

            {
                Entity entity = context.InputParameters["Target"] as Entity;

                if (entity.LogicalName == "annotation" && context.MessageName.ToUpper() == "CREATE")

                {
                    string title = "Title";
                    string fullName = string.Empty;

                    if (!string.IsNullOrEmpty(entity.GetAttributeValue<string>("subject")))

                        title = entity.GetAttributeValue<string>("subject");                          
                            
                    var fetchXml = @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
                                     <entity name='systemuser'>
                                        <attribute name='fullname' />
                                        <attribute name='systemuserid' />
                                        <filter type='and'>
                                            <condition attribute='systemuserid' operator='eq' uitype='systemuser' value='{0}' />
                                        </filter>
                                    </entity>
                                    </fetch>";

                    fetchXml = string.Format(fetchXml, context.UserId.ToString());

                            
                    EntityCollection users = service.RetrieveMultiple(new FetchExpression(fetchXml));

                    if (users != null && users.Entities.Count() > 0)

                        fullName = users.Entities[0].GetAttributeValue<string>("fullname");
                            
                    title = string.Format("{0}: Note created on {1} by {2}", title, PluginDateTime.getDateTimeNow(), fullName);
                    entity.Attributes["subject"] = title;                                
                }
            }
        }
    }

    public static class PluginDateTime

    {
        public static string getDateTimeNow()
        {
            return string.Format("{0:M/d/yyyy hh:mm:ss tt}", DateTime.Now);
        }
    }

}



Add Unit Test Project:

First we need to provision an empty place holder unit test project in the solution before adding MS Fakes and FakeXrmEasy unit testing methods to it later:

1. Add a new project to Visual Studio solution containing the above plug-in project, using the Unit Test Project template installed with Visual Studio. Here I add the unit test project called TXN.Plugin.UnitTest:



























2. At this point the default unit test project can be compiled and run without problem as it isn't really testing anything yet:

























Unit Testing with Fakes Framework:

We want to unit test the static method in the plug-in, getDataTimeNow(), which makes a system call DateTime.Now. And the proper approach is to use Shims type of MS Fakes Framework to simulate the date time return value.

First thing is to have references to the plug-in project so we can make calls to plug-in methods in unit testing class. Right click on References node in UnitTest project to Add Reference; and select the plug-in project:





















The plug-in assembly TXN.Plugin.Annotation should now be visible in the References list of UnitTest project.

Next we add the Fakes assembly to References list. Since Datetime class is in System.dll, right click on System under References and select Add Fakes Assembly. A few system fakes assemblies along with a Fakes folder will be automatically created.

Now add code to TestMethod1() to unit test static method getDateTimeNow():



Mix in FakeXrmEasy Unit Testing:

At this point we have unit tested the static method resides in the same plug-in assembly using Fakes framework. But we have not tested the plug-in at all. We will install the FakeXrmEasy framework to the same UnitTest project before programming the plug-in tests. The true beauty of FakeXrmEasy is that you can fake everything, well almost everything, to come up with a unit test case, without the need to deploy the plug-in. And coding is much shorter and more readable compared to other frameworks. I guess that's why they call it Easy. I think the name makes sense.

To install FakeXrmEasy assemblies, right click on References node from UnitTest project, and select Manage Nuget Packages. Enter 'FakeXrmEasy' to search for the online package. Choose the FakeXrmEasy version compatibled to your compiled plug-in and hit Install:



















When installation completes the whole new set of assemblies will be added to the project and we are now ready to write unit test functions against the plug-in. And then run the unit tests:





























The code is presented at the end of the post and I won't go over the details of it. Some test methods are redundant in the example here for the purpose of a demo.

However I do want to point out that in one of the test methods, Note_With_Fully_Custom_Title(), we have mixed both unit testing frameworks MS Fakes and FakeXrmEasy to achieve the effect of simulating a system-dependent value within the plug-in operation.

I hope you have fun in unit testing discipline with your Dynamics 365/CRM plug-ins.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using FakeXrmEasy;
using Microsoft.Xrm.Sdk;

namespace TXN.Plugin.UnitTest
{
    [TestClass]
    public class UnitTest1
    {
        XrmFakedContext fakeContext = new XrmFakedContext();
        Entity target = new Entity("annotation") { Id = Guid.NewGuid() };

        [TestMethod]
        public void Note_Contains_Title()
        {
            var fakedPlugin = fakeContext.ExecutePluginWithTarget<TXN.Plugin.Annotation.PreCreate>(target);

            Assert.IsTrue(target.Attributes.ContainsKey("subject"));
        }

        [TestMethod]
        public void Note_Begins_With_Empty_Title()
        {
            var fakedPlugin = fakeContext.ExecutePluginWithTarget<TXN.Plugin.Annotation.PreCreate>(target);
            string title = target.Attributes["subject"].ToString();

            Assert.IsTrue(title.StartsWith("Title: Note created on"));
        }

        [TestMethod]
        public void Note_Begins_With_Non_Empty_Title()
        {
            target.Attributes["subject"] = "My Note Title";
            var fakedPlugin = fakeContext.ExecutePluginWithTarget<TXN.Plugin.Annotation.PreCreate>(target);

            Assert.IsTrue(target.Attributes["subject"].ToString().StartsWith("My Note Title: Note created on"));
        }

        [TestMethod]
        public void Note_With_Fully_Custom_Title()
        {
            Guid callerId = Guid.NewGuid();
            fakeContext.CallerId = new EntityReference("systemuser", callerId);

            Entity user = new Entity("systemuser");
            user.Id = callerId;
            user["fullname"] = "Tuan Awesome Nguyen";

            fakeContext.GetFakedOrganizationService().Create(user);

            target.Attributes["subject"] = "Test Note Title";

            using (Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create())
            {
                System.Fakes.ShimDateTime.NowGet = () =>
                {
                    return new DateTime(2025, 10, 10, 11, 12, 13);
                };

                var fakedPlugin = fakeContext.ExecutePluginWithTarget<TXN.Plugin.Annotation.PreCreate>(target);

                Assert.IsTrue(target.Attributes["subject"].ToString().Equals("Test Note Title: Note created on 10/10/2025 11:12:13 AM by Tuan Awesome Nguyen"));
            }
        }

        [TestMethod]
        public void TestMethod1()
        {
            using (Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create())
            {
                System.Fakes.ShimDateTime.NowGet = () =>
                {
                    return new DateTime(2020, 10, 10, 11, 12, 13);
                };

                var componentUnderTest = TXN.Plugin.Annotation.PluginDateTime.getDateTimeNow();

                Assert.IsTrue(componentUnderTest.ToString().Equals("10/10/2020 11:12:13 AM"));
            }
        }
    }
}



1 comment: