Search code examples
typescripttestingdependency-injectiondynamics-crmcrm

Writing testable TypeScript classes for Dynamics CRM


How do you write a class for a Dynamics CRM form in TypeScript that is testable?

By testable, I mean a non-static class that can be instantiated and passed a mock Xrm object.

My current approach is something like this, but it has limitations (which I explain):

export class Contact {
    Xrm: Xrm.XrmStatic;

    constructor(xrm?: Xrm.XrmStatic) {
        this.Xrm = xrm;
    }

    onLoad(): void {
       if (this.Xrm) { Xrm = this.Xrm; }
       // ...
    }
}

Firstly, the Contact class is exported so that can be referenced by a CRM form. CRM can't instantiate an object before calling it's functions, so to call these methods I use Contact.prototype.onLoad in the CRM form designer.

My test looks like this:

beforeEach(function () { 
    this.XrmMock = new XrmStaticMock();
    this.contact = new Contact(this.XrmMock);
    this.contact.onLoad();
}

it('does stuff', function () {
    // ...
}

The test is able to instantiate Contact and pass XrmMock to the constructor. Subsequently when onLoad() is called, it evaluates if (this.Xrm) to true, and uses XrmMock. Conversely, when Contact.prototype.onLoad() is called from within CRM, (this.Xrm) is false because Contact has never been instantiated. Therefore any reference to Xrm within onLoad() uses the default Xrm namespace (which is what we want). We want this because when a CRM form is opened, the real Xrm dependency is loaded by the browser.

The limitation here is having to write a conditional check in every method which wants to use the Xrm dependency. How can this be overcome?

Here's a link to documentation on the Xrm.Page object model. My classes and Xrm mock are written using @types/xrm.


I appreciate I'm asking multiple questions here, if you think you can guide me to writing a more specific question, please let me know :)


Solution

  • Why would you call the Contact.prototype.onLoad method within CRM and not call a method that instantiates your Contact class in the same way as you do in your test but using CRM's Xrm object? E.g.

    function ContactOnLoad() {
        var contact = new Contact(Xrm);
        contact.onLoad();
    }
    

    Then in your form's OnLoad handler you call ContactOnLoad. If you just call Contact.prototype.onLoad, this might refer to your window object and not a new instance of Contact.