Dynamics CRM CE: TDD Plugin Development in Action – Part 1

This tutorial will show you how to create the Dynamics CRM CE Plugin using the Test-Driven-Development (TDD) process. I try to make this as a series (from Easy, Intermediate, and Advanced). I will not explain about basic of the definition of TDD because there are lots of resources from other seniors that will give you information and a deeper understanding of TDD.

Before we start, I will explain to you first what is the scenario. I have an entity called Order Summary. In this entity, we have Customer (lookup to the Account or Contact Entity), Transaction Date, and Name Attributes. The scenario is very simple. We will set the Name attribute with the formula of selected Customer Name + Transaction Date with format (yyyy MMMM).

Set Up Environment

So now we set up the solution, plugin project, and the test project. 

  1. Open Visual Studio > File > New > Project. Find tab Other Project Types and expand it, you will find Visual Studio Solutions. Select Blank Solution, name the Solution OrderSummary.Crm, place the location in the folder you want > Ok.
  2. On the OrderSummary.Crm Solution, right-click > Add New Project > On Tab Visual C# > Find Class Library > Name it as OrderSummary.Plugins > Ok (make sure to choose .NET Framework 4.6.2).
  3.  On the OrderSummary.Plugins, right-click > Manage Nuget Packages > Find Niam.XRM.Framework and install it.
  4. On the OrderSummary.Crm Solution, right-click > Add New Project > In Test tab > choose Unit Test Project (.NET Framework 4.6.2) > name it as OrderSummary.Plugins.Tests > Ok.
  5. On the OrderSummary.Plugins.Tests, right-click > Manage Nuget Packages > Find Niam.XRM.Framework.TestHelper and install it.
  6. On the OrderSummary.Plugins.Test, under Reference folder, right click > Add Reference > Select OrderSummary.Plugins project > Ok.
  7. Delete all the default class from each project (Class1.cs and UnitTest1.cs).
  8. Download the Entities.zip, extract the .cs file into OrderSummary.Plugins folder.

Congratulations! From this step, we finish setting up our environment! You can download the source code of this step in this link.

Creating Test First

On the OrderSummary.Plugins, right-click > Add New Item > Class > name it SetSummaryName. Then change the implementation to the code below:

using System;
using Entities;
using Niam.XRM.Framework.Interfaces.Plugin;
using Niam.XRM.Framework.Plugin;
namespace OrderSummary.Plugins
{
    public class SetSummaryName : OperationBase<new_ordersummary>
    {
        public SetSummaryName(ITransactionContext<new_ordersummary> context) :
            base(context)
        {
        }
        protected override void HandleExecute()
        {
            throw new NotImplementedException();
        }
    }
}

On the OrderSummary.Plugins.Tests, right-click > Add New Item > Class > name it SetSummaryNameTests (Clean Code Tips: make your class as clear as you can). Then change to below code:

using Entities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Niam.XRM.Framework;
using Niam.XRM.Framework.TestHelper;
using System;
namespace OrderSummary.Plugins.Tests
{
    [TestClass]
    public class SetSummaryNameTests
    {
        [TestMethod]
        public void OrderSummary_SetSummaryName_ShouldValid()
        {
            // Set the DB
            var customer = new Contact { Id = Guid.NewGuid() }.
                Set(e => e.FullName, "CONTACT-001");
            var orderSummary = new new_ordersummary { Id = Guid.NewGuid() }
                .Set(e => e.new_customerid, customer.ToEntityReference())
                .Set(e => e.new_transactiondate, new DateTime(2020, 01, 01));
            var test = new TestEvent<new_ordersummary>(customer);
            // Execute the business class
            test.CreateEventCommand<SetSummaryName>(orderSummary);
            // Assert Output
            var name = orderSummary.Get(e => e.new_summaryname);
            Assert.AreEqual("CONTACT-001 2020 January", name);
        }
    }
}

In code above (SetSummaryNameTests), there are 3 parts of testing: Input data, process, and check the result. For our scenario, the input is very simple. We just need to set Customer and TransactionDate.

When we build, we can see in Test Explorer already register our test and we can run it. The result should be thrown NotImplementedException.

Implementing SetSummaryName Class

Change the implementation of SetSummaryName.cs to below code:

using Entities;
using Niam.XRM.Framework;
using Niam.XRM.Framework.Data;
using Niam.XRM.Framework.Interfaces.Plugin;
using Niam.XRM.Framework.Plugin;
namespace OrderSummary.Plugins
{
    public class SetSummaryName : OperationBase<new_ordersummary>
    {
        public SetSummaryName(ITransactionContext<new_ordersummary> context) :
            base(context)
        {
        }
        protected override void HandleExecute()
        {
            var customerRef = Get(e => e.new_customerid);
            var transactionDate = Get(e => e.new_transactiondate);
            var valid = customerRef != null && transactionDate != null;
            if (!valid) return;
            var contactData = Service
                .Retrieve(Contact.EntityLogicalName, customerRef.Id, new ColumnSet<Contact>(e => e.FullName))
                .ToEntity<Contact>();
            var contactFullName = contactData.Get(e => e.FullName);
            var dateFormat = transactionDate.GetValueOrDefault().ToString("yyyy MMMM");
            Set(e => e.new_summaryname, contactFullName + " " + dateFormat);
        }
    }
}

If you run again your test method, it will be green (success). But the problem, Customer lookup can receive multiple source data (Contact and SystemUser). With this knowledge, we know that the test needs to be changed. Below is the code of SetSummaryNameTests:

using Entities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Niam.XRM.Framework;
using Niam.XRM.Framework.TestHelper;
using System;
namespace OrderSummary.Plugins.Tests
{
    [TestClass]
    public class SetSummaryNameTests
    {
        [TestMethod]
        public void OrderSummary_SetSummaryName_Contact()
        {
            // Set the DB
            var customer = new Contact { Id = Guid.NewGuid() }.
                Set(e => e.FullName, "CONTACT-001");
            var orderSummary = new new_ordersummary { Id = Guid.NewGuid() }
                .Set(e => e.new_customerid, customer.ToEntityReference())
                .Set(e => e.new_transactiondate, new DateTime(2020, 01, 01));
            var test = new TestEvent<new_ordersummary>(customer);
            // Execute the business class
            test.CreateEventCommand<SetSummaryName>(orderSummary);
            // Assert Output
            var name = orderSummary.Get(e => e.new_summaryname);
            Assert.AreEqual("CONTACT-001 2020 January", name);
        }
        [TestMethod]
        public void OrderSummary_SetSummaryName_Account()
        {
            // Set the DB
            var customer = new Account { Id = Guid.NewGuid() }.
                Set(e => e.Name, "ACCOUNT-001");
            var orderSummary = new new_ordersummary { Id = Guid.NewGuid() }
                .Set(e => e.new_customerid, customer.ToEntityReference())
                .Set(e => e.new_transactiondate, new DateTime(2020, 01, 01));
            var test = new TestEvent<new_ordersummary>(customer);
            // Execute the business class
            test.CreateEventCommand<SetSummaryName>(orderSummary);
            // Assert Output
            var name = orderSummary.Get(e => e.new_summaryname);
            Assert.AreEqual("ACCOUNT-001 2020 January", name);
        }
    }
}

Remember to always running the tests to see the results because TDD is all about how the test guiding the implementation of the code. We will get result Contact success and Account test failed.

After we know the test being failed in the Account scenario. Then the changes of the SetSummaryName class will be:

using System;
using Entities;
using Microsoft.Xrm.Sdk.Query;
using Niam.XRM.Framework;
using Niam.XRM.Framework.Data;
using Niam.XRM.Framework.Interfaces.Plugin;
using Niam.XRM.Framework.Plugin;
namespace OrderSummary.Plugins
{
    public class SetSummaryName : OperationBase<new_ordersummary>
    {
        public SetSummaryName(ITransactionContext<new_ordersummary> context) :
            base(context)
        {
        }
        protected override void HandleExecute()
        {
            var customerRef = Get(e => e.new_customerid);
            var transactionDate = Get(e => e.new_transactiondate);
            var valid = customerRef != null && transactionDate != null;
            if (!valid) return;
            var fullName = customerRef.LogicalName == Contact.EntityLogicalName ?
                GetContactName(customerRef.Id) : GetAccountName(customerRef.Id);
       
            var dateFormat = transactionDate.GetValueOrDefault().ToString("yyyy MMMM");
            Set(e => e.new_summaryname, fullName + " " + dateFormat);
        }
        private string GetAccountName(Guid customerRefId)
        {
            var accountData = Service
                .Retrieve(Account.EntityLogicalName, customerRefId, new ColumnSet<Account>(e => e.Name))
                .ToEntity<Account>();
            var accountName = accountData.Get(e => e.Name);
            return accountName;
        }
        private string GetContactName(Guid customerRefId)
        {
            var contactData = Service
                .Retrieve(Contact.EntityLogicalName, customerRefId, new ColumnSet<Contact>(e => e.FullName))
                .ToEntity<Contact>();
            var contactFullName = contactData.Get(e => e.FullName);
            return contactFullName;
        }
    }
}

If we running again the tests the result will be all green and means that we can continue to put this business logic class into the plugin (to be registered as plugin step).

Create Plugin Step

Now in the OrderSummary.Plugins project right-click > Add > New Item > Class > name it PreOrderSummaryCreate (we want to register this step in Pre-Operation). Then change the implementation of the class to below code:

using Entities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Niam.XRM.Framework.Interfaces.Plugin;
using Niam.XRM.Framework.Interfaces.Plugin.Configurations;
using Niam.XRM.Framework.Plugin;
namespace OrderSummary.Plugins
{
    public class PreOrderSummaryCreate : PluginBase<new_ordersummary>, IPlugin
    {
        public PreOrderSummaryCreate(string unsecure, string secure) : base(unsecure, secure)
        {
        }
        protected override void Configure(IPluginConfiguration<new_ordersummary> config)
        {
            config.ColumnSet = new ColumnSet(true);
        }
        protected override void ExecuteCrmPlugin(IPluginContext<new_ordersummary> context)
        {
            new SetSummaryName(context).Execute();
        }
    }
}

Until this point, we finished creating the plugin. If we want to deploy this Plugin, we need to sign the OrderSummary.Plugins project and merge the OrderSummary.Plugins.dll with Niam.Xrm.Framework.dll.


Happy Coding!

3 thoughts on “Dynamics CRM CE: TDD Plugin Development in Action – Part 1

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.