How to unit test C# Dynamics CRM interface code - part III
In parts one and two of this series, I gave an introduction to unit testing Dynamics CRM C# interfaces code with mock objects using NUnit and Moq, and I showed code samples for a couple of different scenarios. In this post I will show how to work with CRM metadata (optionset values, statuscode values, etc.) in your unit tests.
One limitation we run into when using Moq (and Rhino.Mocks, too) with the CRM SDK is that many of the objects that the CRM service returns are instances of sealed classes (looking at you, Xrm.Sdk.Messages namespace), so we can't set up useful mock responses. While this wasn't a problem for the code samples in my earlier posts on the topic, it's something you will run into if you try to put together comprehensive tests.
To illustrate, let's say you have a function called GetPicklistOptionCount that returns a count of a picklist's optionset values.
public static int GetPicklistOptionCount(string entityName, string picklistName, IOrganizationService service) { RetrieveAttributeRequest retrieveAttributeRequest = new RetrieveAttributeRequest { EntityLogicalName = entityName, LogicalName = picklistName, RetrieveAsIfPublished = true }; // Execute the request. RetrieveAttributeResponse retrieveAttributeResponse = (RetrieveAttributeResponse)service.Execute(retrieveAttributeRequest); // Access the retrieved attribute. PicklistAttributeMetadata retrievedPicklistAttributeMetadata = (PicklistAttributeMetadata)retrieveAttributeResponse.AttributeMetadata; OptionMetadata[] optionList = retrievedPicklistAttributeMetadata.OptionSet.Options.ToArray(); return optionList.Length; }
The code snippet below intuitively seems like it would be a good way to set up a mock:
//instantiate an optionset to hold some mock metadata PicklistAttributeMetadata retrievedPicklistAttributeMetadata = new PicklistAttributeMetadata(); OptionMetadata femaleOption = new OptionMetadata(new Label("Female", 1033), 43); //as with all our mocks, the actual values don't matter so long as they're consistent throughout the test femaleOption.Label.UserLocalizedLabel = new LocalizedLabel("Female", 1033); femaleOption.Label.UserLocalizedLabel.Label = "Female"; OptionMetadata maleOption = new OptionMetadata(new Label("Male", 1033), 400); maleOption.Label.UserLocalizedLabel = new LocalizedLabel("Male", 400); maleOption.Label.UserLocalizedLabel.Label = "Male"; OptionSetMetadata genderOptionSet = new OptionSetMetadata { Name = "gendercode", DisplayName = new Label("Gender", 1033), IsGlobal = true, OptionSetType = OptionSetType.Picklist, Options = { femaleOption, maleOption } }; retrievedPicklistAttributeMetadata.OptionSet = genderOptionSet; //instantiate a new RetrieveAttributeResponse to return in the mock RetrieveAttributeResponse response = new RetrieveAttributeResponse(); //set the metadata of our response object response.AttributeMetadata = retrievedPicklistAttributeMetadata; //THIS WON'T WORK! //set up the mock mock.Setup(t => t.Execute(It.Is<RetrieveAttributeRequest>(r=>r.LogicalName=="gendercode"))).Returns(response);
/// <summary> /// Wrapper class for the Xrm.Sdk.Messages.RetrieveAttributeResponse class. Primarily used to support Moq injection during testing. /// </summary> [DataContract(Namespace = "http://schemas.microsoft.com/xrm/2011/Contracts")] public class RetrieveAttributeResponseWrapper : OrganizationResponse { private AttributeMetadata _metadata; public RetrieveAttributeResponseWrapper(OrganizationResponse response) { try { _metadata = ((RetrieveAttributeResponseWrapper)response).AttributeMetadata; } catch { _metadata = ((RetrieveAttributeResponse)response).AttributeMetadata; } } public AttributeMetadata AttributeMetadata { get { return _metadata; } set { _metadata = value; } } }
public static int GetPicklistOptionCount(string entityName, string picklistName, IOrganizationService service) { RetrieveAttributeRequest retrieveAttributeRequest = new RetrieveAttributeRequest { EntityLogicalName = entityName, LogicalName = picklistName, RetrieveAsIfPublished = true }; // Execute the request. RetrieveAttributeResponseWrapper retrieveAttributeResponse = (new RetrieveAttributeResponseWrapper(service.Execute(retrieveAttributeRequest))); //this is the only change from before // Access the retrieved attribute. PicklistAttributeMetadata retrievedPicklistAttributeMetadata = (PicklistAttributeMetadata)retrieveAttributeResponse.AttributeMetadata; OptionMetadata[] optionList = retrievedPicklistAttributeMetadata.OptionSet.Options.ToArray(); return optionList.Length; }
[Test] public void GetPicklistOptionCountTest() { //ARRANGE - set up everything our test needs //first - set up a mock service to act like the CRM organization service var serviceMock = new Mock<IOrganizationService>(); IOrganizationService service = serviceMock.Object; PicklistAttributeMetadata retrievedPicklistAttributeMetadata = new PicklistAttributeMetadata(); OptionMetadata femaleOption = new OptionMetadata(new Label("Female", 1033), 43); femaleOption.Label.UserLocalizedLabel = new LocalizedLabel("Female", 1033); femaleOption.Label.UserLocalizedLabel.Label = "Female"; OptionMetadata maleOption = new OptionMetadata(new Label("Male", 1033), 400); maleOption.Label.UserLocalizedLabel = new LocalizedLabel("Male", 400); maleOption.Label.UserLocalizedLabel.Label = "Male"; OptionSetMetadata genderOptionSet = new OptionSetMetadata { Name = "gendercode", DisplayName = new Label("Gender", 1033), IsGlobal = true, OptionSetType = OptionSetType.Picklist, Options = { femaleOption, maleOption } }; retrievedPicklistAttributeMetadata.OptionSet = genderOptionSet; RetrieveAttributeResponseWrapper picklistWrapper = new RetrieveAttributeResponseWrapper(new RetrieveAttributeResponse()); picklistWrapper.AttributeMetadata = retrievedPicklistAttributeMetadata; serviceMock.Setup(t => t.Execute(It.Is<RetrieveAttributeRequest>(r => r.LogicalName == "gendercode"))).Returns(picklistWrapper); //ACT int returnedCount = MockDemo.GetPicklistOptionCount("ANYENTITYMATCHES", "gendercode", service); //ASSERT Assert.AreEqual(2, returnedCount); //we expect 2 to be returned }
This was originally posted here.
*This post is locked for comments