为 Dynamics CRM 编写可测试的 TypeScript 类

Writing testable TypeScript classes for Dynamics CRM

如何使用 TypeScript 为 Dynamics CRM 表单编写可测试的 class?

所谓可测试,是指可以实例化并传递模拟 Xrm 对象的非静态 class。

我目前的方法是这样的,但它有局限性(我会解释):

export class Contact {
    Xrm: Xrm.XrmStatic;

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

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

首先,导出 Contact class 以便 CRM 表单可以引用。 CRM 无法在调用函数之前实例化对象,因此要调用这些方法,我在 CRM 表单设计器中使用 Contact.prototype.onLoad。

我的测试是这样的:

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

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

测试能够实例化 Contact 并将 XrmMock 传递给构造函数。随后,当调用 onLoad() 时,它会将 if (this.Xrm) 评估为 true,并使用 XrmMock。相反,当从 CRM 中调用 Contact.prototype.onLoad() 时,(this.Xrm) 为假,因为 Contact 从未被实例化。因此,在 onLoad() 中对 Xrm 的任何引用都使用默认的 Xrm 命名空间(这正是我们想要的)。我们想要这个是因为当打开 CRM 表单时,真正的 Xrm 依赖项由浏览器加载。

这里的限制是必须在每个想要使用 Xrm 依赖项的方法中编写条件检查。如何克服?

这是一个link to documentation on the Xrm.Page object model. My classes and Xrm mock are written using @types/xrm


感谢我在这里提出多个问题,如果您认为可以指导我写一个更具体的问题,请告诉我:)

为什么您会在 CRM 中调用 Contact.prototype.onLoad 方法而不调用实例化您的联系人的方法 class 以与您在测试中相同的方式但使用 CRM Xrm目的?例如

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

然后在表单的 OnLoad 处理程序中调用 ContactOnLoad。如果您只是调用 Contact.prototype.onLoadthis 可能会引用您的 window 对象,而不是 Contact.

的新实例

根据 J. Doe 的回答,我得到了类似如下的代码:

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 {
            // ...
        }
    }
}

解决方案是为要从 CRM 触发的每个事件创建一个独立于 Contact 对象的导出函数。上面的示例分离了 onLoad 方法,但您可以为其他 CRM 表单事件创建函数,例如字段更改:

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

    export class Contact { // ... }
}

您还可以更进一步,将导出的事件函数包装在每个实体表单的模块中,例如 Main 和 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 { // ... }
}

CRM 中的用法

只需将 Company.Contact.onLoad 添加到表单或字段的事件处理程序。

在测试中的使用

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', () => {
        // ...
    }
}