5

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.

snaits
  • 330
  • 1
  • 3
  • 9
  • 1
    the author (Jordi Montaña) is a stackoverflow user, I think he will reply to this question when he see it – Guido Preite Aug 22 '16 at 09:01
  • thx @GuidoPreite! Just wondering, is that a custom plugin you have or an out of the box thing of Dynamics CRM which sets the order number in the Retrieve Request? – Jordi Aug 22 '16 at 13:30

2 Answers2

3

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

Jordi
  • 1,460
  • 9
  • 9
  • Thanks @jordi for this suggestion, but the code I'm testing executes a retrieve after the create, so I'd need to know the returned Guid from Create ahead. Can I tell FakeXrmEasy what to return? I'll update the question also. – snaits Aug 23 '16 at 06:51
  • No worries, just edited the answer with some extra details. You can pass a custom plugincontext and workflowcontext to any plugin / codeactivity. I've added a link to an example with the Followup plugin. Hope this helps now! Also, in general, CRUD operations are also mocked by default, but injecting these properties after a PostCreate for example, is more tricky and that's where the GetDefaultPluginContext is handy. – Jordi Aug 23 '16 at 08:54
  • This definitively helps a lot! The refactoring part was already planned, but I was hoping to have some kind of safety net in my tests before starting on it. However, I think your approach makes more sense, and the refactored code will be so much easier to test... Cheers! – snaits Aug 23 '16 at 09:28
  • Nice one! If you have any questions please let me know, and apologies if I didn't responded very quickly to this, I just monitor SO from time to time :) – Jordi Aug 23 '16 at 09:34
  • Hi Stian @snaits, I added some [code coverage metrics](https://cdn.rawgit.com/jordimontana82/fake-xrm-easy/master/test/reports/index.htm?v=1) last week. Hope you'll like them! – Jordi Aug 29 '16 at 09:29
1

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.

Daryl
  • 18,592
  • 9
  • 78
  • 145
  • Thanks @Daryl - Please see my updated question, I'll need to mock the Create() call also, since I'll need the created object's Id when retrieving it. – snaits Aug 23 '16 at 06:56
  • Hey @Daryl, another already implemented approach exists in FakeXrmEasy, which is by injecting custom Plugin and Codeactivity contexts as opposed to "remocking" each individual call (like you did with .WithFakeCreate). I personally ended up the route of mocking the context because it saves me from: 1) mocking each individual call every time, as opposed to passing a plugin context with the minimum set of info, and, 2) Avoid me from replicating plugin codebase into the test codebase, which will be harder to maintain in future (duplication) – Jordi Aug 23 '16 at 09:51
  • So @Jordi, when Plugin A Depends on B, you include A in the context first? Then the plugin Context executes the plugin whenever it is applicable? (PreCreate, Post Delete, etc...?) – Daryl Aug 23 '16 at 12:33
  • Not really, I mean, I would unit test plugin B and plugin A separately, and use the injected plugin context or workflow contexts whenever needed. That would be if plugin A needs to check the Stage for example, or anything from the plugin execution pipeline. If the plugin doesn't need anything like that, then just pass the default plugin context in a single call. But the point is that, from a unit testing perspective, I'll test the 2 plugins in isolation from each other. How the plugins interact between each other would be a different question which sounds like Integration testing to me. – Jordi Aug 23 '16 at 13:30
  • @Jordi, I guess I misunderstood your first comment. Are you just saying you mock out the contexts rather than the IOrganizationService? – Daryl Aug 23 '16 at 13:35
  • Sorry no, organisation service is mocked by default, queries, crud operations, and many CRM messages. In addition, you can inject a fake plugin context into the plugin context, if your plugin needs it. The plugin context will receive & use the also mocked Organistionservice. Basically the plugin uses: Orgservice + Plugin context + other things like the TracingService, which is also mocked by default in FakeXrmEasy. Does that make sense? – Jordi Aug 23 '16 at 13:39
  • Ah, yes, I do that as well @Jordi. I was just showing how to Fake the IOrganizationService. this would then be passed into the faked context. – Daryl Aug 23 '16 at 13:49