Hello;
I thoroughly enjoy writing unit tests with FakeXRMEasy. It’s a framework that allows you to fake the Dynamics 365 services into the proper contexts and then use the messages against it. It can be found at https://dynamicsvalue.com/.
The positives I’ve found from using this framework are:
- I’m able to debug the custom code without ever having to leave Visual Studio.
- No more spending time getting plugins to debug properly through visual studio
- I’m able to replicate a wide variety of scenarios through data in my unit tests without having to step through everyone within the Dynamics 365 platform itself.
- Works with custom workflow activities, plugins, portals, javascript.
Let’s walk through a piece of code. The scenario is we are building a sports application in Dynamics 365; when a game ends a plugin triggers. This plugin updates the winning team with the win as well a point total; then adds a loss to the losing team. Let’s examine the plugin first.
public void Execute(IServiceProvider serviceProvider) { if(serviceProvider ==null) { throw new ArgumentNullException("serviceProvider", "serviceProvider cannot be a null refrence"); } IPluginExecutionContext context = (IPluginExecutionContext)(serviceProvider.GetService(typeof(IPluginExecutionContext))); IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId); ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); //Lets get the game information and update each teams record Entity target = (Entity)context.InputParameters["Target"]; try { Entity aGame = null; tracingService.Trace("Let's get the game entity with all of it's columns. The ID is:" + target.Id.ToString()); if(context.MessageName== "Update") { aGame = (Entity)service.Retrieve(target.LogicalName, target.Id, new Microsoft.Xrm.Sdk.Query.ColumnSet(true)); } else { throw new Exception("Plugin:EndGame - This plugin should only be executed against an update message not a create or delete."); } //Let's validate that it returned something if(aGame != null) { EntityReference homeTeam = null; if(aGame.Attributes.Contains("jmvp_hometeam")) { homeTeam = (EntityReference)aGame.Attributes["jmvp_hometeam"]; } EntityReference awayTeam = null; if (aGame.Attributes.Contains("jmvp_awayteam")) { awayTeam = (EntityReference)aGame.Attributes["jmvp_awayteam"]; } OptionSetValue gameOutcome = null; if (aGame.Attributes.Contains("jmvp_outcome")) { gameOutcome = (OptionSetValue)aGame.Attributes["jmvp_outcome"]; //Home - 492,470,000 //Away - 492,470,001 //Tie - 492,470,002 } Entity homeTeamEntity = getEntitywithFields(homeTeam, true, service); Entity awayTeamEntity = getEntitywithFields(awayTeam, true, service); switch(gameOutcome.Value) { //Home case 492470000: homeTeamEntity.Attributes["jmvp_winrecord"] = (int)homeTeamEntity.Attributes["jmvp_winrecord"] + 1; homeTeamEntity.Attributes["jmvp_totalpoints"] = (int)homeTeamEntity.Attributes["jmvp_totalpoints"] + 2; awayTeamEntity.Attributes["jmvp_lossrecord"] = (int)awayTeamEntity.Attributes["jmvp_lossrecord"] + 1; service.Update(homeTeamEntity); service.Update(awayTeamEntity); break; //Away case 492470001: awayTeamEntity.Attributes["jmvp_winrecord"] = (int)awayTeamEntity.Attributes["jmvp_winrecord"] + 1; awayTeamEntity.Attributes["jmvp_totalpoints"] = (int)awayTeamEntity.Attributes["jmvp_totalpoints"] + 2; homeTeamEntity.Attributes["jmvp_lossrecord"] = (int)homeTeamEntity.Attributes["jmvp_lossrecord"] + 1; service.Update(awayTeamEntity); service.Update(homeTeamEntity); break; //Tie case 492470002: break; } } } catch(Exception ex) { throw (ex); } } private Entity getEntitywithFields(EntityReference entityToRetrieve, Boolean columnSet, IOrganizationService service) { return service.Retrieve(entityToRetrieve.LogicalName, entityToRetrieve.Id, new Microsoft.Xrm.Sdk.Query.ColumnSet(columnSet)); } }
As you can see it simple does a check on the jmvp_outcome field to determine who won and updates appropriately. Now using FakeXRMEasy let’s walk through two separate unit tests. The first one is where the home team wins the game.
[TestMethod()] public void HomeTeamWins() { var fakedContext = new XrmFakedContext(); var team1 = new Entity("jmvp_sportsteam"); team1.Id = Guid.NewGuid(); team1["jmvp_name"] = "Canadiens"; team1["jmvp_winrecord"] = 10; team1["jmvp_lossrecord"] = 8; team1["jmvp_totalpoints"] = 20; var team2 = new Entity("jmvp_sportsteam"); team2.Id = Guid.NewGuid(); team2["jmvp_name"] = "Maple Leafs"; team2["jmvp_winrecord"] = 12; team2["jmvp_lossrecord"] = 6; team2["jmvp_totalpoints"] = 24; var game1 = new Entity("jvmp_game"); game1.Id = Guid.NewGuid(); game1["jvmp_name"] = "November 5th - Canadiens vs Maple Leafs"; game1["jmvp_hometeam"] = new EntityReference(team1.LogicalName, team1.Id); game1["jmvp_awayteam"] = new EntityReference(team2.LogicalName, team2.Id); game1["jmvp_outcome"] = new OptionSetValue(492470000); fakedContext.Initialize(new List<Entity>() { team1, team2, game1, game2 }); ParameterCollection inputParameters = new ParameterCollection(); inputParameters.Add("Target", game1); var plugCtx = fakedContext.GetDefaultPluginContext(); plugCtx.MessageName = "Update"; plugCtx.InputParameters = inputParameters; plugCtx.Depth = 1; var FakedPlugin = fakedContext.ExecutePluginWith<EndGame>(plugCtx); IOrganizationService service = fakedContext.GetOrganizationService(); Entity updatedHomeTeam = service.Retrieve(team1.LogicalName, team1.Id, new Microsoft.Xrm.Sdk.Query.ColumnSet(true)); Entity updatedAwayTeam = service.Retrieve(team2.LogicalName, team2.Id, new Microsoft.Xrm.Sdk.Query.ColumnSet(true)); Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreEqual(((int)team1["jmvp_winrecord"] + 1), (int)updatedHomeTeam["jmvp_winrecord"]); Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreEqual(((int)team1["jmvp_totalpoints"] + 2), (int)updatedHomeTeam["jmvp_totalpoints"]); Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreEqual(((int)team2["jmvp_lossrecord"] + 1), (int)updatedAwayTeam["jmvp_lossrecord"]); }
Let’s dive a bit deeper into this unit test. First we create the entities and data set. Then we load it into the faked context “XrmFakedContext”.
var fakedContext = new XrmFakedContext(); var team1 = new Entity("jmvp_sportsteam"); team1.Id = Guid.NewGuid(); team1["jmvp_name"] = "Canadiens"; team1["jmvp_winrecord"] = 10; team1["jmvp_lossrecord"] = 8; team1["jmvp_totalpoints"] = 20; var team2 = new Entity("jmvp_sportsteam"); team2.Id = Guid.NewGuid(); team2["jmvp_name"] = "Maple Leafs"; team2["jmvp_winrecord"] = 12; team2["jmvp_lossrecord"] = 6; team2["jmvp_totalpoints"] = 24; var game1 = new Entity("jvmp_game"); game1.Id = Guid.NewGuid(); game1["jvmp_name"] = "November 5th - Canadiens vs Maple Leafs"; game1["jmvp_hometeam"] = new EntityReference(team1.LogicalName, team1.Id); game1["jmvp_awayteam"] = new EntityReference(team2.LogicalName, team2.Id); game1["jmvp_outcome"] = new OptionSetValue(492470000); fakedContext.Initialize(new List<Entity>() { team1, team2, game1, game2 });
Once we’ve initialized our fakedContext we’re ready to setup the input parameters and define some information around this plugin. When setting up the plugin information we’re able to select the message name which allows us to handle all of messages; which means we can unit test against any of the events. We set the entity that’s trigger the plugin and how much data we want to pass into it. Then i’m setting the depth of the plugin which can be important. The last thing that needs to be done is to actually execute the plugin itself.
ParameterCollection inputParameters = new ParameterCollection(); inputParameters.Add("Target", game1); var plugCtx = fakedContext.GetDefaultPluginContext(); plugCtx.MessageName = "Update"; plugCtx.InputParameters = inputParameters; plugCtx.Depth = 1; var FakedPlugin = fakedContext.ExecutePluginWith<EndGame>(plugCtx);
The last thing that I do is to validate my expected outcomes. In this case I want to ensure team 1’s properly got the additional points and win record. As well team 2’s loss total has increased by one.
Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreEqual(((int)team1["jmvp_winrecord"] + 1), (int)updatedHomeTeam["jmvp_winrecord"]); Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreEqual(((int)team1["jmvp_totalpoints"] + 2), (int)updatedHomeTeam["jmvp_totalpoints"]); Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AreEqual(((int)team2["jmvp_lossrecord"] + 1), (int)updatedAwayTeam["jmvp_lossrecord"]);
Although this is a very simple example of a unit test; I think it gives a good example of whats within the capability of mocking up the fakedContext.
Hope this helps!