Unit testing custom Microsoft Dynamics CRM code – Part 3 (intermediate interface example)

In my last post I gave an introduction to unit testing Dynamics CRM C# interfaces code with mock objects using Visual Studio 2012 and Moq. The sample code in that post was extremely simple, so I wanted to follow up with a more complex example that shows how to test multiple calls to the CRM web service instead of just a single one.

This example supposes you have been asked to write a method that will take a company name as an input and then create a new CRM account. This method will also create a follow-up task linked to the account, and the subject of the task must contain an account number that is generated by a plug-in upon creation of the account.

First, let's think about how we would test this method before we start writing any code. After we have outlined our basic testing approach, writing the actual code will be much easier.

To test this method, we need to verify a few different things:

  1. We send CRM a create request that contains an account with the correct name value.
  2. We send CRM a create request that contains a task with the correct subject value (including the account number) and regardingobjectid value.

These two tests address the requirements, but they don't account for the reality that we need to make a retrieve request to CRM to get the account number upon account creation. So, we should add a third test step to validate sending CRM a retrieve request for the account number using the account id value that is returned by creating the account.

Now that we have established what we have to test, we can write a method that makes the calls. It would look something like this:

/// <summary>
/// Creates a new account with a given name and then creates a follow-up task linked to the account
/// </summary>
/// <param name="accountName">account name</param>
/// <param name="service">organization service</param>
public static void CreateCrmAccount2(string accountName, IOrganizationService service)
{
	//create the account
	Entity account = new Entity("account");
	account["name"] = accountName;
	Guid newId = service.Create(account);
	//get the account number
	account = service.Retrieve("account", newId, new Microsoft.Xrm.Sdk.Query.ColumnSet(new string[] { "name", "accountid", "accountnumber" }));
	string accountNumber = account["accountnumber"].ToString();
	//create the task
	Entity task = new Entity("task");
	task["subject"] = "Finish account set up for " + accountName + " - " + accountNumber;
	task["regardingobjectid"] = new Microsoft.Xrm.Sdk.EntityReference("account", newId);
	service.Create(task);
}

Unlike in my first mocking example, we have two different calls to the service Create method, so we have to tell our mock service how to handle each. Previously we used a Setup like this to handle all calls to the Create method - serviceMock.Setup(t => t.Create(It.IsAny<Entity>())).Returns(idToReturn). The It.IsAny<Entity>() tells the mock service to return idToReturn in response to a Create call made with any Entity object as an input parameter.

This time around we will use a Setup for each Create call that matches on Entity.LogicalName. Because the Setup takes a Linq expression as a parameter, this is how we match on the LogicalName value - Create(It.Is<Entity>(e => e.LogicalName.ToUpper() == "account".ToUpper())).

We only have one Retrieve call to handle, but let's go ahead and be specific with it like we are being with the Create calls. The match for our task creation step would look like this:

Retrieve(
	It.Is<string>(e => e.ToUpper() == "account".ToUpper()),
	It.Is<Guid>(e => e == idToReturn),
	It.IsAny<Microsoft.Xrm.Sdk.Query.ColumnSet>())
)

This matches all Retrieve requests for an account with a specific id and any ColumnSet value. We could match the specific ColumnSet value specified in the method under test, but using an It.IsAny match makes sure a change to the columns retrieved doesn't cause the test to fail.

With the setup work complete, now it's just a matter of sending some mock data through the CreateCrmAccount2 method and making sure we handle the responses correctly. Here is the complete test method:

/// <summary>
/// Tests the CreateCrmAccount2 method
/// </summary>
[TestMethod]
public void CreateCrmAccount2_Test()
{
	//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;
	//next - set a name and account number for our fake account record to create
	string accountName = "Lucas Demo Company";
	string accountNumber = "LPA1234";
	//next - create a guid that we want our mock service Create method to return when called
	Guid idToReturn = Guid.NewGuid();
	//next - create an object that will allow us to capture the account object that is passed to the Create method
	Entity createdAccount = new Entity();
	//next - create an entity object that will allow us to capture the task object that is passed to the Create method
	Entity createdTask = new Entity();
	//next - create an mock account record to pass back to the Retrieve method
	Entity mockReturnedAccount = new Entity("account");
	mockReturnedAccount["name"] = accountName;
	mockReturnedAccount["accountnumber"] = accountNumber;
	mockReturnedAccount["accountid"] = idToReturn;
	mockReturnedAccount.Id = idToReturn;
	//finally - tell our mock service what to do when the CRM service methods are called
	//handle the account creation
	serviceMock.Setup(t =>
		t.Create(It.Is<Entity>(e => e.LogicalName.ToUpper() == "account".ToUpper()))) //only match an entity with a logical name of "account"
		.Returns(idToReturn) //return the idToReturn guid
		.Callback<Entity>(s => createdAccount = s); //store the Create method invocation parameter for inspection later
	//handle the task creation
	serviceMock.Setup(t =>
		t.Create(It.Is<Entity>(e => e.LogicalName.ToUpper() == "task".ToUpper()))) //only match an entity with a logical name of "task"
		.Returns(Guid.NewGuid()) //can return any guid here
		.Callback<Entity>(s => createdTask = s); //store the Create method invocation parameter for inspection later
	//handle the retrieve account operation
	serviceMock.Setup(t =>
		t.Retrieve(
				It.Is<string>(e => e.ToUpper() == "account".ToUpper()),
				It.Is<Guid>(e => e == idToReturn),
				It.IsAny<Microsoft.Xrm.Sdk.Query.ColumnSet>())
			) //here we match on logical name of account and the correct id
		.Returns(mockReturnedAccount);
	//ACT - do the thing(s) we want to test
	//call the CreateCrmAccount2 method like usual, but supply the mock service as an invocation parameter
	MockDemo.CreateCrmAccount2(accountName, service);
	//ASSERT - verify the results are correct
	//verify the entity created inside the CreateCrmAccount method has the name we supplied 
	Assert.AreEqual(accountName, createdAccount["name"]);
	//verify task regardingobjectid is the same as the id we returned upon account creation
	Assert.AreEqual(idToReturn, ((Microsoft.Xrm.Sdk.EntityReference)createdTask["regardingobjectid"]).Id);
	Assert.AreEqual("Finish account set up for " + accountName + " - " + accountNumber, (string)createdTask["subject"]);
}

Something to note when testing this is that it doesn't matter if the mock account number you use matches the format of your real account numbers. Because you are only pretending to generate / retrieve / pass the account number, you can make it anything you want without having to worry about whether this compromises your test.

The code samples for this example can be downloaded here, and the testing code can be downloaded here.

In my next post in this series, we'll take a look at using wrapper classes to help us test operations where we need to mock behavior that uses sealed classes in the CRM SDK like PicklistAttributeMetadata.

A version of this post was originally published on the HP Enterprise Services Application Services blog.

comments powered by Disqus