Develop Better CRM Implementations Using Simulations


Today’s guest blogger is CRM MVP George Doubinski.

Reality check

Recently my family spent some time in Fiji and our stay included a Fijian village tour. Kids were overwhelmed and amused by locals demonstrating olden arts of tribal dance; bare feet palm abseiling and cracking coconuts with bare hands. Did the performances look real? Absolutely. Did I think even for a second that what we saw was a normal everyday life in the village? Not really. Did my kids think that? Most definitely. The objective was achieved: children were happily communicating with what they perceived to be a reality.

Duck test

What does it have to do with CRM, you say? Today I would like to demonstrate the not so olden technique of mocking which is very closely related to the village tour.

“In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A computer programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior of a human in vehicle impacts.” - Wikipedia

They both satisfy the duck test: “If it looks like a duck, walks like a duck and quacks like a duck, then it probably is a duck.”

In a nutshell, I use mocking as a simulation of the behaviour of complex objects when using real objects in unit test is impractical or impossible. And, as savvy developers, we believe in unit testing and some of us even include them into released applications.

But who would want to use unit tests with Microsoft CRM? ISVs and developers of tools and libraries that interface with Microsoft CRM are typical examples. However, for unit tests to be effective in the development lifecycle, they need to be fast and repetitive. Microsoft CRM, unfortunately, does not quite lend itself to that pattern.

  • If we want to use the CRM database in a well-known state then there is no other method but to restore the CRM database from a backup every time we want to run a test suite.
  • If we truly want to run individual tests in isolation, then CRM authentication will have to occur quite frequently at the beginning of every test slowing things down
  • Developers’ machines aren’t production servers and performance could be a problem.
  • Some scenarios like organisation provisioning require more than one server to test properly

But what are we actually trying to test? It is most certainly not Microsoft CRM. What we’re trying to test is code functionality that interfaces with Microsoft CRM. As long as behaviour of CRM interfaces is predictable and according to our expectations, we do not need to have real SOAP fragments flying around forcing Microsoft CRM to busily spin the disks and burn megaflops.

That’s where mocking libraries come in. There is a vast choice of mocking libraries in .NET: Typemock, Rhino Mock, NMock to name but a few. Today we’re going to use new kid on the block, Moq library, written from ground up in .NET 3.5.

The important thing to note before diving into code is that mocking works best with interfaces. Luckily, CRM SDK libraries do provide interface implementation of the services, ICrmService and IMetadataService that we’re going to use in our tests.

You are fired

Let’s consider a typical scenario. You’re writing error handling code for your assembly that is supposed to fall back gracefully if CRM throws some exceptions at it. And while we all have seen errors and exceptions in live systems, according to Murphy law it’ll be nearly impossible to reproduce one reliably in testing. Let’s say we want to play the scenario where a CRM user can create but is unable to delete an account record, because they don’t have delete privileges for accounts. The test would be something along the lines:

   1: [TestMethod]
   2: public void TestMethod()
   3: {
   4:    // Impersonate a required user account here
   5:    // and obtain CRM service interface. 
   6:    // Implementation details of GetImpersonatedService
   7:    // are omitted for brevity.
   8:    ICrmService service = GetImpersonatedService();
   9:        
  10:    try
  11:    {
  12:        account a = new account();
  13:        a.name = "Acme";
  14:        Guid id = service.Create(a);
  15:        service.Delete("account", id);
  16:    }
  17:    catch (SoapException ex)
  18:    {
  19:       // extract exception details and verify that 
  20:       // error code is indeed 'insufficient priveleges' 
  21:       // See http://msdn.microsoft.com/en-us/library/bb928443.aspx
  22:       // for details about handling CRM errors
  23:  XmlNode node = ex.Detail.SelectSingleNode("//code");
  24:  Assert.AreEqual("0x80040220", node.InnerText);
  25:    }
  26: }

While the above code is reasonably straightforward, think about what’s involved in setting it up. You need to:

1. Ensure that database is in consistent state (e.g. restored from a well-known backup).

2. Create a special role where delete privileges are removed for accounts

3. Create a user account

4. Assign the role to the account.

5. Tear down the changes to the database (after all, we have not deleted the account created!).

And, quite importantly, the setup process has to be fast and repeatable. Let’s see how mocking can help.

As we have seen, CRM exceptions are not for the faint-hearted. First, we need to create a function that would reproduce a required exception for us. To get the exception details, we’ve captured and dissected real CRM exception. Luckily, we only had to do it once.

   1: private SoapException GetPermissionException()
   2: {
   3:    XmlDocument doc = new XmlDocument();
   4:    XmlNode errNode = doc.CreateNode(XmlNodeType.Element,
   5: SoapException.DetailElementName.Name,
   6: SoapException.DetailElementName.Namespace);
   7:    errNode.InnerXml = @"
   8: <error>
   9:   <code>0x80040220</code>
  10:   <description>SecLib::CrmCheckPrivilege failed. Returned hr = -2147220960 on UserId: 17c3e0cc-b335-de11-ae53-000c29c1c0e6 and PrivilegeId: ca6c7690-c935-46b3-bfd2-abb306c2acc0</description>
  11:   <type>Platform</type>
  12: </error>";
  13:  
  14:    return new SoapException(
  15: "Server was unable to process request.", 
  16: SoapException.ServerFaultCode, 
  17: "", errNode);
  18: }

Now we’re ready to set up a test with a mock object.

   1: [TestMethod]
   2: public void TestMethodMoq()
   3: {
   4:    // Create mock object. 
   5:    Mock<ICrmService> crmService = new Mock<ICrmService>();
   6:    
   7:    SoapException soapex = GetPermissionException();
   8:  
   9:    // Setup mock object to throw the permission exception
  10:    // whenever Delete method is called
  11:    crmService
  12:       .Setup(s => s.Delete("account", It.IsAny<Guid>()))
  13:       .Throws(soapex);
  14:  
  15:    // get the CRM service out of the mock
  16:    ICrmService service = crmService.Object;
  17:  
  18:    // The following code is *identical* to the test run 
  19:    // against real CRM system. In other words, our code
  20:    // is blissfully unaware that it runs against mock. 
  21:    try
  22:    {
  23:        account a = new account();
  24:        a.name = "Acme";
  25:        Guid id = service.Create(a);
  26:        service.Delete("account", id);
  27:    }
  28:    catch (SoapException ex)
  29:    {
  30:       // extract exception details and verify that 
  31:       // error code is indeed 'insufficient priveleges' 
  32:       // See http://msdn.microsoft.com/en-us/library/bb928443.aspx
  33:       // for details about handling CRM errors
  34:  XmlNode node = ex.Detail.SelectSingleNode("//code");
  35:  Assert.AreEqual("0x80040220", node.InnerText);
  36:     }
  37: }

Though setting up the exception in code looks like a very tedious exercise, this approach actually has quite a few advantages:

  • There is a full control over the content of the exception down to the ability to insert correct user id and PrivilegeId (if your code, for example, takes advantage of those values)
  • It’s possible to throw any exception including those that are difficult to reproduce repeatedly in automated tests such as network timeouts, exceptions caused by racing conditions, etc.
  • Since CRM server is not involved, there is no tedious setup, the test is extremely fast and easily repeatable

If you are serious developer and your component is designed to communicate with Microsoft CRM you should use unit tests for that component. And to run, your unit tests should not require CRM installation.

After all, we are not testing CRM. We’re just making a mock or simulation out of a complex scenario.

Happy coding,

George Doubinski

· https://mvp.support.microsoft.com/profile/george.doubinski

Comments (0)

Skip to main content