I have been using Moq to mock various CRM early bound entities so I can unit test my plugins. I want to fake or mock an Active Account, but the problem is that the statecode is a readonly and not virtual field. For instance, when I try to mock the Account early-bound entity to specify what it should return if its statecode is accessed I get:

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

and the NotSupportedException is thrown: Invalid setup on a non-virtual (overridable in VB) member: x => x.statecode. This happens because in the early-bound wrapper class for Account provided by the SDK the statecode field is not virtual. Moq cannot override it like I ask it to do! I thought, "why not make a wrapper for the Account class I have?".

I could alter the generated code by setting the statecode attribute of each entity I want to mock to virtual, but that will not stick around when/if the entity wrappers are regenerated. It also does not seem to be the Moq way, but I could be mistaken.

My currently working fallback is to read an XML serialized Account that is already active from a file, but that really defeats the purpose of mocking since I basically have a sample data file to be read. It works, but it is not mocking.

My most promising endeavor was making a TestAccount wrapper which extended Account and made it look like you could set as well as get statecode. It was the most promising because I could actually mock that TestAccount class, tell the OrganizationService to return with an active statecode and statuscode (which it did!), and confirm that when it was of type Entity, it had the correct fields. It failed when the TestAccount instance was eventually converted to the early-bound Account type. The statuscode setting stuck, but the statecode setting did not presumably because there is no public setter for statecode like there is for statuscode.

I will explain via code!

// 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; } 
    }
}

And the specific setup case for the organization service mock to return an active Account when Retrieve Account is called:

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

When I inspect the object returned by the service.Retrieve method, it does almost exactly what I want! The Account of type Entity has statecode set the way I want, but the moment the entity is converted to Account, the statecode goes back to null. This is probably because the conversion calls the Account constructor which creates an Account object with all fields null and then sets all the fields that have a public setter with the values available. Basically, when the Account is late-bound, it is what I want almost, and when it is early-bound, I lose my statecode value that I set.

// 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!

My production code uses early-bound entities exclusively for good reason - mainly development with early-bound does not suck (thar be intellissense, compiler checks, etc). I do not want to change the production code just so it meshes better with Moq. That and early-bound entities are awesome!

Am I doing something wrong with Moq? Do I need to suck it up and use the AccountWrapper in my production code? Should I not convert to early-bound in this case so I don't lose that statecode? Then I would have to change the production code to mix late and early bound... yuck. I am worried about doing that since the wrapper gives off the idea that you can set the statecode of an entity directly via account.statecode = AccountState.[Active|Inactive] rather than using the SetStateRequest. I know it does not, the comments explain it does not, but the fact that it looks like you can means that someone will do it and expect it to work.

The whole idea of mocking the plugin logic was so I would not need to contact CRM at all for anything! I could mock whatever entities I needed to use. When unit testing the plugin logic, there is no reason to use real data

The rubber ducky has gotten sick of listening to me...

tl;dr - Can I mock the read-only statecode of an early-bound CRM entity so I can unit test with entities of various statecodes using Moq and inheritance/wrappers/interfaces if necessary? If yes, how? If no, why?

有帮助吗?

解决方案

The Early Bound entities are just a wrapper around the late bound. Try the code below. Basically it sets the value in the Attributes collection, which is what the base Account actually reads it from. It'll bomb if you attempt to updated or create it in CRM, but if everything is local, it should work just fine.

// 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);
            }
        } 
    }
}

UPDATE:

I'd highly recommend using a CRM specific mocking framework like XrmUnitTest for doing Unit Testing with CRM/CDS Entities. Also, the EarlyBoundGenerator in the XrmToolBox will allow all properties to be generated as editable.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top