简体   繁体   中英

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 :)

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? Eg

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 .

From J. Doe's answer I've arrived at code similar to as follows:

module Company.Contact {
    let contact: Contact;

    export function onLoad(xrm?: Xrm.XrmStatic) {
        contact = new Contact(xrm || Xrm);
        contact.onLoad();
    }

    export class Contact {
        constructor(xrm?: Xrm.XrmStatic) { Xrm = xrm || Xrm; }

        onLoad(): void {
            // ...
        }
    }
}

The solution is to create an exported function that is separate from the Contact object for each event you want to fire from CRM. The example above separates the onLoad method, but you could create functions for other CRM form events such as a field change:

module Company.Contact { 
    export function onLoad(xrm?: Xrm.XrmStatic) { // ... }
    export function firstNameOnChange() { // ... }    

    export class Contact { // ... }
}

You can also go one step further, and wrap the exported event functions in a module per entity form, such as Main and Mobile:

module Company.Contact { 
    export module MainForm {
        export function onLoad(xrm?: Xrm.XrmStatic) {
            // ...
        }
    }

    export module MobileForm {
        export function onLoad(xrm?: Xrm.XrmStatic) {
            // ...
        }
    }

    export class Contact { // ... }
}

Usage in CRM

Just add Company.Contact.onLoad to the form or field's event handlers.

Usage in tests

beforeEach(() => {
    // Create your Xrm mock from a separate mocking library
    this.XrmMock = new XrmStaticMock();

    // Call onLoad
    Company.Contact.onLoad(this.XrmMock);

    // Or use this if using separate modules per form
    Company.Contact.MainForm.onLoad(this.XrmMock);
}

describe('onLoad', () => {
    it('does stuff', () => {
        // ...
    }
} 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM