Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fake plugin behavior in FakeXrmEasy?

Tags:

I've been using FakeXrmEasy to write unit tests for the past week or so, and I'm generally happy with how it works. But there's one area where I'm not able to get the mock to work as I want it to.

In the Dynamics CRM installation, there's a plugin running, that sets the Order Number on the Sales Order. Without this, the returned order number's value is always null.

How can i tell the FakeXrmEasy mock to set the ordernumber value? Ideally I'd like to tap into the request pipeline more or less like so:

    var context = new FakeXrmEasy.XrmFakedContext();
    context.Initialize(TestEntities);    

    context.TamperWithResults<RetrieveRequest>( x => { 
           return SetOrderNumber(x); 
        });

   context.GetFakedOrganizationService();

   var result = context.Retrieve(...);

I could attempt using the .AddExecutionMock to mock the entire result, but the response in question is used to verify that the sales order was indeed saved with the correct values.

Update - more detailed info Perhaps I should have gone a little more into detail when asking the question. I've just joined an exisiting project and I'm writing tests to existing code. The failing test is running a function that does this:

  • Validates input
  • Creates sales order
  • Retrieves that sales order
  • Create SalesOrderDetails for each order line
  • Returns a result object containing the order number

Now since the function attempts to save the order, I cannot add it to the context in setup unless I can specify the Guid that will be returned by the Create() call.

like image 546
snaits Avatar asked Aug 22 '16 08:08

snaits


2 Answers

Assuming you are writing a unit test for testing whatever happens after that plugin (the plugin which populates the Order Number), the easiest solution is to initialize a sales order with the OrderNumber in it, which will be used as the precondition. Queries are automatically mocked by default, so that value should be returned. No need to inject anything in the pipeline because of that.

Example:

[Fact]
public void Example_test()
{
var context = new XrmFakedContext();
var service = context.GetFakedOrganizationService();
var salesOrder = new SalesOrder() { Id = Guid.NewGuid(), OrderNumber = 69 }; 
context.Initialize(new List<Entity>() { salesOrder });

//some stuff
//....

//Queries are automatically mocked so any LINQ,FetchXml, 
//QueryExpression or QueryByAttrubute should return the values you had in 
//the context initialisation or as a result of updates / creates during the test execution


var result = context.CreateQuery<SalesOrder>().FirstOrDefault();
Assert.Equal(result.OrderNumber, 69);

}

[EDIT]: If you want to inject the guid after the Create you can use the OutputParameters property for that. Here's an example with the FollowupPlugin.

There are several overloads for plugin execution, that example used to be an "old" one. There is a new general purpose method where you can pass a custom plugin context, where you can inject many properties, including the Outputparameters (look for GetDefaultPluginContext here).

But in general, and back to your original question, if you have many steps like these:

  • Validates input
  • Creates sales order
  • Retrieves that sales order
  • Create SalesOrderDetails
  • Returns a result object containing the order number

There could be many approaches to unit test this stuff, but my personal advice is that, those steps are way too many steps to be included in a single unit test. I'd rather refactor that logic so that it could be unit tested separately, more easily.

I'll resume that to only 3 steps to make it simpler:

  • Creation logic: You create a salesorder record, passing in some attributes.
  • Create plugin: A plugin fires in the create of the salesorder which populates the Order Number.
  • Other stuff: You have some logic afterwards which retrieves a salesorder and based on the OrderNumber (or other attributes) does something.

I would first, refactor that code so that I can test it much more easily, in small chunks. Doing this makes tests simpler to implement and understand & review.

I would create 3 different unit tests for each (at least!):

  • Creation logic: A unit test which doesn't require any input entities (so .Initialize() not needed) and just creates a salesorder entity record. Then Assert it has created whatever attributes you were expecting.
  • Create plugin: A unit test to execute the plugin and make sure it does whatever it is supposed to do
  • If you have any logic running after that, make sure it is refactored so that you can inject any properties / values, and then pass them (like the .OrderNumber) via the .Initialize() method in subsequent unit tests.

Hope this helps (now :P )!

like image 89
Jordi Avatar answered Sep 24 '22 16:09

Jordi


I can't argue with Jordi's answer, it's the same basic answer that I'd provide for an XrmUnitTest based unit test. I'll also give two conventions that I've developed to make this type of change more "automagic". These will be XrmUnitTest examples, but you (or @Jordi) could implement them in the FakeXrmEasy framework.

Option #1 Entity Fluent Builder

Create a fluent OrderNumber Builder. By default, have it default the Order Number to a particular number, or accept a value even:

public class SalesOrderBuilder : EntityBuilder<SalesOrder>
{
    public SalesOrder SalesOrder { get; set; }

    public SalesOrderBuilder()
    {
        SalesOrder = new SalesOrder();
        WithOrderNumber();
    }

    public SalesOrderBuilder(Id id)
        : this() { Id = id; }

    #region Fluent Methods

    public SalesOrderBuilder WithOrderNumber(string orderNumber = null)
    {
        orderNumber = orderNumber ?? "2";
        SalesOrder.OrderNumber = orderNumber;

        return this;
    }

    #endregion // Fluent Methods

    protected override SalesOrder BuildInternal() { return SalesOrder; }
}

Then you would call this when you're initializing your test data:

private class Example : TestMethodClassBase
{
    // Ids struct is used by the TestMethodClassBase to clean up any entities defined
    private struct Ids
    {
        public static readonly Id<SalesOrder> SalesOrder = new Id<SalesOrder>("7CF2BB0D-85D4-4B8C-A7B6-371D3C6EA37C");
    }

    protected override void InitializeTestData(IOrganizationService service)
    {
        new SalesOrderBuilder(Ids.SalesOrder).Create(service);
    }

    protected override void Test(IOrganizationService service)
    {
        // Run test
        Assert.IsNotNull(service.GetFirst<SalesOrder>().OrderNumber);

    }
}

Option #2 Fluent Organization Service Builder

Create a Fluent OrganizationServiceBuilder. Have it default to Adding Order Numbers:

public class OrganizationServiceBuilder : DLaB.Xrm.Test.Builders.OrganizationServiceBuilderBase<OrganizationServiceBuilder>
{
    protected override OrganizationServiceBuilder This
    {
        get { return this; }
    }

    #region Constructors


    public OrganizationServiceBuilder() : this(TestBase.GetOrganizationService()) {}

    public OrganizationServiceBuilder(IOrganizationService service) : base(service) { WithSalesOrderNumbersDefaulted(); }

    #endregion Constructors

    #region Fluent Methods

    private static int _salesNumber = 1;
    public OrganizationServiceBuilder WithSalesOrderNumbersDefaulted() {
        WithFakeCreate((s, e) =>
        {
            if (e.LogicalName == SalesOrder.EntityLogicalName && e.GetAttributeValue<string>(SalesOrder.Fields.OrderNumber) == null)
            {
                _salesNumber++; //Use Interlocking if thread safe is required 
                e[SalesOrder.Fields.OrderNumber] = _salesNumber;
            }
            return s.Create(e);
        });
        return this;
    }

    #endregion Fluent Methods
}

Then your test would just wrap that when you create it:

private class Example : TestMethodClassBase
{
    // Ids struct is used by the TestMethodClassBase to clean up any entities defined
    private struct Ids
    {
        public static readonly Id<SalesOrder> SalesOrder = new Id<SalesOrder>("7CF2BB0D-85D4-4B8C-A7B6-371D3C6EA37C");
    }

    protected override void InitializeTestData(IOrganizationService service)
    {
        service = new OrganizationServiceBuilder(service).WithSalesOrderNumbersDefaulted().Build();
        service.Create(new SalesOrder());
    }

    protected override void Test(IOrganizationService service)
    {
        // Run test
        Assert.IsNotNull(service.GetFirst<SalesOrder>().OrderNumber);

    }
}

Using either of these options will allow you to easily specify you want the auto-order-number defaulted, without you having to default it in every test. And if you add it to you test base class, you would have it set automatically.


Update 1

In response to OP's Update, unit testing a method that creates the entity.

Use the Fluen tOrganization Builder like so:

private class Example : TestMethodClassBase
{
    protected override void Test(IOrganizationService service)
    {
        service = new OrganizationServiceBuilder(service)
                          .WithSalesOrderNumbersDefaulted()
                          .Build();
        // Execute Function for test
        var id = Example.ValidateAndCreateOrderAndDetail(service);
        Assert.IsNotNull(service.GetEntity<SalesOrder>(id).OrderNumber);
    }
}

When your ValidateAndCreateOrderDetail method runs, any SalesOrder that gets created will have a SalesOrder Number populated.

like image 24
Daryl Avatar answered Sep 23 '22 16:09

Daryl