【问题标题】:Mocking/Faking an Active CRM Entity模拟/伪造一个活跃的 CRM 实体
【发布时间】:2019-04-12 03:09:10
【问题描述】:

我一直在使用 Moq 来模拟各种 CRM 早期绑定实体,以便我可以对我的插件进行单元测试。我想伪造或模拟一个活动帐户,但问题是statecode 是一个只读字段,而不是虚拟字段。例如,当我尝试模拟 Account 早期绑定实体以指定如果访问其 statecode 时它应该返回的内容,我得到:

var accountMock = new Mock<Account>();
accountMock.SetupGet(x => x.statecode).Returns(AccountState.Active);

NotSupportedException 被抛出:Invalid setup on a non-virtual (overridable in VB) member: x =&gt; x.statecode。发生这种情况是因为在 SDK 提供的 Account 的早期绑定包装类中,statecode 字段不是虚拟的。 Moq 不能像我要求的那样覆盖它!我想,“为什么不为我的 Account 类做一个包装器呢?”。

可以通过将我要模拟的每个实体的statecode 属性设置为virtual 来更改生成的代码,但是当/如果重新生成实体包装器时,这不会一直存在。这似乎也不是起订量的方式,但我可能弄错了。

我目前的工作后备是从文件中读取已经处于活动状态的 XML 序列化帐户,但这确实违背了模拟的目的,因为我基本上有一个要读取的示例数据文件。它有效,但它不是在嘲笑。

我最有希望的努力是制作一个扩展 Account 的 TestAccount 包装器,让它看起来像你可以设置和获取 statecode。这是最有希望的,因为我实际上可以模拟那个 TestAccount 类,告诉 OrganizationService 返回一个活动的statecodestatuscode(它做到了!),并确认当它是Entity 类型时,它有正确的字段。当 TestAccount 实例最终转换为早期绑定的 Account 类型时,它失败了。 statuscode 设置卡住了,但statecode 设置没有设置可能是因为statecode 没有像statuscode 那样的公共设置器。

我会通过代码来解释!

// Wrapper class for Account so I can mock active and inactive Accounts by changing the statecode and statuscode
public class AccountWrapper : Account
{
    // the member to store our "set statecode" values; only for use in testing and mocking
    private AccountState? _statecode;

    // override and replace the base class statecode
    public new virtual AccountState? statecode
    {
        get { return _statecode; }
        // this is how I intend to get around the read-only of this field in the base class
        // the wrapper pretends to allow the statecode to be set, when it really does not stick on the actual Account entity
        set { _statecode = value; } 
    }
}

以及组织服务模拟在调用检索帐户时返回活动帐户的具体设置案例:

var activeAccountMock = new Mock<AccountWrapper>();
activeAccountMock.SetupGet(x => x.statecode).Returns(AccountState.Active);
var serviceMock = new Mock<IOrganizationService>();
serviceMock.Setup(t =>
    t.Retrieve(It.Is<string>(s => s == Account.EntityLogicalName),
               It.IsAny<Guid>(), // don't care about a specific Account
               It.IsAny<ColumnSet>())) // don't care about a specific ColumnSet
     .Returns(activeAccountMock.Object);

当我检查 service.Retrieve 方法返回的对象时,它几乎完全符合我的要求! Entity 类型的帐户有 statecode 设置我想要的方式,但在实体转换为 Account 的那一刻,statecode 回到 null。这可能是因为转换调用了 Account 构造函数,该构造函数创建了一个所有字段都为 null 的 Account 对象,然后将所有具有公共设置器的字段设置为可用值。基本上,当 Account 处于后期绑定时,它是我想要的几乎,而当它处于早期绑定时,我会丢失我设置的 statecode 值。

// this guy is type Entity, and its statecode is what I want!
var accountLateBound = service.Retrieve(Account.EntityLogicalName, accountId, new ColumnSet(true));
// accountLateBound["statecode"] is AccountState.Active YAY!
// this clobbers my glorious statecode mock...
Account accountEarlyBound = accountLateBound.ToEntity<Account>();
// accountEarlyBound.statecode is null BOO!

我的生产代码完全有充分的理由使用早期绑定实体 - 主要是早期绑定的开发并不糟糕(比如智能感知、编译器检查等)。我不想更改生产代码,以便它与 Moq 更好地结合。那和早期绑定的实体太棒了!

我的起订量有问题吗?我需要把它吸起来并在我的生产代码中使用 AccountWrapper 吗?在这种情况下,我是否应该转换为 early-bound,以免丢失该状态码?然后我将不得不更改生产代码以混合后期和早期绑定......哎呀。我担心这样做,因为包装器给出了您可以直接通过account.statecode = AccountState.[Active|Inactive] 而不是使用SetStateRequest 设置实体的状态码的想法。 知道它不会,cmets 解释它不会,但它看起来像你可以的事实意味着有人会这样做并且期待它的工作。

模拟插件逻辑的整个想法是,我根本不需要联系 CRM 做任何事情!我可以模拟我需要使用的任何实体。对插件逻辑进行单元测试时,没有理由使用真实数据

橡皮鸭已经厌倦了听我说...

tl;dr - 我可以模拟早期绑定 CRM 实体的只读状态码,以便我可以在必要时使用 Moq 和继承/包装器/接口对各种状态码的实体进行单元测试?如果是,如何?如果不是,为什么?

【问题讨论】:

  • tl;dr 我无法谈论您以这种方式更新或创建记录的特定方法,但在我模糊的大脑深处,我似乎记得您必须(或者可能是“应该”)总是同时设置状态码和状态码,因为状态码依赖于状态码(作为一个子类别,有效地),所以通过一起更新可以避免它们错位的问题
  • 有趣...我会调查的。我认为早期绑定实体包装器只有一个构造函数,并且它不带任何参数。

标签: c# unit-testing dynamics-crm-2011 moq


【解决方案1】:

早期绑定实体只是后期绑定的包装。试试下面的代码。基本上它在 Attributes 集合中设置值,这是基本帐户实际从中读取的值。如果您尝试在 CRM 中更新或创建它,它会爆炸,但如果一切都是本地的,它应该可以正常工作。

// Wrapper class for Account so I can mock active and inactive Accounts by changing the statecode and statuscode
public class AccountWrapper : Account
{
    // the member to store our "set statecode" values; only for use in testing and mocking
    private AccountState? _statecode;

    // override and replace the base class statecode
    public new virtual AccountState? statecode
    {
        get { return _statecode; }
        // this is to get around the read-only of this field in the base class
        // the wrapper pretends to allow the statecode to be set, when it really does not stick on the actual Account entity
        set
        { 
            _statecode = value;
            if(value == null){
                if(this.Attributes.Contains("statecode")){
                    this.Attributes.Remove("statecode")
                }
            }
            else
            {
                this.SetAttributeValue("statecode", _statecode);
            }
        } 
    }
}

更新:

我强烈建议使用 CRM 特定的模拟框架(例如 XrmUnitTest)对 CRM/CDS 实体进行单元测试。此外,XrmToolBox 中的 EarlyBoundGenerator 将允许将所有属性生成为可编辑的。

【讨论】:

  • 我看到了这个包装器和我发布的包装器之间的区别。我会试试这个!
  • 就是这样!使用属性集合!
  • 我已经创建了一个广泛的 XRM 测试框架,仍然需要添加更多文档,但如果您有兴趣:github.com/daryllabar/XrmUnitTest
  • 自从这个答案以来我学到了很多东西,包括跟踪你的工作。感觉是时候回顾一下我从您和其他来源拼凑而成的拼凑测试框架了。感谢您的跟进!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-31
  • 2023-03-31
  • 1970-01-01
  • 2016-03-19
  • 2013-08-29
  • 1970-01-01
相关资源
最近更新 更多