Skip to content

Testability for Dynamics CRM SDK code

During the last months I had to code several web-cervices (ASP.Net WCF Service) for connecting other pieces of software with Dynamics CRM. I’ve used the Dynamics CRM SDK (2011, 2015 and 2016) to access CRM from within my C# code.

For those of you who don’t know the Dynamics CRM SDK: it’s a bunch of .Net libraries (and tools) you can use in your C# code to query and update data and metadata in Dynamics CRM.

It provides not only one way to access CRM but about three or four. One is a typed Linq API similar to the EntityFramework where one uses generated C# classes and a fluent API. Another one is accessing CRM untyped using string identifiers for entity and fields names. The third option is to build a transaction style API where one build a transaction request object contains create and update requests. One then executes this request using the SDK and CRM will (try) executing all the contained create and update statements within one transaction.

Tip: For code running outside of Dynamics CRM the transaction request allows you to get multiple write-operations executed within one transaction.

Problems

Connecting to CRM is not quite easy and if you are connected you get multiple objects for accessing CRM (typed and untyped API’s). There is no clean session object.

As unit-testing my code is a must have for my daily work I quickly ran into the issue how to test my Dynamics CRM code. In the first spot this seems tricky as your code highly depends on the existence of Dynamics CRM. There are some caveats in SDK itself like sealed classes, missing interfaces and missing common base classes.

Session-Handling

As a start for my upcoming business logic I like to have a nice disposable session object which provides access to miscellaneous parts of the CRM SDK. I also like to have a easy to use factory for creating sessions (connections). All of this should be ready for the inversion of control (IoC) pattern using Depencendy Injection (DI). More precisely for using constructor injection which is my prevered way of how to do DI.

This means that I have to use C# interfaces and constructor parameters requesting the interfaces they need and depend on.

Let’s code!

First, the Session interface and implementation which holds the SDK’s OrganizationContext and OrganizationService:

ICrmSession Interface:

/// <summary>
/// Wraps the xRM <see cref="IOrganizationService"/> and <see cref="OrganizationServiceContext"/>
/// in a session that gets disposed correctly.
/// </summary>
/// <remarks>Make sure you use <c>using</c> or call <see cref="IDisposable.Dispose"/> manually!</remarks>
public interface ICrmSession : IDisposable
{
    /// <summary>
    /// Gets the organization service.
    /// </summary>
    /// <value>The organization service.</value>
    /// <seealso cref="IOrganizationService"/>
    IOrganizationService OrganizationService { get; }

    /// <summary>
    /// Gets the typed organization service context.
    /// </summary>
    /// <value>The organization service context.</value>
    /// <seealso cref="OrganizationServiceContext"/>
    OrganizationServiceContext OrganizationServiceContext { get; }
}

Then the implementation of ICrmSession which also implements the IDisposable interface. Note that my code uses C# 6 features. If you target an older version of C# you have to do some minor changes to the code.

CrmSession Implementation:

/// <summary>
/// Wraps the xRM  and 
/// in a session that gets disposed correctly.
/// </summary>
/// 
/// 
/// 
public class CrmSession : ICrmSession
{
    public CrmSession(IOrganizationService organizationService, OrganizationServiceContext organizationServiceContext)
    {
        if (organizationService == null) throw new ArgumentNullException(nameof(organizationService));
        if (organizationServiceContext == null) throw new ArgumentNullException(nameof(organizationServiceContext));

        OrganizationService = organizationService;
        OrganizationServiceContext = organizationServiceContext;
    }

    /// 
    public IOrganizationService OrganizationService { get; private set; }

    /// 
    public OrganizationServiceContext OrganizationServiceContext { get; private set; }

    /// 
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected bool IsDisposed { get; private set; }

    protected virtual void Dispose(bool disposing)
    {
        if (!IsDisposed)
        {
            OrganizationServiceContext?.Dispose();
            OrganizationServiceContext = null;

            (OrganizationService as IDisposable)?.Dispose();
            OrganizationService = null;

            IsDisposed = true;
        }
    }

    ~CrmSession()
    {
        Dispose(false);
    }
}

Nothing too fancy so far. Now we need a factory to easily create such ICrmSession instances. First we need an interface again.

ICrmSessionFactory Interface:

public interface ICrmSessionFactory
{
    /// <summary>
    /// Creates a new session and connects to the CRM instance.
    /// </summary>
    ICrmSession CreateSession();
}

And now the implementation of ICrmSessionFactory. It creates the OrganizationServiceProxy and OrganizationServiceContext CRM SDK which are used to connect to CRM. it also configures the support for the typed proxy classes and finally construct a CrmSession instance with the all of this.

CrmSessionFactory Implementation:

/// <summary>
/// Implements <see cref="ICrmSession"/> against Dynamics CRM.
/// </summary>
public class CrmSessionFactory : ICrmSessionFactory
{
    readonly Uri defaultOrgUri;
    readonly ClientCredentials defaultOrgCredentials;

    public CrmSessionFactory(ICrmSessionFactoryConfig config)
    {
        if (config == null) { throw new ArgumentNullException(nameof(config)); }

        defaultOrgUri = config.ServerUri;
        defaultOrgCredentials = config.Credentials;
    }

    internal CrmSessionFactory(Uri defaultOrgUri, ClientCredentials defaultOrgCredentials)
    {
        if (defaultOrgUri == null) { throw new ArgumentNullException(nameof(defaultOrgUri)); }

        this.defaultOrgUri = defaultOrgUri;
        this.defaultOrgCredentials = defaultOrgCredentials;
    }

    /// <inheritDoc/>
    public ICrmSession CreateSession()
    {
        var organizationServiceProxy = new OrganizationServiceProxy(defaultOrgUri, null, defaultOrgCredentials, null);
        var organizationServiceContext = new OrganizationServiceContext(organizationServiceProxy);
        organizationServiceProxy.EnableProxyTypes();

        return new CrmSession(organizationServiceProxy, organizationServiceContext);
    }
}

The code above should look familiar to CRM developers. As our factory does have a constructor that takes a configuration object we now need the interface and implementation for that. Later on we can use this configuration object to configure our factory instances right from the DI container.

ICrmSessionFactoryConfig Interface for easy configuration of the factory

public interface ICrmSessionFactoryConfig
{
    Uri ServerUri { get; }
    ClientCredentials Credentials { get; }
}

CrmSessionFactoryConfig Implementation

public class CrmSessionFactoryConfig : ICrmSessionFactoryConfig
{
    public CrmSessionFactoryConfig(string orgUrl, string orgUsername, string orgPassword)
    {
        if (string.IsNullOrWhiteSpace(orgUrl)) { throw new ArgumentNullException(nameof(orgUrl)); }
        if (orgUsername == null) { throw new ArgumentNullException(nameof(orgUsername)); }

        ServerUri = new Uri(orgUrl);
        Credentials = new ClientCredentials
        {
            UserName =
            {
                UserName = orgUsername,
                Password = orgPassword,
            }
        };
    }

    public Uri ServerUri { get; }
    public ClientCredentials Credentials { get; }
}

So far the above code is generic. We can but it into a central library for reuse in other apps and projects. Build a Nuget package for it allows you to easily update it later on.

Example on how to use the sessions

Now I can create and use the session like this:

var config = new CrmSessionFactoryConfig('<orgurl>', '<orgusername>', 'orguserpwd');
var factory = new CrmSessionFactory(config);

using (var session = fatory.CreateSession())
{
    var query session.OrganizationServiceContext
        .CreateQuery<MyEntity>()
        .Where(p => p.Firstname == "John")
    ...
}

Note: After leaving the using scope the CrmSession and therefore the connection to the CRM gets disposed automatically.

This code is clean but did not save a lot of lines of code. But we now have a clear separation in place (decoupling).

Example for using with Dependency Injection (DI)

As we have everything decoupled nicely we now can inject them using Dependency Injection. To do so add a DI container and configure it somewhere in your applications startup. The following code show how to register a `ICrmSessionFactoryConfigsingleton instance and theICrmSessionFactory“ using SimpleInjector.

var container = new Container();
container.Register<ICrmSessionFactoryConfig>(() => new CrmSessionFactoryConfig(crmwsConfig.OrgUrl, crmwsConfig.OrgUser, crmwsConfig.OrgPassword));
container.Register<ICrmSessionFactory, CrmSessionFactory>();
// TODO: Add other type registrations here
// TODO: Use this container to create the instances of your root objects (see container documentation)
...

Note 1: Using other DI containers like Unity, DryLock, Ninject, etc. is basically the same but may vary in the syntax.

Note 2: Beside creating and configuring the DI container you must make sure that the instances of your objects do get created by your DI container so the constructor injection is in place. How this is done depends on the type of application and which DI container you are using. See your DI container documentation and samples for More information. There probably is a Nuget package for your container and type of application to set up these things automatically.

With this registration in place I’ve build my service classes containing the logic like this:

public interface IMyService 
{
    void Serve(string name);
}

public class MyService : IMyService
{
    private readonly ICrmSessionFactory sessionFactory;

    public MyLogic(ICrmSessionFactory sessionFactory)
    {
        if (sessionFactory == null) { throw new ArgumentNullException(nameof(sessionFactory)); }

        this.sessionFactory = sessionFactory;
    }

    /// <inheritdoc />
    public virtual void Serve>(string name)
    {
        using (var session = sessionFactory.CreateSession())
        {
            var request = new ExecuteTransactionRequest
            {
                Requests = new OrganizationRequestCollection(),
                ReturnResponses = true,
            };

            var newEntity = new MyEntity() { 
                Name = name,
            };

            request.Requests.Add(new CreateRequest
            { 
                Target = new MyEntity() 
                { 
                    ... 
                } 
            });

            session.OrganizationService.Execute(request);
        }
    }

Note: I also registered IMyService with my DI container so one level up
(for example in your WebAPI controller) you can request an instance of MyService using constructor parameters too.

public class HomeController()
{
    private IMyService myService;

    public HomeController(IMyService myService)
    {
        if (myService == null) { throw new ArgumentNullException(nameof(myService)) }

        this.myService = myService;
    }

    public ActionResult Index()
    {
        myService.Serve(...)
        ...
    }
}

Unit-Testing

The above was pretty straight forward and gives us a nice layer of abstraction and decoupling. With this in place Unit-Testing of your service logic in `MyController“ can be done by faking or mocking the ICrmSession. This way we do not need a Dynamics CRM running and get lighting fast unit-tests.

To do so I created myself a mock-class for ICrmSession which I can reuse in other apps and projects. It basically catches all call to OrganizationContext.Execute() and records them in the property SentOrganizationRequests. To set up the fakes for IOrganizationService
and OrganzationContext I use my favorite faking library Fake it easy.

CrmSessionFake Implementation

using System;
using System.Collections.Generic;
using FakeItEasy;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;

namespace CrmTests
{
    /// <summary>
    /// Fake of a <see cref="ICrmSession"/> that can be used for unit-testing.
    /// </summary>
    /// <seealso cref="ICrmSession" />
    public class CrmSessionFake : ICrmSession
    {
        public CrmSessionFake()
        {
            OrganizationService = SetUpOrganizationServiceFake();
            OrganizationServiceContext = SetUpOrganizationServiceContextFake();
        }

        /// <summary>
        /// Gets the <see cref="OrganizationRequest"/>'s that where sent using <see cref="OrganizationService"/>
        /// or <see cref="OrganizationServiceContext"/>.
        /// </summary>
        /// <value>The sent <see cref="OrganizationRequest"/>'s.</value>
        public IList<OrganizationRequest> SentOrganizationRequests { get; } = new List<OrganizationRequest>();

        /// <summary>
        /// Gets the organization service.
        /// </summary>
        /// <value>The organization service.</value>
        /// <seealso cref="IOrganizationService"/>
        public IOrganizationService OrganizationService { get; }

        /// <summary>
        /// Gets the typed organization service context.
        /// </summary>
        /// <value>The organization service context.</value>
        /// <seealso cref="ICrmSession.OrganizationServiceContext"/>
        public OrganizationServiceContext OrganizationServiceContext { get; }

        /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
        public void Dispose()
        {
        }

        IOrganizationService SetUpOrganizationServiceFake()
        {
            var result = A.Fake<IOrganizationService>();

            // Record all call OrganizationRequest's sent to .Execute() in SentOrganizationRequests
            A.CallTo(() => result.Execute(A<OrganizationRequest>._))
                .ReturnsLazily((OrganizationRequest request) =>
                {
                    SentOrganizationRequests.Add(request);
                    return new OrganizationResponse
                    {
                        Results = new ParameterCollection(),
                        ResponseName = Guid.NewGuid().ToString(),
                    };
                });

            return result;
        }

        OrganizationServiceContext SetUpOrganizationServiceContextFake()
        {
            var result = A.Fake<OrganizationServiceContext>();
            A.CallTo(result)
                .Where(call => call.Method.Name == "CreateQuery")
                .WithNonVoidReturnType()
                .CallsBaseMethod();

            return result;
        }
    }
}

Then I created a little reusable base class for all my tests which sets up a new CrmSessionFake for every test and disposes it on tear down of each test:

CrmSessionTestBase:

    public abstract class CrmSessionTestBase
    {
        public CrmSessionFake CrmSession { get; private set; }
        public IOrganizationService OrgService { get; private set; }

        [SetUp]
        public void SetUpCrmSession()
        {
            CrmSession = new CrmSessionFake();
        }

        [TearDown]
        public void TearDownCrmSession()
        {
            CrmSession?.Dispose();
            CrmSession = null;
        }
    }

Finally code the test for MyService:

using NUnit.Framework;

[TestFixture]
public class MyServiceTests : CrmSessionTestBase
{
    [Test]
    public void Test_InsertNewEntity()
    {
        // Arrange
        var service = new MyService(CrmSession);

        // Act
        service.Serve("John")

        // Assert that a CreateRequest was set up and fired corretly
        CrmSession.SentOrganizationRequests.Should().HaveCount(1);
        CrmSession.SentOrganizationRequests.First().Should().BeOfType<ExecuteTransactionRequest>();
        ((ExecuteTransactionRequest)CrmSession.SentOrganizationRequests.First()).Requests.Should().HaveCount(1);
        ((ExecuteTransactionRequest)CrmSession.SentOrganizationRequests.First()).Requests.First().Should().BeOfType<CreateRequest>();

        var request = (CreateRequest)((ExecuteTransactionRequest) CrmSession.SentOrganizationRequests.First()).Requests.First();
        var entity = request.Target;

        entity.Name.Should().Be("John")
    }
}

Remark: In practice the logic in MyService is slightly more complex then in this example so testing it makes a lot more sense and there may be more then one simple test.

Conclusion:

My main point besides providing some code samples is that even with the CRM SDK one can (and should) do unit-testing for your C# code. The most important one to test in every application is the business- / domain-logic. Its where the business value comes from and makes your application worth living.

You can go even further then the above samples. Some ideas (and work in progress at my company):

  • Implement helper methods in your test baseclass to do standard test validations like the SentOrganzationRequests.
  • Introduce more abstraction from the CRM SDK by wrapping the API’s you need in their own interface.
  • When writing CRM plugins and workflow-activities extract your logic out of your plugin code. Decouple them from the SDK plugin method.
  • In plugins and workflow activities wrap your OrgContext and Service in a ICrmSession too and write your code against this interface

Just some ideas.

Go and do it! Its cool coding work – way better then do manual testing all the time. Spend you time in automate things once instead in doing manual work (testing) over and over again.

You are a developer – a coder – not a click-user! 🙂

One thought on “Testability for Dynamics CRM SDK code Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: