Dynamics 365 Unit Testing 101

Despite the fact that unit tests aren’t something new for most of us, that’s not always the case within the Dynamics 365 ecosystem. 🙁

In my experience, 1 out of 5 D365 developers write unit tests to test their code, actually, some of them build a console app to help them test their functionalities and there are even ones that just test their code after publishing into CRM, directly via web interface. 😮

There might be several reasons why developers usually don’t write unit tests. Sometimes the project is too small, the number of customisations is minimal, the project budget doesn’t stretch to cover the extra time needed to write tests, legacy code may seem impossible to test, test plugins and workflows are not a trivial task, your team are not writing test, etc.

Despite all those reasons not to write unit tests, the fact of having your code fully tested will guarantee to help you sleep better at night – literally! The chances of you receiving a call from your client in the middle of the night is much less if you have tests for your code.

I could quote the benefits of having your code fully tested here but that’s not the target for today’s post, the purpose here is to encourage you to start writing them by showing a practical real code example of how you can start testing your D365 plug-ins right now.

Keep in mind the following user story:

Whenever a new contact is created in CRM, if the record has an account related with it, the name of the contact record should be a concatenation of the account number and the contact name, if the record doesn’t have a account related an exception should be thrown.

The following code is a simple representation how to achieve our requirement and keep the example didactic:


using Microsoft.Xrm.Sdk;

using Microsoft.Xrm.Sdk.Query;

using System;

 

namespace Plugin.Account

{

public class PreCreate : IPlugin

{

public void Execute(IServiceProvider serviceProvider)

{

var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

 

var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

 

var service = serviceFactory.CreateOrganizationService(context.UserId);

 

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

{

var entity = (Entity)context.InputParameters["Target"];

 

if (entity.LogicalName == "contact")

{

var accountRef = entity.GetAttributeValue<EntityReference>("accountid");

&nbsp;

if (accountRef == null)

{

throw new InvalidPluginExecutionException("Please, provide a account for the contact");

}

else

{

var accountEntity = service.Retrieve("account", accountRef.Id, new ColumnSet("accountnumber"));

entity["firstname"] = $"{accountEntity.GetAttributeValue<string>("accountnumber")}-{entity["firstname"]}";

}

}

}

}

}

}

 

Pretty straightforward, right? The first thing that we have to bear in mind when writing  unit tests is: We don’t want to access external services, perform SDK calls or call any other code that is not related with the method that we want to test.

Why? Simple, we should only test our code! The D365 SDK methods aren’t our code! Why should we test someone else code if we just want to test our plugin?

Ok, with that in mind we can start…

Wait… Our plugin does one SDK call to achieve our requirement, if we don’t want to do SDK calls how can we test it?

There are different approaches to solve this issue. Maybe the disclosed one is Dependency Injection (DI).

Using DI, we could inject the dependencies that our plugin has (IServiceProvider) and we could define “fake” instances of those classes wherein instead of performing the expected operation we could just skip everything and return the result that we expect.

The most common strategies to implement DI is through injection by constructor or injection by setters, but we need to be careful with this kind of approaches as D365 developers.

In both strategies, the dependencies are stored into class members and since D365 instantiates one instance of each plugin running concurrently you might end up with dependencies from the plugin execution 1 into the plugin execution 2.

As an alternative, we could use a technique called “Extract & Override” described in Art of Unit Testing – By the way, if you didn’t read this book yet you should.

“Extract & Override is a very powerful technique because it lets you deal directly with replacing the dependency without going down the rabbit hole at all (changing dependencies deep inside the call stack). That makes it very quick and clean to perform, almost to the point where it corrupts your good sense of object oriented aesthetics, leading you to code that might even have less interfaces but more virtual methods.”

(Art of Unit Testing, pg.50)

Although this technique is not complex to implement, after extracting and overriding the dependencies we would still need to create mocks and/or stubs to our tests, and since this post is our Unit Test 101 we rather do something easy than something not complex, right?

How nice would it be if we could create a in-memory version of D365, just with the data that we need to run our tests, that we didn’t need to worry about mocks, stubs, DI or any other thing unrelated with our code?

Ladies and gentlemans, I introduce you Fake XRM Easy, this library is defined by the author as:

“The testing framework for Dynamics CRM and Dynamics 365 which runs on an In-Memory context and deals with mocks or fakes for you.”

Wait!? That’s exactly what we need.

The library is opensource, the documentation is great and the website has even animations to guide us step by step through the samples.

Enough talking, what about the test for our plugin? Show me the code:


using System;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using FakeXrmEasy;

using Microsoft.Xrm.Sdk;

&nbsp;

namespace Plugin.Account.Test

{

[TestClass]

public class UnitTest1

{

[TestMethod]

[ExpectedException(typeof(InvalidPluginExecutionException))]

public void ContactPreCreatePlugin_ContactWithoutAccount_ShouldThrowAnException()

{

var fakedContext = new XrmFakedContext();

&nbsp;

var target = new Entity("contact") { Id = Guid.NewGuid() };

&nbsp;

//Execute our plugin against a target that doesn't contains the account

var fakedPlugin = fakedContext.ExecutePluginWithTarget<PreCreate>(target);

&nbsp;

//Because of the ExpectedException annotation, if this

//test doenst thrown an exception the test will fail

}

&nbsp;

[TestMethod]

public void ContactPreCreatePlugin_ContactWithAccount_ContactNameShouldBeAConcatenationOfAccountNumberAndContactName()

{

var fakedContext = new XrmFakedContext();

&nbsp;

var accountId = Guid.NewGuid();

&nbsp;

fakedContext.Initialize(new Entity("account")

{

Id = accountId,

["accountnumber"] = "1"

});

&nbsp;

var target = new Entity("contact")

{

Id = Guid.NewGuid(),

["firstname"] = "Henrique",

["accountid"] = new EntityReference("account", accountId)

};

&nbsp;

//Execute our plugin against a target that doesn't contains the account

var fakedPlugin = fakedContext.ExecutePluginWithTarget<PreCreate>(target);

&nbsp;

//Assert that the target contains a new attribute

Assert.IsTrue(target.GetAttributeValue<string>("firstname").Equals("1-Henrique"));

}

}

}

 

The only few things that we needed to do were:

  • Download the Fake Xrm Easy lib – Which you can easily do via Nuget and Referencing in our test project.
  • Create our CRM context and initialize with the data that we want.
  • Execute the plugin and make the necessaries asserts.

 

If you made it until here, I’m sorry but you don’t have more excuses not to write unit tests!

About Technical Hero: Dimitri 'Reverso Engineero' Chevkov

The Technical Hero of the Dynamics 365 Heroes Team A programming wizard, I was a child prodigy and had hacked into 7 separate government agencies by the time I was 16 years old. Older, wiser, more experienced and seeing the errors of my impetuous youth, I now spends my time helping others to extend their Dynamics platform beyond what was previously thought as the limit. I can rush into developing a solution to a problem which would be better solved using the Dynamics 365 platform capabilities, and am constantly kept in check by Howard.

Leave a Reply