模拟/创建活动CRM实体

gfr*_*itz 4 c# unit-testing moq dynamics-crm-2011

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

var accountMock = new Mock<Account>();
accountMock.SetupGet(x => x.statecode).Returns(AccountState.Active);
Run Code Online (Sandbox Code Playgroud)

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

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

我目前的工作备用方法是从文件中读取一个已激活的XML序列化帐户,但是由于我基本上有一个要读取的示例数据文件,因此这实际上使模拟的目的无法实现。它有效,但不是在嘲笑。

我最有前途的工作是制作一个TestAccount包装器,该包装器扩展了Account并使其看起来既可以设置又可以得到statecode。这是最有前途的,因为我实际上可以模拟该TestAccount类,告诉OrganizationService返回一个active statecodestatuscode(这样做!),并确认它是type时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; } 
    }
}
Run Code Online (Sandbox Code Playgroud)

以及在调用“检索帐户”时组织服务模拟返回活动帐户的特定设置情况:

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);
Run Code Online (Sandbox Code Playgroud)

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

// 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!
Run Code Online (Sandbox Code Playgroud)

我的生产代码完全有理由使用早期绑定的实体-主要是使用早期绑定的开发不会产生影响(例如智能感知,编译器检查等)。我不想更改生产代码,只是为了使其与Moq更好地啮合。那个和早期绑定的实体太棒了!

Moq我在做错什么吗?我需要吸收它并在生产代码中使用AccountWrapper吗?在这种情况下,我是否应该转换为早绑定版本,以免丢失该状态码?然后,我将不得不更改生产代码以将后期和早期绑定混合在一起。我担心这样做,因为包装程序发出了这样一种想法,您可以直接通过account.statecode = AccountState.[Active|Inactive]而不是使用来设置实体的状态码SetStateRequest知道不是的,评论说明并没有,但是看起来像您这样的事实意味着有人会这样做并且期望它能起作用。

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

橡皮鸭已经厌倦了听我的话。

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

Dar*_*ryl 5

早期绑定实体只是后期绑定的包装。试试下面的代码。基本上,它在Attributes集合中设置值,这是基本Account实际从中读取的值。如果您尝试在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);
            }
        } 
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:

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